Skip to content

Commit

Permalink
Adjust types and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Aug 27, 2024
1 parent e214e14 commit 72dc4a9
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 115 deletions.
72 changes: 30 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,37 @@
## Why?

As per 2024, Node.js `fs.watch`:

* Doesn't report filenames on MacOS.
* Doesn't report events at all when using some text editors on MacOS.
* Often reports events twice.
* Emits most changes as `rename`.
* Does not provide an easy way to recursively watch file trees.
* Does not support recursive watching on Linux.

Node.js `fs.watchFile`:

* Almost as bad at event handling.
* Also does not provide any recursive watching.
* Results in high CPU utilization.
Chokidar is more useful than raw `fs.watch` / `fs.watchFile` in 2024:

- Events are properly reported:
- macOS events report filenames
- events are not reported twice
- changes are reported as add / change / unlink instead of useless `rename`
- Atomic writes are supported, using `atomic` option
- Some file editors use them
- Chunked writes are supported, using `awaitWriteFinish` option
- Large files are commonly written in chunks
- File / dir filtering is supported
- Symbolic links are supported
- Recursive watching is always supported, instead of partial when using raw events
- Includes a way to limit recursion depth

Chokidar relies on the Node.js core `fs` module, but when using
`fs.watch` and `fs.watchFile` for watching, it normalizes the events it
receives, often checking for truth by getting file stats and/or dir contents.

Chokidar resolves these problems.
The `fs.watch`-based implementation is the default, which
avoids polling and keeps CPU usage down. Be advised that chokidar will initiate
watchers recursively for everything within scope of the paths that have been
specified, so be judicious about not wasting system resources by watching much
more than needed. For some cases, `fs.watchFile`, which utilizes polling and uses more resources, is used.

Made for **[Brunch](https://brunch.io/)** in 2012,
it is now used in [~30 million repositories](https://www.npmjs.com/browse/depended/chokidar) and
has proven itself in production environments.

**Aug 2024 update:** v4 is out! It decreases dependency count from 13 to 2, removes
support for globs, adds support for ESM / Common.js hybrid, and increases minimum node.js version from v8 to v14.

## How?

Chokidar does still rely on the Node.js core `fs` module, but when using
`fs.watch` and `fs.watchFile` for watching, it normalizes the events it
receives, often checking for truth by getting file stats and/or dir contents.

On MacOS, chokidar by default uses a native extension exposing the Darwin
`FSEvents` API. This provides very efficient recursive watching compared with
implementations like `kqueue` available on most \*nix platforms. Chokidar still
does have to do some work to normalize the events received that way as well.

On most other platforms, the `fs.watch`-based implementation is the default, which
avoids polling and keeps CPU usage down. Be advised that chokidar will initiate
watchers recursively for everything within scope of the paths that have been
specified, so be judicious about not wasting system resources by watching much
more than needed.
support for globs, adds support for ESM / Common.js modules, and bumps minimum node.js version from v8 to v14.

## Getting started

Expand All @@ -53,21 +44,18 @@ Install with npm:
npm install chokidar
```

Then `require` and use it in your code:
Use it in your code:

```javascript
const chokidar = require('chokidar');

import chokidar from 'chokidar';
// One-liner for current directory
chokidar.watch('.').on('all', (event, path) => {
console.log(event, path);
});
```

## API

```javascript
// Example of a more typical implementation structure
// Extended options
// ----------------

// Initialize watcher.
const watcher = chokidar.watch('file, dir, or array', {
Expand Down Expand Up @@ -101,10 +89,10 @@ watcher.on('change', (path, stats) => {

// Watch new files.
watcher.add('new-file');
watcher.add(['new-file-2', 'new-file-3', '**/other-file*']);
watcher.add(['new-file-2', 'new-file-3']);

// Get list of actual paths being watched on the filesystem
var watchedPaths = watcher.getWatched();
let watchedPaths = watcher.getWatched();

// Un-watch some files.
await watcher.unwatch('new-file');
Expand Down
4 changes: 2 additions & 2 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,8 @@ export default class NodeFsHandler {

let closer;
if (opts.usePolling) {
options.interval =
opts.enableBinaryInterval && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;
const enableBin = opts.interval !== opts.binaryInterval;
options.interval = enableBin && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;
closer = setFsWatchFileListener(path, absolutePath, options, {
listener,
rawEmitter: this.fsw._emitRaw,
Expand Down
118 changes: 47 additions & 71 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,61 @@ import {
STR_END,
} from './handler.js';

export type ThrottleType = 'readdir' | 'watch' | 'add' | 'remove' | 'change';
export type EmitArgs = [EventName, Path, any?, any?, any?];
type AWF = {
stabilityThreshold: number;
pollInterval: number;
};

type BasicOpts = {
persistent: boolean;
ignoreInitial: boolean;
followSymlinks: boolean;
cwd?: string;
// Polling
usePolling: boolean;
interval: number;
binaryInterval: number; // Used only for pooling and if different from interval

export const SLASH = '/';
export const SLASH_SLASH = '//';
export const ONE_DOT = '.';
export const TWO_DOTS = '..';
export const STRING_TYPE = 'string';
alwaysStat?: boolean;
depth?: number;
ignorePermissionErrors: boolean;
atomic: boolean | number; // or a custom 'atomicity delay', in milliseconds (default 100)
// useAsync?: boolean; // Use async for stat/readlink methods

export const BACK_SLASH_RE = /\\/g;
export const DOUBLE_SLASH_RE = /\/\//;
export const SLASH_OR_BACK_SLASH_RE = /[/\\]/;
export const DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
export const REPLACER_RE = /^\.[/\\]/;
// ioLimit?: number; // Limit parallel IO operations (CPU usage + OS limits)
};

export type ChokidarOptions = Partial<
BasicOpts & {
ignored: string | ((path: string) => boolean); // And what about regexps?
awaitWriteFinish: boolean | Partial<AWF>;
}
>;

export type FSWInstanceOptions = BasicOpts & {
ignored: Matcher[]; // string | fn ->
awaitWriteFinish: false | AWF;
};

export type ThrottleType = 'readdir' | 'watch' | 'add' | 'remove' | 'change';
export type EmitArgs = [EventName, Path, any?, any?, any?];
export type MatchFunction = (val: string, stats?: Stats) => boolean;
export interface MatcherObject {
path: string;
recursive?: boolean;
}
export type Matcher = string | RegExp | MatchFunction | MatcherObject;

const SLASH = '/';
const SLASH_SLASH = '//';
const ONE_DOT = '.';
const TWO_DOTS = '..';
const STRING_TYPE = 'string';
const BACK_SLASH_RE = /\\/g;
const DOUBLE_SLASH_RE = /\/\//;
const DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
const REPLACER_RE = /^\.[/\\]/;

function arrify<T>(item: T | T[]): T[] {
return Array.isArray(item) ? item : [item];
}
Expand Down Expand Up @@ -113,9 +146,6 @@ const flatten = (list, result = []) => {
};

const unifyPaths = (paths_) => {
/**
* @type {Array<String>}
*/
const paths = flatten(arrify(paths_));
if (!paths.every((p) => typeof p === STRING_TYPE)) {
throw new TypeError(`Non-string provided as watch path: ${paths}`);
Expand Down Expand Up @@ -263,56 +293,6 @@ export class WatchHelper {
}
}

export type ChokidarOptions = Partial<{
persistent: boolean;

ignored: string | ((path: string) => boolean);
ignoreInitial: boolean;
followSymlinks: boolean;
cwd: string;

usePolling: boolean;
interval: number;
binaryInterval: number;
enableBinaryInterval: boolean;
alwaysStat: boolean;
depth: number;
awaitWriteFinish:
| boolean
| Partial<{
stabilityThreshold: number;
pollInterval: number;
}>;

ignorePermissionErrors: boolean;
atomic: boolean | number; // or a custom 'atomicity delay', in milliseconds (default 100)
}>;

export interface FSWInstanceOptions {
persistent: boolean;

ignored: Matcher[];
ignoreInitial: boolean;
followSymlinks: boolean;
cwd: string;

usePolling: boolean;
interval: number;
binaryInterval: number;
enableBinaryInterval: boolean;
alwaysStat: boolean;
depth: number;
awaitWriteFinish:
| false
| {
stabilityThreshold: number;
pollInterval: number;
};

ignorePermissionErrors: boolean;
atomic: boolean | number; // or a custom 'atomicity delay', in milliseconds (default 100)
}

/**
* Watches files & directories for changes. Emitted events:
* `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
Expand Down Expand Up @@ -344,7 +324,7 @@ export class FSWatcher extends EventEmitter {
_nodeFsHandler?: NodeFsHandler;

// Not indenting methods for history sake; for now.
constructor(_opts) {
constructor(_opts: ChokidarOptions) {
super();

const opts: Partial<FSWInstanceOptions> = {};
Expand All @@ -363,7 +343,6 @@ export class FSWatcher extends EventEmitter {
if (undef(opts, 'ignorePermissionErrors')) opts.ignorePermissionErrors = false;
if (undef(opts, 'interval')) opts.interval = 100;
if (undef(opts, 'binaryInterval')) opts.binaryInterval = 300;
opts.enableBinaryInterval = opts.binaryInterval !== opts.interval;

// Always default to polling on IBM i because fs.watch() is not available on IBM i.
if (isIBMi) {
Expand Down Expand Up @@ -1010,16 +989,13 @@ export class FSWatcher extends EventEmitter {
}
}

// Export FSWatcher class
// exports.FSWatcher = FSWatcher;

/**
* Instantiates watcher with paths to be tracked.
* @param {String|Array<String>} paths file/directory paths and/or globs
* @param {Object=} options chokidar opts
* @returns an instance of FSWatcher for chaining.
*/
export const watch = (paths, options) => {
export const watch = (paths: string | string[], options: ChokidarOptions) => {
const watcher = new FSWatcher(options);
watcher.add(paths);
return watcher;
Expand Down

0 comments on commit 72dc4a9

Please sign in to comment.