0
# File Watching
1
2
File system monitoring capabilities with event-driven notifications for file and directory changes, supporting recursive watching and various encoding options. Provides real-time monitoring of filesystem changes.
3
4
## Capabilities
5
6
### File System Watching
7
8
Monitor files and directories for changes with event-based notifications.
9
10
```javascript { .api }
11
/**
12
* Watch for changes on a file or directory
13
* @param filename - File or directory path to watch
14
* @param options - Watch options including encoding and recursion
15
* @param listener - Event listener function (optional)
16
* @returns FSWatcher instance
17
*/
18
watch(filename: string | Buffer, options?: WatchOptions, listener?: WatchListener): FSWatcher;
19
watch(filename: string | Buffer, listener: WatchListener): FSWatcher;
20
watch(filename: string | Buffer, encoding: string, listener: WatchListener): FSWatcher;
21
22
interface WatchOptions {
23
/** Encoding for filename in events */
24
encoding?: string;
25
/** Keep process alive while watching */
26
persistent?: boolean;
27
/** Watch subdirectories recursively */
28
recursive?: boolean;
29
}
30
31
type WatchListener = (eventType: 'rename' | 'change', filename?: string | Buffer) => void;
32
33
interface FSWatcher extends EventEmitter {
34
/** Close the watcher and stop monitoring */
35
close(): void;
36
37
// Event emitters
38
on(event: 'change', listener: WatchListener): this;
39
on(event: 'error', listener: (error: Error) => void): this;
40
on(event: 'close', listener: () => void): this;
41
}
42
```
43
44
**Event Types:**
45
- `'change'` - File content was modified
46
- `'rename'` - File was created, deleted, or renamed
47
48
**Usage Examples:**
49
50
```javascript
51
// Basic file watching
52
const watcher = fs.watch('/important.txt', (eventType, filename) => {
53
console.log(`Event: ${eventType} on ${filename}`);
54
});
55
56
// Watch with options
57
const dirWatcher = fs.watch('/project', {
58
recursive: true,
59
encoding: 'utf8'
60
}, (eventType, filename) => {
61
console.log(`${eventType}: ${filename}`);
62
});
63
64
// Watch with event listeners
65
const fileWatcher = fs.watch('/config.json');
66
fileWatcher.on('change', (eventType, filename) => {
67
if (eventType === 'change') {
68
console.log('File content changed:', filename);
69
// Reload configuration
70
reloadConfig();
71
} else if (eventType === 'rename') {
72
console.log('File was renamed or deleted:', filename);
73
}
74
});
75
76
fileWatcher.on('error', (err) => {
77
console.error('Watcher error:', err);
78
});
79
80
fileWatcher.on('close', () => {
81
console.log('Watcher closed');
82
});
83
84
// Close watcher when done
85
setTimeout(() => {
86
fileWatcher.close();
87
}, 10000);
88
```
89
90
### Directory Watching
91
92
Monitor directories for file and subdirectory changes.
93
94
```javascript
95
// Watch directory for file changes
96
const dirWatcher = fs.watch('/uploads', (eventType, filename) => {
97
if (eventType === 'rename') {
98
if (fs.existsSync(`/uploads/${filename}`)) {
99
console.log('New file added:', filename);
100
} else {
101
console.log('File removed:', filename);
102
}
103
} else if (eventType === 'change') {
104
console.log('File modified:', filename);
105
}
106
});
107
108
// Recursive directory watching
109
const projectWatcher = fs.watch('/project', { recursive: true }, (eventType, filename) => {
110
console.log(`Project change - ${eventType}: ${filename}`);
111
112
// Handle different file types
113
if (filename.endsWith('.js')) {
114
console.log('JavaScript file changed, may need restart');
115
} else if (filename.endsWith('.json')) {
116
console.log('Config file changed, reloading...');
117
}
118
});
119
120
// Watch multiple directories
121
function watchMultiple(paths) {
122
const watchers = [];
123
124
paths.forEach(path => {
125
const watcher = fs.watch(path, { recursive: true }, (eventType, filename) => {
126
console.log(`[${path}] ${eventType}: ${filename}`);
127
});
128
watchers.push(watcher);
129
});
130
131
// Return cleanup function
132
return () => {
133
watchers.forEach(watcher => watcher.close());
134
};
135
}
136
137
const cleanup = watchMultiple(['/src', '/config', '/public']);
138
```
139
140
### Event Handling Patterns
141
142
Advanced patterns for handling file system events.
143
144
```javascript
145
// Debounced file watching (avoid multiple rapid events)
146
function createDebouncedWatcher(path, delay = 100) {
147
const timers = new Map();
148
149
return fs.watch(path, { recursive: true }, (eventType, filename) => {
150
const key = `${eventType}:${filename}`;
151
152
// Clear existing timer
153
if (timers.has(key)) {
154
clearTimeout(timers.get(key));
155
}
156
157
// Set new timer
158
const timer = setTimeout(() => {
159
console.log(`Debounced event - ${eventType}: ${filename}`);
160
timers.delete(key);
161
// Handle the event here
162
handleFileChange(eventType, filename);
163
}, delay);
164
165
timers.set(key, timer);
166
});
167
}
168
169
// Filter events by file type
170
function createFilteredWatcher(path, extensions) {
171
return fs.watch(path, { recursive: true }, (eventType, filename) => {
172
if (!filename) return;
173
174
const ext = filename.split('.').pop()?.toLowerCase();
175
if (extensions.includes(ext)) {
176
console.log(`${eventType} on ${ext} file: ${filename}`);
177
processFileChange(eventType, filename);
178
}
179
});
180
}
181
182
// Usage: only watch JavaScript and TypeScript files
183
const codeWatcher = createFilteredWatcher('/src', ['js', 'ts', 'jsx', 'tsx']);
184
185
// Event aggregation
186
function createAggregatingWatcher(path, flushInterval = 1000) {
187
const events = [];
188
189
const watcher = fs.watch(path, { recursive: true }, (eventType, filename) => {
190
events.push({ eventType, filename, timestamp: Date.now() });
191
});
192
193
// Flush events periodically
194
const interval = setInterval(() => {
195
if (events.length > 0) {
196
console.log(`Processing ${events.length} events:`);
197
events.forEach(event => {
198
console.log(` ${event.eventType}: ${event.filename}`);
199
});
200
201
processBatchedEvents([...events]);
202
events.length = 0; // Clear events
203
}
204
}, flushInterval);
205
206
// Return watcher with custom close method
207
const originalClose = watcher.close.bind(watcher);
208
watcher.close = () => {
209
clearInterval(interval);
210
originalClose();
211
};
212
213
return watcher;
214
}
215
```
216
217
### Configuration File Watching
218
219
Monitor configuration files and reload application state.
220
221
```javascript
222
// Configuration file watcher with reload
223
class ConfigWatcher {
224
constructor(configPath, onReload) {
225
this.configPath = configPath;
226
this.onReload = onReload;
227
this.config = this.loadConfig();
228
this.watcher = null;
229
this.startWatching();
230
}
231
232
loadConfig() {
233
try {
234
const content = fs.readFileSync(this.configPath, 'utf8');
235
return JSON.parse(content);
236
} catch (err) {
237
console.error('Error loading config:', err);
238
return {};
239
}
240
}
241
242
startWatching() {
243
this.watcher = fs.watch(this.configPath, (eventType) => {
244
if (eventType === 'change') {
245
console.log('Config file changed, reloading...');
246
const newConfig = this.loadConfig();
247
248
if (JSON.stringify(newConfig) !== JSON.stringify(this.config)) {
249
this.config = newConfig;
250
this.onReload(newConfig);
251
}
252
}
253
});
254
255
this.watcher.on('error', (err) => {
256
console.error('Config watcher error:', err);
257
});
258
}
259
260
stop() {
261
if (this.watcher) {
262
this.watcher.close();
263
this.watcher = null;
264
}
265
}
266
267
getConfig() {
268
return this.config;
269
}
270
}
271
272
// Usage
273
const configWatcher = new ConfigWatcher('/app/config.json', (newConfig) => {
274
console.log('Configuration updated:', newConfig);
275
// Update application settings
276
updateAppSettings(newConfig);
277
});
278
```
279
280
### Build System File Watching
281
282
File watching patterns commonly used in build systems and development tools.
283
284
```javascript
285
// Development server with auto-reload
286
class DevServer {
287
constructor(srcPath, buildPath) {
288
this.srcPath = srcPath;
289
this.buildPath = buildPath;
290
this.building = false;
291
this.pendingBuild = false;
292
293
this.watcher = fs.watch(srcPath, { recursive: true }, (eventType, filename) => {
294
if (filename && this.shouldTriggerBuild(filename)) {
295
this.scheduleBuild();
296
}
297
});
298
}
299
300
shouldTriggerBuild(filename) {
301
// Only build for source files
302
const sourceExtensions = ['js', 'ts', 'jsx', 'tsx', 'css', 'scss'];
303
const ext = filename.split('.').pop()?.toLowerCase();
304
return sourceExtensions.includes(ext);
305
}
306
307
scheduleBuild() {
308
if (this.building) {
309
this.pendingBuild = true;
310
return;
311
}
312
313
// Debounce builds
314
setTimeout(() => {
315
this.runBuild();
316
}, 200);
317
}
318
319
async runBuild() {
320
if (this.building) return;
321
322
this.building = true;
323
console.log('Building...');
324
325
try {
326
await this.performBuild();
327
console.log('Build completed');
328
329
if (this.pendingBuild) {
330
this.pendingBuild = false;
331
setTimeout(() => this.runBuild(), 100);
332
}
333
} catch (err) {
334
console.error('Build failed:', err);
335
} finally {
336
this.building = false;
337
}
338
}
339
340
async performBuild() {
341
// Simulate build process
342
const sourceFiles = this.getAllSourceFiles(this.srcPath);
343
344
for (const file of sourceFiles) {
345
const content = fs.readFileSync(file, 'utf8');
346
const processed = this.processFile(content);
347
348
const outputPath = file.replace(this.srcPath, this.buildPath);
349
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
350
fs.writeFileSync(outputPath, processed);
351
}
352
}
353
354
getAllSourceFiles(dir) {
355
const files = [];
356
const entries = fs.readdirSync(dir, { withFileTypes: true });
357
358
for (const entry of entries) {
359
const fullPath = `${dir}/${entry.name}`;
360
if (entry.isDirectory()) {
361
files.push(...this.getAllSourceFiles(fullPath));
362
} else if (this.shouldTriggerBuild(entry.name)) {
363
files.push(fullPath);
364
}
365
}
366
367
return files;
368
}
369
370
processFile(content) {
371
// Simulate file processing
372
return content.replace(/\/\*\s*DEBUG\s*\*\/.*?\/\*\s*END_DEBUG\s*\*\//gs, '');
373
}
374
375
stop() {
376
if (this.watcher) {
377
this.watcher.close();
378
}
379
}
380
}
381
382
// Usage
383
const devServer = new DevServer('/src', '/build');
384
```
385
386
### Testing File Watching
387
388
File watching utilities for testing scenarios.
389
390
```javascript
391
// Test helper for file watching
392
class FileWatchTester {
393
constructor() {
394
this.events = [];
395
this.watchers = [];
396
}
397
398
watchFile(path) {
399
const watcher = fs.watch(path, (eventType, filename) => {
400
this.events.push({
401
eventType,
402
filename,
403
timestamp: Date.now(),
404
path
405
});
406
});
407
408
this.watchers.push(watcher);
409
return watcher;
410
}
411
412
clearEvents() {
413
this.events = [];
414
}
415
416
getEvents() {
417
return [...this.events];
418
}
419
420
waitForEvent(timeout = 1000) {
421
return new Promise((resolve, reject) => {
422
const startLength = this.events.length;
423
const timer = setTimeout(() => {
424
reject(new Error('Timeout waiting for file event'));
425
}, timeout);
426
427
const checkForEvent = () => {
428
if (this.events.length > startLength) {
429
clearTimeout(timer);
430
resolve(this.events[this.events.length - 1]);
431
} else {
432
setTimeout(checkForEvent, 10);
433
}
434
};
435
436
checkForEvent();
437
});
438
}
439
440
cleanup() {
441
this.watchers.forEach(watcher => watcher.close());
442
this.watchers = [];
443
this.events = [];
444
}
445
}
446
447
// Usage in tests
448
async function testFileWatching() {
449
const tester = new FileWatchTester();
450
451
try {
452
// Create test file
453
fs.writeFileSync('/test.txt', 'initial content');
454
455
// Start watching
456
tester.watchFile('/test.txt');
457
458
// Modify file
459
fs.writeFileSync('/test.txt', 'modified content');
460
461
// Wait for event
462
const event = await tester.waitForEvent();
463
console.log('Received event:', event);
464
465
// Check event type
466
assert.equal(event.eventType, 'change');
467
assert.equal(event.filename, '/test.txt');
468
} finally {
469
tester.cleanup();
470
}
471
}
472
```
473
474
## Encoding Options
475
476
Handle different filename encodings in watch events.
477
478
```javascript
479
// Watch with buffer encoding
480
const bufferWatcher = fs.watch('/files', { encoding: 'buffer' }, (eventType, filename) => {
481
if (filename instanceof Buffer) {
482
console.log('Filename as buffer:', filename);
483
console.log('Filename as string:', filename.toString('utf8'));
484
}
485
});
486
487
// Watch with hex encoding
488
const hexWatcher = fs.watch('/files', { encoding: 'hex' }, (eventType, filename) => {
489
console.log('Filename in hex:', filename);
490
const originalName = Buffer.from(filename, 'hex').toString('utf8');
491
console.log('Original filename:', originalName);
492
});
493
494
// Handle encoding errors gracefully
495
function createSafeWatcher(path, encoding = 'utf8') {
496
return fs.watch(path, { encoding }, (eventType, filename) => {
497
try {
498
if (filename) {
499
console.log(`${eventType}: ${filename}`);
500
} else {
501
console.log(`${eventType}: (filename not provided)`);
502
}
503
} catch (err) {
504
console.error('Error processing watch event:', err);
505
}
506
});
507
}
508
```
509
510
## Error Handling and Cleanup
511
512
Proper error handling and resource cleanup for file watchers.
513
514
```javascript
515
// Robust watcher with error handling
516
function createRobustWatcher(path, options = {}) {
517
let watcher = null;
518
let reconnectTimer = null;
519
const maxRetries = 5;
520
let retryCount = 0;
521
522
function startWatcher() {
523
try {
524
watcher = fs.watch(path, options, (eventType, filename) => {
525
retryCount = 0; // Reset retry count on successful event
526
console.log(`${eventType}: ${filename}`);
527
});
528
529
watcher.on('error', (err) => {
530
console.error('Watcher error:', err);
531
532
if (retryCount < maxRetries) {
533
retryCount++;
534
console.log(`Attempting to reconnect (${retryCount}/${maxRetries})...`);
535
536
reconnectTimer = setTimeout(() => {
537
startWatcher();
538
}, 1000 * retryCount); // Exponential backoff
539
} else {
540
console.error('Max retries reached, giving up');
541
}
542
});
543
544
watcher.on('close', () => {
545
console.log('Watcher closed');
546
});
547
548
console.log('Watcher started successfully');
549
} catch (err) {
550
console.error('Failed to start watcher:', err);
551
}
552
}
553
554
startWatcher();
555
556
// Return cleanup function
557
return () => {
558
if (reconnectTimer) {
559
clearTimeout(reconnectTimer);
560
}
561
if (watcher) {
562
watcher.close();
563
}
564
};
565
}
566
567
// Usage
568
const cleanup = createRobustWatcher('/important-files', { recursive: true });
569
570
// Clean up when done
571
process.on('SIGINT', () => {
572
cleanup();
573
process.exit(0);
574
});
575
```