source: trip-planner-front/node_modules/watchpack/lib/watchpack.js@ b738035

Last change on this file since b738035 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 9.6 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const getWatcherManager = require("./getWatcherManager");
8const LinkResolver = require("./LinkResolver");
9const EventEmitter = require("events").EventEmitter;
10const globToRegExp = require("glob-to-regexp");
11const watchEventSource = require("./watchEventSource");
12
13let EXISTANCE_ONLY_TIME_ENTRY; // lazy required
14
15const EMPTY_ARRAY = [];
16const EMPTY_OPTIONS = {};
17
18function addWatchersToSet(watchers, set) {
19 for (const w of watchers) {
20 if (w !== true && !set.has(w.directoryWatcher)) {
21 set.add(w.directoryWatcher);
22 addWatchersToSet(w.directoryWatcher.directories.values(), set);
23 }
24 }
25}
26
27const stringToRegexp = ignored => {
28 const source = globToRegExp(ignored, { globstar: true, extended: true })
29 .source;
30 const matchingStart = source.slice(0, source.length - 1) + "(?:$|\\/)";
31 return matchingStart;
32};
33
34const ignoredToRegexp = ignored => {
35 if (Array.isArray(ignored)) {
36 return new RegExp(ignored.map(i => stringToRegexp(i)).join("|"));
37 } else if (typeof ignored === "string") {
38 return new RegExp(stringToRegexp(ignored));
39 } else if (ignored instanceof RegExp) {
40 return ignored;
41 } else if (ignored) {
42 throw new Error(`Invalid option for 'ignored': ${ignored}`);
43 } else {
44 return undefined;
45 }
46};
47
48const normalizeOptions = options => {
49 return {
50 followSymlinks: !!options.followSymlinks,
51 ignored: ignoredToRegexp(options.ignored),
52 poll: options.poll
53 };
54};
55
56const normalizeCache = new WeakMap();
57const cachedNormalizeOptions = options => {
58 const cacheEntry = normalizeCache.get(options);
59 if (cacheEntry !== undefined) return cacheEntry;
60 const normalized = normalizeOptions(options);
61 normalizeCache.set(options, normalized);
62 return normalized;
63};
64
65class Watchpack extends EventEmitter {
66 constructor(options) {
67 super();
68 if (!options) options = EMPTY_OPTIONS;
69 this.options = options;
70 this.aggregateTimeout =
71 typeof options.aggregateTimeout === "number"
72 ? options.aggregateTimeout
73 : 200;
74 this.watcherOptions = cachedNormalizeOptions(options);
75 this.watcherManager = getWatcherManager(this.watcherOptions);
76 this.fileWatchers = new Map();
77 this.directoryWatchers = new Map();
78 this.startTime = undefined;
79 this.paused = false;
80 this.aggregatedChanges = new Set();
81 this.aggregatedRemovals = new Set();
82 this.aggregateTimer = undefined;
83 this._onTimeout = this._onTimeout.bind(this);
84 }
85
86 watch(arg1, arg2, arg3) {
87 let files, directories, missing, startTime;
88 if (!arg2) {
89 ({
90 files = EMPTY_ARRAY,
91 directories = EMPTY_ARRAY,
92 missing = EMPTY_ARRAY,
93 startTime
94 } = arg1);
95 } else {
96 files = arg1;
97 directories = arg2;
98 missing = EMPTY_ARRAY;
99 startTime = arg3;
100 }
101 this.paused = false;
102 const oldFileWatchers = this.fileWatchers;
103 const oldDirectoryWatchers = this.directoryWatchers;
104 const ignored = this.watcherOptions.ignored;
105 const filter = ignored
106 ? path => !ignored.test(path.replace(/\\/g, "/"))
107 : () => true;
108 const addToMap = (map, key, item) => {
109 const list = map.get(key);
110 if (list === undefined) {
111 map.set(key, [item]);
112 } else {
113 list.push(item);
114 }
115 };
116 const fileWatchersNeeded = new Map();
117 const directoryWatchersNeeded = new Map();
118 const missingFiles = new Set();
119 if (this.watcherOptions.followSymlinks) {
120 const resolver = new LinkResolver();
121 for (const file of files) {
122 if (filter(file)) {
123 for (const innerFile of resolver.resolve(file)) {
124 if (file === innerFile || filter(innerFile)) {
125 addToMap(fileWatchersNeeded, innerFile, file);
126 }
127 }
128 }
129 }
130 for (const file of missing) {
131 if (filter(file)) {
132 for (const innerFile of resolver.resolve(file)) {
133 if (file === innerFile || filter(innerFile)) {
134 missingFiles.add(file);
135 addToMap(fileWatchersNeeded, innerFile, file);
136 }
137 }
138 }
139 }
140 for (const dir of directories) {
141 if (filter(dir)) {
142 let first = true;
143 for (const innerItem of resolver.resolve(dir)) {
144 if (filter(innerItem)) {
145 addToMap(
146 first ? directoryWatchersNeeded : fileWatchersNeeded,
147 innerItem,
148 dir
149 );
150 }
151 first = false;
152 }
153 }
154 }
155 } else {
156 for (const file of files) {
157 if (filter(file)) {
158 addToMap(fileWatchersNeeded, file, file);
159 }
160 }
161 for (const file of missing) {
162 if (filter(file)) {
163 missingFiles.add(file);
164 addToMap(fileWatchersNeeded, file, file);
165 }
166 }
167 for (const dir of directories) {
168 if (filter(dir)) {
169 addToMap(directoryWatchersNeeded, dir, dir);
170 }
171 }
172 }
173 const newFileWatchers = new Map();
174 const newDirectoryWatchers = new Map();
175 const setupFileWatcher = (watcher, key, files) => {
176 watcher.on("initial-missing", type => {
177 for (const file of files) {
178 if (!missingFiles.has(file)) this._onRemove(file, file, type);
179 }
180 });
181 watcher.on("change", (mtime, type) => {
182 for (const file of files) {
183 this._onChange(file, mtime, file, type);
184 }
185 });
186 watcher.on("remove", type => {
187 for (const file of files) {
188 this._onRemove(file, file, type);
189 }
190 });
191 newFileWatchers.set(key, watcher);
192 };
193 const setupDirectoryWatcher = (watcher, key, directories) => {
194 watcher.on("initial-missing", type => {
195 for (const item of directories) {
196 this._onRemove(item, item, type);
197 }
198 });
199 watcher.on("change", (file, mtime, type) => {
200 for (const item of directories) {
201 this._onChange(item, mtime, file, type);
202 }
203 });
204 watcher.on("remove", type => {
205 for (const item of directories) {
206 this._onRemove(item, item, type);
207 }
208 });
209 newDirectoryWatchers.set(key, watcher);
210 };
211 // Close unneeded old watchers
212 const fileWatchersToClose = [];
213 const directoryWatchersToClose = [];
214 for (const [key, w] of oldFileWatchers) {
215 if (!fileWatchersNeeded.has(key)) {
216 w.close();
217 } else {
218 fileWatchersToClose.push(w);
219 }
220 }
221 for (const [key, w] of oldDirectoryWatchers) {
222 if (!directoryWatchersNeeded.has(key)) {
223 w.close();
224 } else {
225 directoryWatchersToClose.push(w);
226 }
227 }
228 // Create new watchers and install handlers on these watchers
229 watchEventSource.batch(() => {
230 for (const [key, files] of fileWatchersNeeded) {
231 const watcher = this.watcherManager.watchFile(key, startTime);
232 if (watcher) {
233 setupFileWatcher(watcher, key, files);
234 }
235 }
236 for (const [key, directories] of directoryWatchersNeeded) {
237 const watcher = this.watcherManager.watchDirectory(key, startTime);
238 if (watcher) {
239 setupDirectoryWatcher(watcher, key, directories);
240 }
241 }
242 });
243 // Close old watchers
244 for (const w of fileWatchersToClose) w.close();
245 for (const w of directoryWatchersToClose) w.close();
246 // Store watchers
247 this.fileWatchers = newFileWatchers;
248 this.directoryWatchers = newDirectoryWatchers;
249 this.startTime = startTime;
250 }
251
252 close() {
253 this.paused = true;
254 if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
255 for (const w of this.fileWatchers.values()) w.close();
256 for (const w of this.directoryWatchers.values()) w.close();
257 this.fileWatchers.clear();
258 this.directoryWatchers.clear();
259 }
260
261 pause() {
262 this.paused = true;
263 if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
264 }
265
266 getTimes() {
267 const directoryWatchers = new Set();
268 addWatchersToSet(this.fileWatchers.values(), directoryWatchers);
269 addWatchersToSet(this.directoryWatchers.values(), directoryWatchers);
270 const obj = Object.create(null);
271 for (const w of directoryWatchers) {
272 const times = w.getTimes();
273 for (const file of Object.keys(times)) obj[file] = times[file];
274 }
275 return obj;
276 }
277
278 getTimeInfoEntries() {
279 if (EXISTANCE_ONLY_TIME_ENTRY === undefined) {
280 EXISTANCE_ONLY_TIME_ENTRY = require("./DirectoryWatcher")
281 .EXISTANCE_ONLY_TIME_ENTRY;
282 }
283 const directoryWatchers = new Set();
284 addWatchersToSet(this.fileWatchers.values(), directoryWatchers);
285 addWatchersToSet(this.directoryWatchers.values(), directoryWatchers);
286 const map = new Map();
287 for (const w of directoryWatchers) {
288 const times = w.getTimeInfoEntries();
289 for (const [path, entry] of times) {
290 if (map.has(path)) {
291 if (entry === EXISTANCE_ONLY_TIME_ENTRY) continue;
292 const value = map.get(path);
293 if (value === entry) continue;
294 if (value !== EXISTANCE_ONLY_TIME_ENTRY) {
295 map.set(path, Object.assign({}, value, entry));
296 continue;
297 }
298 }
299 map.set(path, entry);
300 }
301 }
302 return map;
303 }
304
305 getAggregated() {
306 if (this.aggregateTimer) {
307 clearTimeout(this.aggregateTimer);
308 this.aggregateTimer = undefined;
309 }
310 const changes = this.aggregatedChanges;
311 const removals = this.aggregatedRemovals;
312 this.aggregatedChanges = new Set();
313 this.aggregatedRemovals = new Set();
314 return { changes, removals };
315 }
316
317 _onChange(item, mtime, file, type) {
318 file = file || item;
319 if (!this.paused) {
320 this.emit("change", file, mtime, type);
321 if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
322 this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout);
323 }
324 this.aggregatedRemovals.delete(item);
325 this.aggregatedChanges.add(item);
326 }
327
328 _onRemove(item, file, type) {
329 file = file || item;
330 if (!this.paused) {
331 this.emit("remove", file, type);
332 if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
333 this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout);
334 }
335 this.aggregatedChanges.delete(item);
336 this.aggregatedRemovals.add(item);
337 }
338
339 _onTimeout() {
340 this.aggregateTimer = undefined;
341 const changes = this.aggregatedChanges;
342 const removals = this.aggregatedRemovals;
343 this.aggregatedChanges = new Set();
344 this.aggregatedRemovals = new Set();
345 this.emit("aggregated", changes, removals);
346 }
347}
348
349module.exports = Watchpack;
Note: See TracBrowser for help on using the repository browser.