Tiny universal file watcher for Node, Bun, and Deno. Chokidar-compatible, zero deps, 3KB minified.
Chokidar is 2MB+ and doesn’t run natively on Bun/Deno.
@rabbx/watcher gives you the same API with native speed everywhere and no dependencies.
npm i @rabbx/watcherimport { watch } from '@rabbx/watcher';
const watcher = watch('./src', {
ignored: [/node_modules/, /\.git/],
ignoreInitial: true,
recursive: true,
ignoreBinary: true
});
watcher.on('all', (event, file) => {
console.log(`${event}: ${file}`);
// trigger HMR, rebuild, restart
});
watcher.on('ready', () => console.log('Watcher ready'));`watch(paths, options)`
Create a watcher.
*Options*
Option Type Default Description
`ignored` `(string \| RegExp)[]` `[]` Ignore patterns
`ignoreInitial` `boolean` `false` Skip initial `add` events
`recursive` `boolean` `true` Watch subdirectories
`delay` `number` `50` Debounce delay in ms
`maxDepth` `number` `Infinity` Max recursion depth
`ignoreBinary` `boolean` `true` Skip images, videos, archives
`usePolling` `boolean` `false` Force polling mode
`pollInterval` `number` `1000` Polling interval in ms
`awaitWriteFinish` `boolean \| object` `false` Wait for file writes to finish
*awaitWriteFinish options:*
{
stabilityThreshold?: number; // ms file must be unchanged. Default 500
pollInterval?: number; // ms between checks. Default 100
}
Events
watcher.on('add', (path) => {})
watcher.on('change', (path) => {})
watcher.on('unlink', (path) => {})
watcher.on('all', (event, path) => {})
watcher.on('ready', () => {})
watcher.on('error', (err) => {})
`watcher.close()`
Stop all watchers and timers.-
Universal - Auto-detects runtime and uses
Bun.watch,Deno.watchFs, or Nodefs.watch. Falls back to polling if needed. -
Fast - Debounce + binary filtering cuts noise by 70-90%. No full file reads during polling.
-
Small - 120 LOC, zero deps, 3KB minified. Installs instantly.
-
Robust - Handles NFS, Docker volumes, WSL1 with polling fallback.
awaitWriteFinishprevents HMR on half-written files.
Full types included. No extra setup needed.
import { watch, WatcherOptions,Watcher } from '@rabbx/watcher';
const opts: WatcherOptions = {
ignored: [/node_modules/],
awaitWriteFinish: { stabilityThreshold: 500 }
};
const watcher:Watcher = watch('./src', {
include: ['**/*.ts', '**/*.js'], // only these files
exclude: ['**/*.test.ts', '**/node_modules/**'],
ignored: [/\\.git/, '/dist'], // regex or string
followSymlinks: false, // follow symlinks or not
yieldEvery: 500 // yield to event loop every N files
}) import { watch } from '@rabbx/watcher';
const watcher = watch(
['./src', './config'], // paths to watch
{
// 1. Filtering
include: ['**/*.ts', '**/*.js', '**/*.json'], // only watch these
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/.git/**',
'**/*.test.ts'
],
ignored: [/\.log$/, /\.tmp$/], // regex or substring match
ignoreBinary: true, // skip.png,.mp4,.exe etc
followSymlinks: false, // set true if you need symlinks
// 2. Recursion & depth
recursive: true,
maxDepth: 10, // prevent runaway scans in huge dirs
// 3. Performance & stability
delay: 100, // debounce events by 100ms
yieldEvery: 500, // yield event loop every 500 files during scan
ignoreInitial: false, // emit 'add' for existing files on startup
// 4. Write stability - crucial for VSCode, vim, editors
awaitWriteFinish: {
stabilityThreshold: 500, // file size unchanged for 500ms
pollInterval: 100 // check every 100ms
},
// 5. Fallback strategy
usePolling: false, // force polling if true
pollInterval: 1000, // polling interval in ms
autoFallback: true, // auto-switch to polling if native fails
fallbackTimeout: 2000 // wait 2s for native events before fallback
}
);
// 6. Event listeners
watcher
.on('ready', () => {
console.log('[watcher] Ready and watching');
})
.on('add', (path) => {
console.log('[add]', path);
// trigger build, restart, etc
})
.on('change', (path) => {
console.log('[change]', path);
// debounce rebuild here if needed
})
.on('unlink', (path) => {
console.log('[unlink]', path);
})
.on('all', (event, path) => {
// catch-all for logging/metrics
// console.log(event, path);
})
.on('fallback', ({ dir, reason }) => {
console.warn(`[watcher] Fallback to polling for ${dir}: ${reason}`);
// send to Sentry, Datadog, etc
})
.on('error', (err) => {
console.error('[watcher] Error:', err);
// don't crash the process
});
// 7. Graceful shutdown
const shutdown = async () => {
console.log('[watcher] Closing...');
await watcher.close();
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);License
If @rabbx/watcher saves you time, consider supporting development:
Your support helps keep the package maintained, tested, and fast across Bun, Node, and Deno.