gaze vs chokidar vs node-watch vs watchpack
File System Watching in Node.js for Frontend Tooling
gazechokidarnode-watchwatchpackSimilar Packages:

File System Watching in Node.js for Frontend Tooling

chokidar, gaze, node-watch, and watchpack are all Node.js libraries designed to monitor file system changes, a critical capability for modern frontend development workflows like live reloading, hot module replacement, and asset rebuilding. These packages abstract away the inconsistencies and limitations of Node.js's built-in fs.watch() API, offering cross-platform reliability, performance optimizations, and developer-friendly APIs. While they share the same fundamental goal — detecting when files or directories are created, modified, or deleted — they differ significantly in architecture, feature set, maintenance status, and integration patterns with larger toolchains.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
gaze2,417,4221,154-688 years agoMIT
chokidar012,08082.1 kB375 months agoMIT
node-watch034226.1 kB83 years agoMIT
watchpack039795.7 kB93 months agoMIT

File System Watching in Node.js: chokidar vs gaze vs node-watch vs watchpack

Watching files for changes is a foundational requirement in modern frontend development — whether you're triggering rebuilds, refreshing browsers, or updating dev server state. While Node.js provides fs.watch(), its behavior varies wildly across operating systems and often misses events or fails on network drives. That’s where dedicated watcher libraries come in. Let’s compare the four main options.

🚫 Deprecation Status: Don’t Use What’s Dead

First, an important reality check:

  • gaze is officially deprecated. Its npm page states: "This project is deprecated. Please use chokidar instead." The GitHub repo archive notice confirms this. Do not use gaze in new projects.

The other three (chokidar, node-watch, watchpack) are actively maintained as of 2024.

🛠️ Basic Usage: How You Set Up a Watcher

All libraries follow a similar event-driven pattern, but their APIs differ in ergonomics and flexibility.

chokidar uses a fluent, chainable API with explicit event types:

const chokidar = require('chokidar');

const watcher = chokidar.watch('src/**/*.js', {
  ignored: /node_modules/,
  persistent: true
});

watcher
  .on('add', path => console.log('File added:', path))
  .on('change', path => console.log('File changed:', path))
  .on('unlink', path => console.log('File removed:', path));

gaze (deprecated) used a callback-based approach with glob groups:

// ⚠️ DO NOT USE — shown for historical context only
const gaze = require('gaze');

gaze('src/**/*.js', (err, watcher) => {
  watcher.on('all', (event, filepath) => {
    console.log(event, filepath); // 'added', 'changed', 'deleted'
  });
});

node-watch mimics fs.watch() but adds glob support:

const watch = require('node-watch');

const watcher = watch('src/**/*.js', { recursive: true }, (evt, name) => {
  console.log(`${evt}: ${name}`); // 'update' or 'remove'
});

watchpack is more complex, designed for batched processing:

const Watchpack = require('watchpack');

const wp = new Watchpack({
  aggregateTimeout: 300 // wait 300ms before emitting changes
});

wp.watch({
  files: [],
  directories: ['src']
});

wp.on('aggregated', (changes, removals) => {
  console.log('Changed files:', changes);
  console.log('Removed files:', removals);
});

🔍 Feature Comparison: What Each Library Handles Well

Recursive Directory Watching

  • chokidar: Excellent recursive support with fine control via depth option and disableGlobbing.
  • gaze: Supported recursive globs but unreliably on some platforms (now irrelevant due to deprecation).
  • node-watch: Supports recursive: true but may struggle with deep trees or symlinks.
  • watchpack: Built for recursive watching as part of Webpack’s dependency graph traversal.

Glob Pattern Support

  • chokidar: Full glob support via micromatch, including negation (!) and complex patterns.
  • gaze: Had glob support but limited compared to modern standards.
  • node-watch: Basic glob support; doesn’t handle complex patterns like !(exclude).
  • watchpack: Doesn’t use globs directly; watches directories and filters internally based on Webpack’s needs.

Cross-Platform Reliability

  • chokidar: Uses multiple fallback strategies (fsevents on macOS, inotify on Linux, polling on Windows/network drives) for maximum reliability.
  • node-watch: Better than raw fs.watch() but still prone to missed events on network volumes.
  • watchpack: Inherits reliability from its underlying watcher (often chokidar in newer versions) but adds its own polling logic.

Performance Under Load

  • chokidar: Highly optimized with options like usePolling, interval, and binaryInterval for tuning.
  • node-watch: Lightweight but lacks advanced tuning; may consume more CPU under heavy I/O.
  • watchpack: Optimized for Webpack’s use case — batches changes to reduce handler invocations, which improves performance in large projects.

🧩 Integration Patterns: Where Each Shines

For General-Purpose Tools (CLI, Scripts, etc.)

Use chokidar. It’s battle-tested, well-documented, and handles edge cases gracefully. Example: a custom build script that recompiles TypeScript on change:

const chokidar = require('chokidar');
const { exec } = require('child_process');

chokidar.watch('src/**/*.ts').on('change', () => {
  exec('tsc', (err, stdout) => {
    if (err) console.error(err);
    else console.log('Rebuilt!');
  });
});

For Minimalist or Embedded Use Cases

Use node-watch if you can’t afford extra dependencies. It’s a single file with no external deps. Example: a simple log watcher:

const watch = require('node-watch');

watch('logs/app.log', (evt, name) => {
  if (evt === 'update') console.log('Log updated');
});

For Webpack Ecosystem Extensions

Use watchpack only if you’re writing a Webpack plugin or loader that needs to hook into its watching lifecycle. Otherwise, it’s overkill. Example: a custom resolver that watches non-standard files:

// Inside a Webpack plugin
apply(compiler) {
  const wp = new Watchpack();
  wp.watch({ directories: [customDir] });
  wp.on('aggregated', () => compiler.hooks.emit.callAsync());
}

⚙️ Advanced Capabilities

Symlink Handling

  • chokidar: Explicit followSymlinks option (default: true).
  • node-watch: Follows symlinks by default; no option to disable.
  • watchpack: Respects Webpack’s symlink resolution settings.

Polling Fallbacks

  • chokidar: Robust polling with configurable intervals.
  • node-watch: Basic polling via persistent: true but less tunable.
  • watchpack: Uses polling strategically when native watchers fail.

📊 Summary Table

Featurechokidargazenode-watchwatchpack
Status✅ Active❌ Deprecated✅ Active✅ Active
DependenciesModerateLightNoneHeavy (Webpack)
Glob Support✅ Full⚠️ Basic⚠️ Basic❌ (Directory-only)
Recursive Watch✅ Excellent⚠️ Unreliable✅ Basic✅ Optimized
Cross-Platform✅ Best-in-class⚠️ Inconsistent⚠️ Good✅ Good
Use Case FitGeneral-purposeAvoidLightweightWebpack-specific

💡 Final Recommendation

  • Default choice: chokidar — it’s the most reliable, feature-complete, and widely adopted solution.
  • Avoid: gaze — it’s deprecated and unmaintained.
  • Consider node-watch only if you need zero dependencies and simple watching.
  • Use watchpack exclusively when working within Webpack’s ecosystem.

In practice, if you’re building anything beyond a trivial script, chokidar is almost always the right answer. Its balance of power, reliability, and ease of use has made it the backbone of file watching in the JavaScript ecosystem for good reason.

How to Choose: gaze vs chokidar vs node-watch vs watchpack

  • gaze:

    Do not choose gaze for new projects. The package is officially deprecated as noted on its npm page and GitHub repository, with the author recommending migration to chokidar. While it once offered glob pattern support and a simpler API than raw fs.watch(), it lacks modern features, performance optimizations, and ongoing support. Existing projects using gaze should plan to migrate.

  • chokidar:

    Choose chokidar if you need a robust, cross-platform file watcher with excellent performance, fine-grained control over watch behavior, and active maintenance. It’s the de facto standard in modern frontend tooling (used by Webpack, Vite, and many others) and handles edge cases like network drives, symlinks, and recursive directory watching reliably. Ideal for CLI tools, bundlers, and any project requiring dependable file system monitoring.

  • node-watch:

    Choose node-watch if you need a lightweight, zero-dependency alternative that closely mirrors Node.js's native fs.watch() API but with better cross-platform consistency. It supports basic glob patterns and recursive watching without the overhead of more complex solutions. Suitable for simple scripts or small utilities where minimal dependencies and straightforward usage are priorities, but avoid it for large-scale or performance-sensitive applications.

  • watchpack:

    Choose watchpack if you're building or extending a Webpack-based toolchain, as it's specifically designed as Webpack's internal file watcher. It provides advanced features like aggregated change events, efficient polling strategies, and integration with Webpack's caching layer. While powerful in its niche, it's less suitable as a general-purpose watcher outside of Webpack ecosystems due to its specialized design and tighter coupling to Webpack's internals.

README for gaze

gaze Build Status Build status

A globbing fs.watch wrapper built from the best parts of other fine watch libs.
Compatible with Node.js >= 4.x, Windows, macOS, and Linux.

gaze

NPM

Usage

Install the module with: npm install gaze or place into your package.json and run npm install.

var gaze = require('gaze');

// Watch all .js files/dirs in process.cwd()
gaze('**/*.js', function(err, watcher) {
  // Files have all started watching
  // watcher === this

  // Get all watched files
  var watched = this.watched();

  // On file changed
  this.on('changed', function(filepath) {
    console.log(filepath + ' was changed');
  });

  // On file added
  this.on('added', function(filepath) {
    console.log(filepath + ' was added');
  });

  // On file deleted
  this.on('deleted', function(filepath) {
    console.log(filepath + ' was deleted');
  });

  // On changed/added/deleted
  this.on('all', function(event, filepath) {
    console.log(filepath + ' was ' + event);
  });

  // Get watched files with relative paths
  var files = this.relative();
});

// Also accepts an array of patterns
gaze(['stylesheets/*.css', 'images/**/*.png'], function() {
  // Add more patterns later to be watched
  this.add(['js/*.js']);
});

Alternate Interface

var Gaze = require('gaze').Gaze;

var gaze = new Gaze('**/*');

// Files have all started watching
gaze.on('ready', function(watcher) { });

// A file has been added/changed/deleted has occurred
gaze.on('all', function(event, filepath) { });

Errors

gaze('**/*', function(error, watcher) {
  if (error) {
    // Handle error if it occurred while starting up
  }
});

// Or with the alternative interface
var gaze = new Gaze();
gaze.on('error', function(error) {
  // Handle error here
});
gaze.add('**/*');

Minimatch / Glob

See isaacs's minimatch for more information on glob patterns.

Documentation

gaze([patterns, options, callback])

  • patterns {String|Array} File patterns to be matched
  • options {Object}
  • callback {Function}
    • err {Error | null}
    • watcher {Object} Instance of the Gaze watcher

Class: gaze.Gaze

Create a Gaze object by instancing the gaze.Gaze class.

var Gaze = require('gaze').Gaze;
var gaze = new Gaze(pattern, options, callback);

Properties

  • options The options object passed in.
    • interval {integer} Interval to pass to fs.watchFile
    • debounceDelay {integer} Delay for events called in succession for the same file/event in milliseconds
    • mode {string} Force the watch mode. Either 'auto' (default), 'watch' (force native events), or 'poll' (force stat polling).
    • cwd {string} The current working directory to base file patterns from. Default is process.cwd().

Events

  • ready(watcher) When files have been globbed and watching has begun.
  • all(event, filepath) When an added, changed, renamed, or deleted event occurs.
  • added(filepath) When a file has been added to a watch directory.
  • changed(filepath) When a file has been changed.
  • deleted(filepath) When a file has been deleted.
  • renamed(newPath, oldPath) When a file has been renamed.
  • end() When the watcher is closed and watches have been removed.
  • error(err) When an error occurs.
  • nomatch When no files have been matched.

Methods

  • emit(event, [...]) Wrapper for EventEmitter.emit. added|changed|renamed|deleted events will also trigger the all event.
  • close() Unwatch all files and reset the watch instance.
  • add(patterns, callback) Adds file(s) patterns to be watched.
  • remove(filepath) Removes a file or directory from being watched. Does not recurse directories.
  • watched() Returns the currently watched files.
  • relative([dir, unixify]) Returns the currently watched files with relative paths.
    • dir {string} Only return relative files for this directory.
    • unixify {boolean} Return paths with / instead of \\ if on Windows.

Similar Projects

Other great watch libraries to try are:

Contributing

In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using grunt.

Release History

  • 1.1.3 - Fix for Node 10 support (@aredridel). Officially dropping support for Node < 4.
  • 1.1.2 - Prevent more ENOENT errors from escaping (@alexgorbatchev).
  • 1.1.1 - Prevent fs.watch errors from escaping error handler (@rosen-vladimirov). Fix _addToWatched without path.sep (@wyicwx).
  • 1.1.0 - Update to globule@1.0.0 with minimatch >= 3.0.0.
  • 1.0.0 - Revert back to 0.5.2. Drop support for Node.js v0.8. Fix for maxListeners. Update globule to 0.2.0.
  • 0.6.4 - Catch and emit error from readdir (@oconnore). Fix for 0 maxListeners. Use graceful-fs to avoid EMFILE errors in other places fs is used. Better method to determine if pathwatcher was built. Fix keeping process alive too much, only init pathwatcher if a file is being watched. Set min required to Windows Vista when building on Windows (@pvolok).
  • 0.6.3 - Add support for Node.js v0.11
  • 0.6.2 - Fix argument error with watched(). Fix for erroneous added events on folders. Ignore msvs build error 4244.
  • 0.6.1 - Fix for absolute paths.
  • 0.6.0 - Uses native OS events (fork of pathwatcher) but can fall back to stat polling. Everything is async to avoid blocking, including relative() and watched(). Better error handling. Update to globule@0.2.0. No longer watches cwd by default. Added mode option. Better EMFILE message. Avoids ENOENT errors with symlinks. All constructor arguments are optional.
  • 0.5.2 - Fix for ENOENT error with non-existent symlinks [BACKPORTED].
  • 0.5.1 - Use setImmediate (process.nextTick for Node.js v0.8) to defer ready/nomatch events (@amasad).
  • 0.5.0 - Process is now kept alive while watching files. Emits a nomatch event when no files are matching.
  • 0.4.3 - Track file additions in newly created folders (@brett-shwom).
  • 0.4.2 - Fix .remove() method to remove a single file in a directory (@kaelzhang). Fixing “Cannot call method 'call' of undefined” (@krasimir). Track new file additions within folders (@brett-shwom).
  • 0.4.1 - Fix watchDir not respecting close in race condition (@chrisirhc).
  • 0.4.0 - Drop support for Node.js v0.6. Use globule for file matching. Avoid Node.js v0.10 path.resolve/join errors. Register new files when added to non-existent folder. Multiple instances can now poll the same files (@jpommerening).
  • 0.3.4 - Code clean up. Fix “path must be strings” errors (@groner). Fix incorrect added events (@groner).
  • 0.3.3 - Fix for multiple patterns with negate.
  • 0.3.2 - Emit end before removeAllListeners.
  • 0.3.1 - Fix added events within subfolder patterns.
  • 0.3.0 - Handle safewrite events, forceWatchMethod option removed, bug fixes and watch optimizations (@rgaskill).
  • 0.2.2 - Fix issue where subsequent add calls dont get watched (@samcday). removeAllListeners on close.
  • 0.2.1 - Fix issue with invalid added events in current working dir.
  • 0.2.0 - Support and mark folders with path.sep. Add forceWatchMethod option. Support renamed events.
  • 0.1.6 - Recognize the cwd option properly
  • 0.1.5 - Catch “too many open file” errors
  • 0.1.4 - Really fix the race condition with 2 watches
  • 0.1.3 - Fix race condition with 2 watches
  • 0.1.2 - Read triggering changed event fix
  • 0.1.1 - Minor fixes
  • 0.1.0 - Initial release

License

Copyright (c) 2018 Kyle Robinson Young
Licensed under the MIT license.