diff --git a/lib/internal/watch_mode/files_watcher.js b/lib/internal/watch_mode/files_watcher.js index e3f37557a627dc..14692bd64ef1fd 100644 --- a/lib/internal/watch_mode/files_watcher.js +++ b/lib/internal/watch_mode/files_watcher.js @@ -17,7 +17,7 @@ const { addAbortListener } = require('internal/events/abort_listener'); const { watch } = require('fs'); const { fileURLToPath } = require('internal/url'); const { resolve, dirname } = require('path'); -const { setTimeout } = require('timers'); +const { setTimeout, clearTimeout } = require('timers'); const supportsRecursiveWatching = process.platform === 'win32' || process.platform === 'darwin'; @@ -25,9 +25,10 @@ const supportsRecursiveWatching = process.platform === 'win32' || class FilesWatcher extends EventEmitter { #watchers = new SafeMap(); #filteredFiles = new SafeSet(); - #debouncing = new SafeSet(); #depencencyOwners = new SafeMap(); #ownerDependencies = new SafeMap(); + #debounceOwners = new SafeSet(); + #debounceTimer; #debounce; #mode; #signal; @@ -75,17 +76,27 @@ class FilesWatcher extends EventEmitter { } #onChange(trigger) { - if (this.#debouncing.has(trigger)) { - return; - } if (this.#mode === 'filter' && !this.#filteredFiles.has(trigger)) { return; } - this.#debouncing.add(trigger); + const owners = this.#depencencyOwners.get(trigger); - setTimeout(() => { - this.#debouncing.delete(trigger); + if (owners) { + for (const owner of owners) { + this.#debounceOwners.add(owner); + } + } + + clearTimeout(this.#debounceTimer); + this.#debounceTimer = setTimeout(() => { + this.#debounceTimer = null; + // In order to allow async processing of the 'changed' event, let's + // make sure the Set emitted is a new instance. We could emit using a copy + // of #debounceOwners, then clear #debounceOwners, but that would be + // slower than just replacing #debounceOwners with an empty Set. + const owners = this.#debounceOwners; this.emit('changed', { owners }); + this.#debounceOwners = new SafeSet(); }, this.#debounce).unref(); }