Skip to content

Commit 8d14e94

Browse files
committed
Merge branch 'main' into markjm/split
2 parents a3b2b82 + e71b62b commit 8d14e94

7 files changed

Lines changed: 166 additions & 85 deletions

File tree

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ name: Test
33
on:
44
push:
55
branches:
6-
- master
6+
- main
77
pull_request:
88
branches:
9-
- master
9+
- main
1010

1111
jobs:
1212
lint:
@@ -36,7 +36,7 @@ jobs:
3636
fail-fast: false
3737
matrix:
3838
os: [ubuntu-latest, windows-latest, macos-latest]
39-
node-version: [10.x, 12.x, 14.x, 16.x]
39+
node-version: [10.x, 12.x, 14.x, 16.x, 17.x]
4040
polling: ["false", "200"]
4141
exclude:
4242
- os: macos-latest

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ var wp = new Watchpack({
4949
// ignored: "string" - a glob pattern for files or folders that should not be watched
5050
// ignored: ["string", "string"] - multiple glob patterns that should be ignored
5151
// ignored: /regexp/ - a regular expression for files or folders that should not be watched
52+
// ignored: (entry) => boolean - an arbitrary function which must return truthy to ignore an entry
53+
// For all cases expect the arbitrary function the path will have path separator normalized to '/'.
5254
// All subdirectories are ignored too
5355
});
5456

@@ -133,13 +135,13 @@ var fileTimes = wp.getTimes();
133135
// key: absolute path, value: timestamp as number
134136
```
135137

136-
[build-status]: https://travis-ci.org/webpack/watchpack.svg?branch=master
138+
[build-status]: https://travis-ci.org/webpack/watchpack.svg?branch=main
137139
[build-status-url]: https://travis-ci.org/webpack/watchpack
138-
[build-status-veyor]: https://ci.appveyor.com/api/projects/status/e5u2qvmugtv0r647/branch/master?svg=true
139-
[build-status-veyor-url]: https://ci.appveyor.com/project/sokra/watchpack/branch/master
140+
[build-status-veyor]: https://ci.appveyor.com/api/projects/status/e5u2qvmugtv0r647/branch/main?svg=true
141+
[build-status-veyor-url]: https://ci.appveyor.com/project/sokra/watchpack/branch/main
140142
[coveralls-url]: https://coveralls.io/r/webpack/watchpack/
141143
[coveralls-image]: https://img.shields.io/coveralls/webpack/watchpack.svg
142-
[codecov]: https://codecov.io/gh/webpack/watchpack/branch/master/graph/badge.svg
144+
[codecov]: https://codecov.io/gh/webpack/watchpack/branch/main/graph/badge.svg
143145
[codecov-url]: https://codecov.io/gh/webpack/watchpack
144146
[downloads]: https://img.shields.io/npm/dm/watchpack.svg
145147
[downloads-url]: https://www.npmjs.com/package/watchpack

lib/DirectoryWatcher.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class DirectoryWatcher extends EventEmitter {
7070
this.directories = new Map();
7171
this.lastWatchEvent = 0;
7272
this.initialScan = true;
73-
this.ignored = options.ignored;
73+
this.ignored = options.ignored || (() => false);
7474
this.nestedWatching = false;
7575
this.polledWatching =
7676
typeof options.poll === "number"
@@ -95,12 +95,6 @@ class DirectoryWatcher extends EventEmitter {
9595
this.doScan(true);
9696
}
9797

98-
checkIgnore(path) {
99-
if (!this.ignored) return false;
100-
path = path.replace(/\\/g, "/");
101-
return this.ignored.test(path);
102-
}
103-
10498
createWatcher() {
10599
try {
106100
if (this.polledWatching) {
@@ -175,7 +169,7 @@ class DirectoryWatcher extends EventEmitter {
175169
setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) {
176170
const now = Date.now();
177171

178-
if (this.checkIgnore(filePath)) return;
172+
if (this.ignored(filePath)) return;
179173

180174
const old = this.files.get(filePath);
181175

@@ -233,7 +227,7 @@ class DirectoryWatcher extends EventEmitter {
233227
}
234228

235229
setDirectory(directoryPath, birthtime, initial, type) {
236-
if (this.checkIgnore(directoryPath)) return;
230+
if (this.ignored(directoryPath)) return;
237231
if (directoryPath === this.path) {
238232
if (!initial) {
239233
this.forEachWatcher(this.path, w =>
@@ -391,7 +385,7 @@ class DirectoryWatcher extends EventEmitter {
391385
}
392386

393387
const filePath = path.join(this.path, filename);
394-
if (this.checkIgnore(filePath)) return;
388+
if (this.ignored(filePath)) return;
395389

396390
if (this._activeEvents.get(filename) === undefined) {
397391
this._activeEvents.set(filename, false);
@@ -637,6 +631,7 @@ class DirectoryWatcher extends EventEmitter {
637631
if (
638632
err2.code === "ENOENT" ||
639633
err2.code === "EPERM" ||
634+
err2.code === "EACCES" ||
640635
err2.code === "EBUSY"
641636
) {
642637
this.setMissing(itemPath, initial, "scan (" + err2.code + ")");

lib/watchpack.js

Lines changed: 117 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const EMPTY_ARRAY = [];
1414
const EMPTY_OPTIONS = {};
1515

1616
function addWatchersToSet(watchers, set) {
17-
for (const w of watchers) {
17+
for (const ww of watchers) {
18+
const w = ww.watcher;
1819
if (!set.has(w.directoryWatcher)) {
1920
set.add(w.directoryWatcher);
2021
}
@@ -28,24 +29,28 @@ const stringToRegexp = ignored => {
2829
return matchingStart;
2930
};
3031

31-
const ignoredToRegexp = ignored => {
32+
const ignoredToFunction = ignored => {
3233
if (Array.isArray(ignored)) {
33-
return new RegExp(ignored.map(i => stringToRegexp(i)).join("|"));
34+
const regexp = new RegExp(ignored.map(i => stringToRegexp(i)).join("|"));
35+
return x => regexp.test(x.replace(/\\/g, "/"));
3436
} else if (typeof ignored === "string") {
35-
return new RegExp(stringToRegexp(ignored));
37+
const regexp = new RegExp(stringToRegexp(ignored));
38+
return x => regexp.test(x.replace(/\\/g, "/"));
3639
} else if (ignored instanceof RegExp) {
40+
return x => ignored.test(x.replace(/\\/g, "/"));
41+
} else if (ignored instanceof Function) {
3742
return ignored;
3843
} else if (ignored) {
3944
throw new Error(`Invalid option for 'ignored': ${ignored}`);
4045
} else {
41-
return undefined;
46+
return () => false;
4247
}
4348
};
4449

4550
const normalizeOptions = options => {
4651
return {
4752
followSymlinks: !!options.followSymlinks,
48-
ignored: ignoredToRegexp(options.ignored),
53+
ignored: ignoredToFunction(options.ignored),
4954
poll: options.poll
5055
};
5156
};
@@ -59,6 +64,85 @@ const cachedNormalizeOptions = options => {
5964
return normalized;
6065
};
6166

67+
class WatchpackFileWatcher {
68+
constructor(watchpack, watcher, files) {
69+
if (!watcher) throw new Error();
70+
this.files = Array.isArray(files) ? files : [files];
71+
this.watcher = watcher;
72+
watcher.on("initial-missing", type => {
73+
for (const file of this.files) {
74+
if (!watchpack._missing.has(file))
75+
watchpack._onRemove(file, file, type);
76+
}
77+
});
78+
watcher.on("change", (mtime, type) => {
79+
for (const file of this.files) {
80+
watchpack._onChange(file, mtime, file, type);
81+
}
82+
});
83+
watcher.on("remove", type => {
84+
for (const file of this.files) {
85+
watchpack._onRemove(file, file, type);
86+
}
87+
});
88+
}
89+
90+
update(files) {
91+
if (!Array.isArray(files)) {
92+
if (this.files.length !== 1) {
93+
this.files = [files];
94+
} else if (this.files[0] !== files) {
95+
this.files[0] = files;
96+
}
97+
} else {
98+
this.files = files;
99+
}
100+
}
101+
102+
close() {
103+
this.watcher.close();
104+
}
105+
}
106+
107+
class WatchpackDirectoryWatcher {
108+
constructor(watchpack, watcher, directories) {
109+
if (!watcher) throw new Error();
110+
this.directories = Array.isArray(directories) ? directories : [directories];
111+
this.watcher = watcher;
112+
watcher.on("initial-missing", type => {
113+
for (const item of this.directories) {
114+
watchpack._onRemove(item, item, type);
115+
}
116+
});
117+
watcher.on("change", (file, mtime, type) => {
118+
for (const item of this.directories) {
119+
watchpack._onChange(item, mtime, file, type);
120+
}
121+
});
122+
watcher.on("remove", type => {
123+
for (const item of this.directories) {
124+
watchpack._onRemove(item, item, type);
125+
}
126+
});
127+
}
128+
129+
update(directories) {
130+
if (!Array.isArray(directories)) {
131+
if (this.directories.length !== 1) {
132+
this.directories = [directories];
133+
} else if (this.directories[0] !== directories) {
134+
this.directories[0] = directories;
135+
}
136+
} else {
137+
this.directories = directories;
138+
}
139+
}
140+
141+
close() {
142+
this.watcher.close();
143+
}
144+
}
145+
62146
class Watchpack extends EventEmitter {
63147
constructor(options) {
64148
super();
@@ -72,6 +156,7 @@ class Watchpack extends EventEmitter {
72156
this.watcherManager = getWatcherManager(this.watcherOptions);
73157
this.fileWatchers = new Map();
74158
this.directoryWatchers = new Map();
159+
this._missing = new Set();
75160
this.startTime = undefined;
76161
this.paused = false;
77162
this.aggregatedChanges = new Set();
@@ -96,18 +181,18 @@ class Watchpack extends EventEmitter {
96181
startTime = arg3;
97182
}
98183
this.paused = false;
99-
const oldFileWatchers = this.fileWatchers;
100-
const oldDirectoryWatchers = this.directoryWatchers;
184+
const fileWatchers = this.fileWatchers;
185+
const directoryWatchers = this.directoryWatchers;
101186
const ignored = this.watcherOptions.ignored;
102-
const filter = ignored
103-
? path => !ignored.test(path.replace(/\\/g, "/"))
104-
: () => true;
187+
const filter = path => !ignored(path);
105188
const addToMap = (map, key, item) => {
106189
const list = map.get(key);
107190
if (list === undefined) {
108-
map.set(key, [item]);
109-
} else {
191+
map.set(key, item);
192+
} else if (Array.isArray(list)) {
110193
list.push(item);
194+
} else {
195+
map.set(key, [list, item]);
111196
}
112197
};
113198
const fileWatchersNeeded = new Map();
@@ -167,82 +252,47 @@ class Watchpack extends EventEmitter {
167252
}
168253
}
169254
}
170-
const newFileWatchers = new Map();
171-
const newDirectoryWatchers = new Map();
172-
const setupFileWatcher = (watcher, key, files) => {
173-
watcher.on("initial-missing", type => {
174-
for (const file of files) {
175-
if (!missingFiles.has(file)) this._onRemove(file, file, type);
176-
}
177-
});
178-
watcher.on("change", (mtime, type) => {
179-
for (const file of files) {
180-
this._onChange(file, mtime, file, type);
181-
}
182-
});
183-
watcher.on("remove", type => {
184-
for (const file of files) {
185-
this._onRemove(file, file, type);
186-
}
187-
});
188-
newFileWatchers.set(key, watcher);
189-
};
190-
const setupDirectoryWatcher = (watcher, key, directories) => {
191-
watcher.on("initial-missing", type => {
192-
for (const item of directories) {
193-
this._onRemove(item, item, type);
194-
}
195-
});
196-
watcher.on("change", (file, mtime, type) => {
197-
for (const item of directories) {
198-
this._onChange(item, mtime, file, type);
199-
}
200-
});
201-
watcher.on("remove", type => {
202-
for (const item of directories) {
203-
this._onRemove(item, item, type);
204-
}
205-
});
206-
newDirectoryWatchers.set(key, watcher);
207-
};
208255
// Close unneeded old watchers
209-
const fileWatchersToClose = [];
210-
const directoryWatchersToClose = [];
211-
for (const [key, w] of oldFileWatchers) {
212-
if (!fileWatchersNeeded.has(key)) {
256+
// and update existing watchers
257+
for (const [key, w] of fileWatchers) {
258+
const needed = fileWatchersNeeded.get(key);
259+
if (needed === undefined) {
213260
w.close();
261+
fileWatchers.delete(key);
214262
} else {
215-
fileWatchersToClose.push(w);
263+
w.update(needed);
264+
fileWatchersNeeded.delete(key);
216265
}
217266
}
218-
for (const [key, w] of oldDirectoryWatchers) {
219-
if (!directoryWatchersNeeded.has(key)) {
267+
for (const [key, w] of directoryWatchers) {
268+
const needed = directoryWatchersNeeded.get(key);
269+
if (needed === undefined) {
220270
w.close();
271+
directoryWatchers.delete(key);
221272
} else {
222-
directoryWatchersToClose.push(w);
273+
w.update(needed);
274+
directoryWatchersNeeded.delete(key);
223275
}
224276
}
225277
// Create new watchers and install handlers on these watchers
226278
watchEventSource.batch(() => {
227279
for (const [key, files] of fileWatchersNeeded) {
228280
const watcher = this.watcherManager.watchFile(key, startTime);
229281
if (watcher) {
230-
setupFileWatcher(watcher, key, files);
282+
fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files));
231283
}
232284
}
233285
for (const [key, directories] of directoryWatchersNeeded) {
234286
const watcher = this.watcherManager.watchDirectory(key, startTime);
235287
if (watcher) {
236-
setupDirectoryWatcher(watcher, key, directories);
288+
directoryWatchers.set(
289+
key,
290+
new WatchpackDirectoryWatcher(this, watcher, directories)
291+
);
237292
}
238293
}
239294
});
240-
// Close old watchers
241-
for (const w of fileWatchersToClose) w.close();
242-
for (const w of directoryWatchersToClose) w.close();
243-
// Store watchers
244-
this.fileWatchers = newFileWatchers;
245-
this.directoryWatchers = newDirectoryWatchers;
295+
this._missing = missingFiles;
246296
this.startTime = startTime;
247297
}
248298

test/ManyWatchers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ describe("ManyWatchers", function() {
6666
}
6767
w.watch({ files });
6868
console.timeEnd("creating/closing watchers");
69+
console.time("calling watch with the same files");
70+
for (let i = 0; i < 2000; i++) {
71+
w.watch({ files });
72+
}
73+
console.timeEnd("calling watch with the same files");
74+
6975
testHelper.tick(10000, () => {
7076
console.time("detecting change event");
7177
testHelper.file("4096/900/file");

0 commit comments

Comments
 (0)