File system monitoring and incremental rebuild capabilities for development workflows. The Watcher system provides efficient file change detection and automatic rebuilds.
The main class responsible for monitoring file system changes and triggering rebuilds.
/**
* Manages file watching and rebuilds for development workflows
*/
class Watcher {
/**
* Creates a new Watcher instance
* @param builder - Builder instance to use for rebuilds
* @param watchedNodes - Array of source node wrappers to watch
* @param options - Optional watcher configuration
*/
constructor(
builder: Builder,
watchedNodes: SourceNodeWrapper[],
options?: WatcherOptions
);
/** Watcher configuration options */
readonly options: WatcherOptions;
/** Builder instance used for rebuilds */
readonly builder: Builder;
/** File watcher adapter instance */
readonly watcherAdapter: WatcherAdapter;
/** Promise representing the current build operation */
readonly currentBuild: Promise<void>;
/**
* Start file watching and initial build
* @returns Promise that resolves when watching is active
*/
start(): Promise<void>;
/**
* Stop file watching and clean up resources
* @returns Promise that resolves when watching is stopped
*/
quit(): Promise<void>;
}
interface WatcherOptions {
/** Debounce delay in milliseconds for file change events (default: 100) */
debounce?: number;
/** Custom watcher adapter instance */
watcherAdapter?: WatcherAdapter;
/** Options passed to the underlying sane file watcher */
saneOptions?: any;
/** Array of glob patterns for files/directories to ignore */
ignored?: string[];
}Usage Examples:
const { Builder, Watcher, loadBrocfile } = require("broccoli");
// Basic file watching setup
const buildFn = loadBrocfile();
const tree = buildFn({ env: 'development' });
const builder = new Builder(tree);
const watcher = new Watcher(
builder,
builder.watchedSourceNodeWrappers,
{
debounce: 100,
ignored: ['**/.git/**', '**/node_modules/**']
}
);
// Start watching
await watcher.start();
console.log('Watching for file changes...');
// The watcher will automatically rebuild when files change
// Access current build status
console.log('Current build:', await watcher.currentBuild);
// Stop watching when done
process.on('SIGINT', async () => {
console.log('Stopping watcher...');
await watcher.quit();
process.exit(0);
});Low-level adapter that interfaces with the file system watcher library (sane).
/**
* Adapter for file system watching via sane package
*/
class WatcherAdapter {
/**
* Creates a new WatcherAdapter instance
* @param watchedNodes - Array of source nodes to watch
* @param options - Options passed to sane watchers
* @param ignored - Array of paths to ignore
*/
constructor(
watchedNodes: SourceNodeWrapper[],
options?: any,
ignored?: string[]
);
/** Array of active file watchers */
readonly watchers: any[];
/** Array of watched source node wrappers */
readonly watchedNodes: SourceNodeWrapper[];
/** Options passed to sane watchers */
readonly options: any;
/**
* Start watching the file system
* @returns Promise that resolves when all watchers are active
*/
watch(): Promise<void>;
/**
* Stop all file watchers and clean up
* @returns Promise that resolves when all watchers are stopped
*/
quit(): Promise<void>;
}Usage Examples:
const { WatcherAdapter } = require("broccoli");
// Custom watcher adapter setup
const adapter = new WatcherAdapter(
builder.watchedSourceNodeWrappers,
{
// Sane watcher options
poll: false,
watchman: true
},
['**/tmp/**', '**/.cache/**']
);
// Start watching manually
await adapter.watch();
// Stop watching
await adapter.quit();
// Use with custom Watcher
const customWatcher = new Watcher(
builder,
builder.watchedSourceNodeWrappers,
{
watcherAdapter: adapter,
debounce: 200
}
);Non-watching fallback watcher for build-only scenarios.
/**
* Non-watching fallback watcher for build-only mode
*/
class DummyWatcher extends EventEmitter {
/**
* Creates a dummy watcher that performs a single build
* @param builder - Builder instance to use
*/
constructor(builder: Builder);
/** Builder instance */
readonly builder: Builder;
/** Current build promise */
readonly currentBuild: Promise<void>;
/**
* Perform a single build without watching
* @returns Promise that resolves when build is complete
*/
start(): Promise<void>;
/**
* No-op quit method for consistency
*/
quit(): void;
}Usage Examples:
const { DummyWatcher } = require("broccoli");
// Build once without watching
const dummyWatcher = new DummyWatcher(builder);
await dummyWatcher.start();
console.log('Single build completed');
dummyWatcher.quit(); // No-op but maintains API consistencyUtility functions for binding file system events to rebuild triggers.
/**
* Bind file system events to watcher adapter
* @param adapter - WatcherAdapter instance
* @param watcher - Sane watcher instance
* @param node - Node wrapper to associate with events
* @param event - Type of file system event
*/
function bindFileEvent(
adapter: WatcherAdapter,
watcher: any,
node: TransformNodeWrapper | SourceNodeWrapper,
event: 'change' | 'add' | 'delete'
): void;Usage Examples:
const { bindFileEvent } = require("broccoli");
// Custom event binding (advanced usage)
const adapter = new WatcherAdapter(watchedNodes);
const saneWatcher = adapter.watchers[0];
const sourceNode = builder.watchedSourceNodeWrappers[0];
// Bind specific events
bindFileEvent(adapter, saneWatcher, sourceNode, 'change');
bindFileEvent(adapter, saneWatcher, sourceNode, 'add');
bindFileEvent(adapter, saneWatcher, sourceNode, 'delete');Common configuration patterns for different development scenarios.
Development with Fast Rebuilds:
const devWatcher = new Watcher(builder, watchedNodes, {
debounce: 50, // Fast response to changes
saneOptions: {
watchman: true, // Use Facebook's Watchman if available
poll: false // Disable polling for performance
}
});Large Project Watching:
const largeProjectWatcher = new Watcher(builder, watchedNodes, {
debounce: 300, // Longer debounce for stability
ignored: [
'**/node_modules/**',
'**/.git/**',
'**/tmp/**',
'**/dist/**',
'**/.cache/**'
],
saneOptions: {
ignored: /node_modules|\.git/,
poll: false
}
});Docker/VM Development:
const dockerWatcher = new Watcher(builder, watchedNodes, {
debounce: 1000, // Higher debounce for network filesystems
saneOptions: {
poll: true, // Use polling in containerized environments
interval: 1000 // Poll every second
}
});File watching systems need robust error handling for various failure scenarios.
const watcher = new Watcher(builder, watchedNodes);
watcher.start().catch(error => {
console.error('Failed to start watcher:', error);
// Fallback to dummy watcher for single build
const fallback = new DummyWatcher(builder);
return fallback.start();
});
// Handle build errors during watching
watcher.on('error', (error) => {
console.error('Watcher error:', error);
});
watcher.on('buildFailure', (error) => {
console.error('Build failed during watching:', error);
// Continue watching despite build failure
});