Skip to content

Commit 0ba4d8e

Browse files
authored
perf(@angular/build): reduce watcher debounce latency for faster incremental rebuilds
Previously, the esbuild file watcher utilized a static 250 ms trailing debounce timer (scheduleFlush) after each filesystem change event.
1 parent 062ac27 commit 0ba4d8e

1 file changed

Lines changed: 27 additions & 2 deletions

File tree

  • packages/angular/build/src/tools/esbuild

packages/angular/build/src/tools/esbuild/watcher.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ class WatcherQueue {
141141
private currentChangedFiles: ChangedFiles | undefined;
142142
private isClosed = false;
143143
private timeoutId: NodeJS.Timeout | undefined;
144+
private firstChangeTime: number | undefined;
145+
146+
constructor(
147+
private readonly debounceMs = 100,
148+
private readonly maxWaitMs = 500,
149+
) {}
144150

145151
addChange(type: 'added' | 'modified' | 'removed', file: string): void {
146152
if (this.isClosed) {
@@ -167,16 +173,25 @@ class WatcherQueue {
167173
}
168174

169175
private scheduleFlush(): void {
176+
const now = Date.now();
177+
const firstChangeTime = (this.firstChangeTime ??= now);
178+
170179
if (this.timeoutId) {
171180
clearTimeout(this.timeoutId);
172181
}
182+
183+
const elapsed = now - firstChangeTime;
184+
const remainingMaxWait = Math.max(0, this.maxWaitMs - elapsed);
185+
const delay = Math.min(this.debounceMs, remainingMaxWait);
186+
173187
this.timeoutId = setTimeout(() => {
174188
this.timeoutId = undefined;
175189
this.flush();
176-
}, 250);
190+
}, delay);
177191
}
178192

179193
private flush(): void {
194+
this.firstChangeTime = undefined;
180195
if (
181196
this.currentChangedFiles &&
182197
this.currentChangedFiles.all.length > 0 &&
@@ -224,6 +239,7 @@ class WatcherQueue {
224239
clearTimeout(this.timeoutId);
225240
this.timeoutId = undefined;
226241
}
242+
this.firstChangeTime = undefined;
227243

228244
this.isClosed = true;
229245
this.currentChangedFiles = undefined;
@@ -511,7 +527,16 @@ async function createChokidarWatcher(
511527
): Promise<BuildWatcher> {
512528
const chokidar = chokidarModule ?? (await import('chokidar'));
513529
const watchedFiles = new Set<string>();
514-
const queue = new WatcherQueue();
530+
531+
let queue: WatcherQueue;
532+
if (options?.polling) {
533+
const pollingInterval = options.interval ?? 100;
534+
const debounceMs = Math.min(250, Math.max(100, Math.ceil(pollingInterval * 1.5)));
535+
const maxWaitMs = Math.max(500, debounceMs * 3);
536+
queue = new WatcherQueue(debounceMs, maxWaitMs);
537+
} else {
538+
queue = new WatcherQueue();
539+
}
515540

516541
const rootDir = options?.cwd ?? process.cwd();
517542
const isCaseSensitive = isFileSystemCaseSensitive(rootDir);

0 commit comments

Comments
 (0)