chokidar の使い方完全ガイド — Node.js ファイル監視ライブラリ
一言でいうと
chokidar は、Node.js の fs.watch / fs.watchFile をラップし、クロスプラットフォームで安定したファイル監視を提供するライブラリです。macOS でのファイル名欠落やイベント重複といった fs モジュールの既知の問題を解消し、add / change / unlink といった直感的なイベントで変更を通知してくれます。
どんな時に使う?
- 開発サーバーのホットリロード — ソースコードの変更を検知して自動でビルド・再起動する(Vite, webpack, Brunch などが内部で利用)
- ファイル同期・バックアップツール — 特定ディレクトリの変更をリアルタイムに検知して別の場所へ同期する
- ログ監視・自動処理パイプライン — ログファイルや入力ディレクトリへの新規ファイル追加を検知して後続処理をトリガーする
インストール
# npm
npm install chokidar
# yarn
yarn add chokidar
# pnpm
pnpm add chokidar
注意: v5 は ESM 専用パッケージです。Node.js v20 以上が必要です。CommonJS (
require) では利用できません。
基本的な使い方
最もシンプルなパターンは、ディレクトリを指定して全イベントを購読する形です。
import chokidar from 'chokidar';
// カレントディレクトリを再帰的に監視
const watcher = chokidar.watch('.', {
ignored: (path, stats) => stats?.isFile() && !path.endsWith('.ts'), // .ts ファイルのみ監視
persistent: true,
ignoreInitial: true, // 初回スキャン時のイベントを抑制
});
watcher
.on('add', (filePath) => {
console.log(`[追加] ${filePath}`);
})
.on('change', (filePath) => {
console.log(`[変更] ${filePath}`);
})
.on('unlink', (filePath) => {
console.log(`[削除] ${filePath}`);
})
.on('ready', () => {
console.log('初回スキャン完了。変更を監視中...');
})
.on('error', (error) => {
console.error(`監視エラー: ${error}`);
});
// プロセス終了時にクリーンアップ
process.on('SIGINT', async () => {
await watcher.close();
console.log('監視を終了しました');
process.exit(0);
});
よく使う API
1. chokidar.watch(paths, options) — 監視の開始
監視対象のパスとオプションを指定して FSWatcher インスタンスを返します。
import chokidar from 'chokidar';
// 単一ファイル
const watcher1 = chokidar.watch('src/index.ts');
// 複数パス(配列)
const watcher2 = chokidar.watch(['src', 'config', 'package.json']);
// オプション付き
const watcher3 = chokidar.watch('src', {
cwd: '/projects/my-app', // 基準ディレクトリ(イベントのパスが相対パスになる)
depth: 3, // 再帰の深さを制限
followSymlinks: false, // シンボリックリンクを辿らない
usePolling: false, // ポーリングを使わない(デフォルト)
alwaysStat: true, // イベントに常に fs.Stats を付与
});
watcher3.on('change', (filePath, stats) => {
// alwaysStat: true なので stats が常に渡される
console.log(`${filePath} が変更されました (${stats?.size} bytes)`);
});
v5 での変更点: glob パターン(
**/*.jsなど)のサポートは v4 で削除されました。フィルタリングにはignoredオプションを使用してください。
2. watcher.on(event, callback) — イベントリスナーの登録
利用可能なイベント一覧:
| イベント | 説明 | コールバック引数 |
|---|---|---|
add | ファイル追加 | (path, stats?) |
change | ファイル変更 | (path, stats?) |
unlink | ファイル削除 | (path) |
addDir | ディレクトリ追加 | (path, stats?) |
unlinkDir | ディレクトリ削除 | (path) |
error | エラー発生 | (error) |
ready | 初回スキャン完了 | なし |
all | 全イベント | (event, path, stats?) |
raw | 生イベント(内部用) | (event, path, details) |
// 'all' イベントで一括ハンドリング
watcher.on('all', (eventName, filePath) => {
switch (eventName) {
case 'add':
case 'change':
console.log(`ビルド対象: ${filePath}`);
break;
case 'unlink':
console.log(`削除検知: ${filePath}`);
break;
}
});
3. watcher.add(paths) — 監視対象の追加
実行中のウォッチャーに新しいパスを追加します。
const watcher = chokidar.watch('src', { ignoreInitial: true });
// 後から監視対象を追加
watcher.add('tests');
watcher.add(['docs/api.md', 'README.md']);
4. watcher.unwatch(paths) — 監視対象の除外
特定のパスの監視を停止します。ウォッチャー自体は動き続けます。
// 特定ファイルの監視を解除
await watcher.unwatch('src/legacy');
await watcher.unwatch(['tmp', 'dist']);
5. watcher.close() — 監視の完全停止
すべての監視を停止し、リソースを解放します。非同期メソッドなので await が必要です。
// 監視を完全に停止
await watcher.close();
console.log('すべてのファイル監視を停止しました');
補足: watcher.getWatched() — 監視中パスの取得
現在監視しているパスの一覧をオブジェクトとして返します。
const watched = watcher.getWatched();
// {
// '.': ['src', 'package.json'],
// 'src': ['index.ts', 'utils.ts']
// }
console.log(watched);
主要オプション早見表
import chokidar from 'chokidar';
const watcher = chokidar.watch('src', {
// --- パスフィルタリング ---
ignored: (path, stats) => stats?.isFile() && !path.endsWith('.ts'),
ignoreInitial: true, // 初回スキャンの add/addDir を抑制
cwd: process.cwd(), // 基準ディレクトリ
depth: undefined, // 再帰の深さ制限(undefined = 無制限)
followSymlinks: true, // シンボリックリンクを辿る
// --- パフォーマンス ---
usePolling: false, // true にするとポーリング(NFS等で必要)
interval: 100, // ポーリング間隔 (ms)
binaryInterval: 300, // バイナリファイルのポーリング間隔 (ms)
alwaysStat: false, // イベントに常に Stats を付与
// --- 書き込み検知 ---
awaitWriteFinish: false, // チャンク書き込み完了を待つ
// awaitWriteFinish: {
// stabilityThreshold: 2000, // サイズ安定までの待機時間 (ms)
// pollInterval: 100, // サイズチェック間隔 (ms)
// },
atomic: true, // アトミック書き込み(mv による置換)対応
// --- その他 ---
persistent: true, // プロセスを維持
ignorePermissionErrors: false,
});
類似パッケージとの比較
| 特徴 | chokidar | node:fs.watch | fb-watchman | nsfw |
|---|---|---|---|---|
| クロスプラットフォーム安定性 | ◎ | △(OS差異大) | ◎ | ○ |
| 再帰監視 | ◎(常時対応) | △(OS依存) | ◎ | ◎ |
| イベントの正確性 | ◎ | △(rename のみ等) | ◎ | ○ |
| 依存パッケージ数 | 1(v5) | 0(組み込み) | 多い | ネイティブ |
| セットアップの手軽さ | ◎ | ◎ | △(デーモン必要) | △(ネイティブビルド) |
| ESM 対応 | ◎(v5 ESM 専用) | ◎ | ○ | ○ |
| 利用実績 | ~3,000万リポジトリ | — | Facebook 社内 | — |
| 最小 Node.js | v20(v5) | — | v14+ | v14+ |
選定の目安:
- 手軽に安定したファイル監視をしたい → chokidar(最も実績豊富)
- 依存ゼロで最小限の監視 →
node:fs.watch(ただしOS差異の吸収は自前) - 超大規模リポジトリ → fb-watchman(差分検知に特化)
注意点・Tips
1. v5 は ESM 専用
// ✅ OK(ESM)
import chokidar from 'chokidar';
// ❌ NG(CommonJS — v5 では動作しない)
// const chokidar = require('chokidar');
CommonJS が必要な場合は v4 系(chokidar@^4)を使用してください。
2. ignored の関数シグネチャに注意
ignored に関数を渡す場合、引数が2つの関数は2回呼ばれます。1回目はパスのみ、2回目はパスと fs.Stats が渡されます。
// ディレクトリは通過させ、ファイルは .ts のみ許可
const watcher = chokidar.watch('src', {
ignored: (path, stats) => {
// stats が undefined の場合(1回目の呼び出し)はフィルタしない
if (!stats) return false;
// ファイルの場合、.ts 以外を無視
return stats.isFile() && !path.endsWith('.ts');
},
});
3. close() は必ず await する
close() は非同期です。await せずにプロセスを終了すると、ファイルディスクリプタのリークやイベントの取りこぼしが起きる可能性があります。
// ✅ 正しい
await watcher.close();
// ❌ リソースリークの可能性
watcher.close(); // await なし
process.exit(0);
4. NFS・Docker マウントでは usePolling: true
ネットワークファイルシステムや Docker のバインドマウントでは fs.watch のイベントが発火しないことがあります。その場合はポーリングに切り替えてください。
const watcher = chokidar.watch('/mnt/shared', {
usePolling: true,
interval: 500, // CPU 負荷を抑えるため間隔を広めに
});
環境変数 CHOKIDAR_USEPOLLING=1 でも切り替え可能です。
5. 大きなファイルの書き込みには awaitWriteFinish
動画ファイルやログファイルなど、書き込みに時間がかかるファイルを監視する場合は awaitWriteFinish を有効にしましょう。
const watcher = chokidar.watch('uploads', {
awaitWriteFinish: {
stabilityThreshold: 2000, // 2秒間サイズが変わらなければ完了とみなす
pollInterval: 100,
},
});
watcher.on('add', (filePath) => {
// 書き込み完了後に発火するので安全に読み取れる
console.log(`アップロード完了: ${filePath}`);
});
6. 監視範囲は最小限に
chokidar は指定パス配下を再帰的に監視します。node_modules や .git を含むプロジェクトルート全体を監視すると、大量のファイルディスクリプタを消費します。
// ❌ 非推奨:プロジェクトルート全体
chokidar.watch('.');
// ✅ 推奨:必要なディレクトリのみ + depth 制限
chokidar.watch(['src', 'config'], {
depth: 5,
ignored: (path) => path.includes('node_modules