Skip to content

Commit

Permalink
watch: allow listening for grouped changes
Browse files Browse the repository at this point in the history
PR-URL: #52722
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
matthieusieben committed May 12, 2024
1 parent cd155d6 commit c75675c
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 9 deletions.
22 changes: 13 additions & 9 deletions lib/internal/watch_mode/files_watcher.js
Expand Up @@ -19,17 +19,18 @@ 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';

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;
Expand Down Expand Up @@ -80,17 +81,20 @@ 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);
this.emit('changed', { owners });
if (owners) {
for (const owner of owners) {
this.#debounceOwners.add(owner);
}
}
clearTimeout(this.#debounceTimer);
this.#debounceTimer = setTimeout(() => {
this.#debounceTimer = null;
this.emit('changed', { owners: this.#debounceOwners });
this.#debounceOwners.clear();
}, this.#debounce).unref();
}

Expand Down
23 changes: 23 additions & 0 deletions test/parallel/test-watch-mode-files_watcher.mjs
Expand Up @@ -70,6 +70,29 @@ describe('watch mode file watcher', () => {
assert.ok(changesCount < 5);
});

it('should debounce changes on multiple files', async () => {
const files = [];
for (let i = 0; i < 10; i++) {
const file = tmpdir.resolve(`file-debounced-${i}`);
writeFileSync(file, 'written');
watcher.filterFile(file);
files.push(file);
}

files.forEach((file) => writeFileSync(file, '1'));
files.forEach((file) => writeFileSync(file, '2'));
files.forEach((file) => writeFileSync(file, '3'));
files.forEach((file) => writeFileSync(file, '4'));

await setTimeout(200); // debounce * 2
files.forEach((file) => writeFileSync(file, '5'));
const changed = once(watcher, 'changed');
files.forEach((file) => writeFileSync(file, 'after'));
await changed;
// Unfortunately testing that changesCount === 2 is flaky
assert.ok(changesCount < 5);
});

it('should ignore files in watched directory if they are not filtered',
{ skip: !supportsRecursiveWatching }, async () => {
watcher.on('changed', common.mustNotCall());
Expand Down

0 comments on commit c75675c

Please sign in to comment.