0
# File System Watching
1
2
Real-time file system monitoring for interactive development tools, providing change events and automatic map updates. The watching system supports multiple backends including Watchman, FSEvents, and Node.js fs.watch, with intelligent change detection and batching.
3
4
## Capabilities
5
6
### Watch Mode Configuration
7
8
Configure file system watching when creating the haste map.
9
10
```typescript { .api }
11
/**
12
* Enable file system watching in HasteMap options
13
*/
14
interface WatchOptions extends Options {
15
/** Enable file system watching, defaults to false */
16
watch?: boolean;
17
/** Use Watchman for file system monitoring, defaults to true */
18
useWatchman?: boolean;
19
/** Whether to throw errors on module name collisions in watch mode, defaults to false */
20
throwOnModuleCollision?: boolean;
21
}
22
```
23
24
### Change Event Handling
25
26
Subscribe to file system changes through the EventEmitter interface.
27
28
```typescript { .api }
29
/**
30
* HasteMap extends EventEmitter to provide change notifications
31
*/
32
interface IHasteMap extends EventEmitter {
33
/**
34
* Subscribe to file system change events
35
* @param eventType - Event type, currently only 'change' is supported
36
* @param handler - Function to handle change events
37
*/
38
on(eventType: 'change', handler: (event: ChangeEvent) => void): void;
39
}
40
41
/**
42
* Change event data structure providing access to changed files and updated maps
43
*/
44
interface ChangeEvent {
45
/** Queue of file system events that triggered this change */
46
eventsQueue: EventsQueue;
47
/** Updated HasteFS instance with current file system state */
48
hasteFS: IHasteFS;
49
/** Updated ModuleMap instance with current module mappings */
50
moduleMap: IModuleMap;
51
}
52
53
/**
54
* Queue of individual file system events
55
*/
56
interface EventsQueue extends Array<{
57
/** Absolute path to the changed file */
58
filePath: string;
59
/** File statistics, undefined for deleted files */
60
stat: Stats | undefined;
61
/** Type of change: 'add', 'change', or 'unlink' */
62
type: string;
63
}> {}
64
```
65
66
### Lifecycle Management
67
68
Methods for managing the watching lifecycle.
69
70
```typescript { .api }
71
/**
72
* Stop file system watching and cleanup resources
73
* @returns Promise that resolves when all watchers are closed
74
*/
75
end(): Promise<void>;
76
```
77
78
**Usage Examples:**
79
80
```typescript
81
import HasteMap from "jest-haste-map";
82
83
// Create haste map with watching enabled
84
const hasteMap = await HasteMap.create({
85
id: "my-project",
86
extensions: ["js", "ts", "jsx", "tsx"],
87
maxWorkers: 4,
88
platforms: ["ios", "android"],
89
roots: ["/src"],
90
retainAllFiles: true,
91
rootDir: "/project/root",
92
watch: true, // Enable watching
93
useWatchman: true, // Prefer Watchman if available
94
});
95
96
// Build initial map
97
const {hasteFS, moduleMap} = await hasteMap.build();
98
99
// Set up change handler
100
hasteMap.on('change', (event) => {
101
console.log(`Detected ${event.eventsQueue.length} file system changes:`);
102
103
for (const fileEvent of event.eventsQueue) {
104
console.log(` ${fileEvent.type}: ${fileEvent.filePath}`);
105
106
if (fileEvent.stat) {
107
console.log(` Size: ${fileEvent.stat.size} bytes`);
108
console.log(` Modified: ${fileEvent.stat.mtime}`);
109
}
110
}
111
112
// Access updated file system
113
const updatedFiles = event.hasteFS.getAllFiles();
114
console.log(`Total files now: ${updatedFiles.length}`);
115
116
// Check specific module resolution
117
const appPath = event.moduleMap.getModule('App');
118
if (appPath) {
119
console.log(`App module resolved to: ${appPath}`);
120
121
// Get updated dependencies
122
const appDeps = event.hasteFS.getDependencies(appPath);
123
console.log(`App dependencies: ${appDeps?.length || 0}`);
124
}
125
});
126
127
// In a real application, keep the process running
128
// The change handler will be called whenever files change
129
console.log("Watching for file changes...");
130
131
// Clean up when shutting down
132
process.on('SIGINT', async () => {
133
console.log("Shutting down file watcher...");
134
await hasteMap.end();
135
process.exit(0);
136
});
137
```
138
139
### Watcher Backend Selection
140
141
The watching system automatically selects the best available backend:
142
143
1. **Watchman** (preferred): Facebook's file watching service, optimal for large projects
144
2. **FSEvents** (macOS): Native macOS file system events, efficient for Mac development
145
3. **NodeWatcher**: Cross-platform fallback using Node.js fs.watch
146
147
```typescript
148
// The backend selection is automatic, but you can control preferences
149
const hasteMap = await HasteMap.create({
150
id: "my-project",
151
extensions: ["js", "ts"],
152
maxWorkers: 4,
153
platforms: [],
154
roots: ["/src"],
155
retainAllFiles: true,
156
rootDir: "/project/root",
157
watch: true,
158
useWatchman: false, // Disable Watchman, will use FSEvents or NodeWatcher
159
});
160
```
161
162
### Change Detection and Batching
163
164
The watching system includes intelligent change detection and batching:
165
166
- **Duplicate Detection**: Filters out duplicate events for the same file
167
- **Change Batching**: Groups rapid changes into single events (30ms intervals)
168
- **Mtime Checking**: Only processes files that have actually changed
169
- **Incremental Updates**: Only re-processes files that have changed, not the entire project
170
171
```typescript
172
// Example of handling batched changes
173
hasteMap.on('change', (event) => {
174
const changedFiles = event.eventsQueue.filter(e => e.type === 'change');
175
const addedFiles = event.eventsQueue.filter(e => e.type === 'add');
176
const deletedFiles = event.eventsQueue.filter(e => e.type === 'unlink');
177
178
console.log(`Batch contains: ${changedFiles.length} changed, ${addedFiles.length} added, ${deletedFiles.length} deleted`);
179
180
// Process different types of changes
181
for (const added of addedFiles) {
182
console.log(`New file detected: ${added.filePath}`);
183
184
// Check if it's a new module
185
const moduleName = event.hasteFS.getModuleName(added.filePath);
186
if (moduleName) {
187
console.log(` New module available: ${moduleName}`);
188
}
189
}
190
191
for (const deleted of deletedFiles) {
192
console.log(`File deleted: ${deleted.filePath}`);
193
194
// Note: deleted files won't exist in the updated hasteFS
195
const stillExists = event.hasteFS.exists(deleted.filePath);
196
console.log(` Still in map: ${stillExists}`); // Should be false
197
}
198
});
199
```
200
201
### Integration with Development Tools
202
203
File system watching is particularly useful for development tools:
204
205
```typescript
206
// Development server integration
207
class DevServer {
208
private hasteMap: IHasteMap;
209
private clients: WebSocket[] = [];
210
211
async start() {
212
this.hasteMap = await HasteMap.create({
213
id: "dev-server",
214
extensions: ["js", "ts", "jsx", "tsx", "css", "json"],
215
maxWorkers: 4,
216
platforms: [],
217
roots: ["/src", "/public"],
218
retainAllFiles: true,
219
rootDir: process.cwd(),
220
watch: true,
221
});
222
223
await this.hasteMap.build();
224
225
// Watch for changes and notify clients
226
this.hasteMap.on('change', (event) => {
227
const changes = event.eventsQueue.map(e => ({
228
type: e.type,
229
path: e.filePath,
230
size: e.stat?.size,
231
}));
232
233
// Notify all connected clients
234
this.clients.forEach(client => {
235
client.send(JSON.stringify({
236
type: 'file-change',
237
changes,
238
}));
239
});
240
241
// Rebuild if package.json changed
242
const packageJsonChanged = event.eventsQueue.some(
243
e => e.filePath.endsWith('package.json')
244
);
245
246
if (packageJsonChanged) {
247
console.log('package.json changed, triggering rebuild...');
248
this.rebuild();
249
}
250
});
251
}
252
253
async stop() {
254
await this.hasteMap.end();
255
}
256
257
private rebuild() {
258
// Restart the server or rebuild assets
259
}
260
}
261
```
262
263
### Error Handling in Watch Mode
264
265
Watch mode automatically handles many error conditions:
266
267
- **File Access Errors**: Files that become inaccessible are automatically removed from the map
268
- **Permission Errors**: Gracefully handled, with warnings logged
269
- **Watcher Failures**: Automatic fallback to different watcher backends
270
- **Module Collisions**: In watch mode, collisions generate warnings instead of errors by default
271
272
```typescript
273
// Watch mode error handling
274
const hasteMap = await HasteMap.create({
275
id: "my-project",
276
extensions: ["js", "ts"],
277
maxWorkers: 4,
278
platforms: [],
279
roots: ["/src"],
280
retainAllFiles: true,
281
rootDir: "/project/root",
282
watch: true,
283
throwOnModuleCollision: false, // Warn instead of throw in watch mode
284
console: {
285
// Custom console for handling warnings
286
log: console.log,
287
warn: (message) => {
288
console.warn(`[Haste Warning] ${message}`);
289
},
290
error: console.error,
291
},
292
});
293
294
hasteMap.on('change', (event) => {
295
// Changes are automatically applied even if some files had errors
296
console.log('Map updated successfully despite any individual file errors');
297
});
298
```