1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const Stats = require("./Stats");
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | class Watching {
|
23 | |
24 |
|
25 |
|
26 |
|
27 |
|
28 | constructor(compiler, watchOptions, handler) {
|
29 | this.startTime = null;
|
30 | this.invalid = false;
|
31 | this.handler = handler;
|
32 |
|
33 | this.callbacks = [];
|
34 |
|
35 | this._closeCallbacks = undefined;
|
36 | this.closed = false;
|
37 | this.suspended = false;
|
38 | this.blocked = false;
|
39 | this._isBlocked = () => false;
|
40 | this._onChange = () => {};
|
41 | this._onInvalid = () => {};
|
42 | if (typeof watchOptions === "number") {
|
43 | this.watchOptions = {
|
44 | aggregateTimeout: watchOptions
|
45 | };
|
46 | } else if (watchOptions && typeof watchOptions === "object") {
|
47 | this.watchOptions = { ...watchOptions };
|
48 | } else {
|
49 | this.watchOptions = {};
|
50 | }
|
51 | if (typeof this.watchOptions.aggregateTimeout !== "number") {
|
52 | this.watchOptions.aggregateTimeout = 20;
|
53 | }
|
54 | this.compiler = compiler;
|
55 | this.running = false;
|
56 | this._initial = true;
|
57 | this._invalidReported = true;
|
58 | this._needRecords = true;
|
59 | this.watcher = undefined;
|
60 | this.pausedWatcher = undefined;
|
61 |
|
62 | this._collectedChangedFiles = undefined;
|
63 |
|
64 | this._collectedRemovedFiles = undefined;
|
65 | this._done = this._done.bind(this);
|
66 | process.nextTick(() => {
|
67 | if (this._initial) this._invalidate();
|
68 | });
|
69 | }
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 | _mergeWithCollected(changedFiles, removedFiles) {
|
76 | if (!changedFiles) return;
|
77 | if (!this._collectedChangedFiles) {
|
78 | this._collectedChangedFiles = new Set(changedFiles);
|
79 | this._collectedRemovedFiles = new Set(removedFiles);
|
80 | } else {
|
81 | for (const file of changedFiles) {
|
82 | this._collectedChangedFiles.add(file);
|
83 | this._collectedRemovedFiles.delete(file);
|
84 | }
|
85 | for (const file of removedFiles) {
|
86 | this._collectedChangedFiles.delete(file);
|
87 | this._collectedRemovedFiles.add(file);
|
88 | }
|
89 | }
|
90 | }
|
91 |
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | _go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
|
100 | this._initial = false;
|
101 | if (this.startTime === null) this.startTime = Date.now();
|
102 | this.running = true;
|
103 | if (this.watcher) {
|
104 | this.pausedWatcher = this.watcher;
|
105 | this.lastWatcherStartTime = Date.now();
|
106 | this.watcher.pause();
|
107 | this.watcher = null;
|
108 | } else if (!this.lastWatcherStartTime) {
|
109 | this.lastWatcherStartTime = Date.now();
|
110 | }
|
111 | this.compiler.fsStartTime = Date.now();
|
112 | if (
|
113 | changedFiles &&
|
114 | removedFiles &&
|
115 | fileTimeInfoEntries &&
|
116 | contextTimeInfoEntries
|
117 | ) {
|
118 | this._mergeWithCollected(changedFiles, removedFiles);
|
119 | this.compiler.fileTimestamps = fileTimeInfoEntries;
|
120 | this.compiler.contextTimestamps = contextTimeInfoEntries;
|
121 | } else if (this.pausedWatcher) {
|
122 | if (this.pausedWatcher.getInfo) {
|
123 | const {
|
124 | changes,
|
125 | removals,
|
126 | fileTimeInfoEntries,
|
127 | contextTimeInfoEntries
|
128 | } = this.pausedWatcher.getInfo();
|
129 | this._mergeWithCollected(changes, removals);
|
130 | this.compiler.fileTimestamps = fileTimeInfoEntries;
|
131 | this.compiler.contextTimestamps = contextTimeInfoEntries;
|
132 | } else {
|
133 | this._mergeWithCollected(
|
134 | this.pausedWatcher.getAggregatedChanges &&
|
135 | this.pausedWatcher.getAggregatedChanges(),
|
136 | this.pausedWatcher.getAggregatedRemovals &&
|
137 | this.pausedWatcher.getAggregatedRemovals()
|
138 | );
|
139 | this.compiler.fileTimestamps =
|
140 | this.pausedWatcher.getFileTimeInfoEntries();
|
141 | this.compiler.contextTimestamps =
|
142 | this.pausedWatcher.getContextTimeInfoEntries();
|
143 | }
|
144 | }
|
145 | this.compiler.modifiedFiles = this._collectedChangedFiles;
|
146 | this._collectedChangedFiles = undefined;
|
147 | this.compiler.removedFiles = this._collectedRemovedFiles;
|
148 | this._collectedRemovedFiles = undefined;
|
149 |
|
150 | const run = () => {
|
151 | if (this.compiler.idle) {
|
152 | return this.compiler.cache.endIdle(err => {
|
153 | if (err) return this._done(err);
|
154 | this.compiler.idle = false;
|
155 | run();
|
156 | });
|
157 | }
|
158 | if (this._needRecords) {
|
159 | return this.compiler.readRecords(err => {
|
160 | if (err) return this._done(err);
|
161 |
|
162 | this._needRecords = false;
|
163 | run();
|
164 | });
|
165 | }
|
166 | this.invalid = false;
|
167 | this._invalidReported = false;
|
168 | this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
|
169 | if (err) return this._done(err);
|
170 | const onCompiled = (err, compilation) => {
|
171 | if (err) return this._done(err, compilation);
|
172 | if (this.invalid) return this._done(null, compilation);
|
173 |
|
174 | if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
|
175 | return this._done(null, compilation);
|
176 | }
|
177 |
|
178 | process.nextTick(() => {
|
179 | const logger = compilation.getLogger("webpack.Compiler");
|
180 | logger.time("emitAssets");
|
181 | this.compiler.emitAssets(compilation, err => {
|
182 | logger.timeEnd("emitAssets");
|
183 | if (err) return this._done(err, compilation);
|
184 | if (this.invalid) return this._done(null, compilation);
|
185 |
|
186 | logger.time("emitRecords");
|
187 | this.compiler.emitRecords(err => {
|
188 | logger.timeEnd("emitRecords");
|
189 | if (err) return this._done(err, compilation);
|
190 |
|
191 | if (compilation.hooks.needAdditionalPass.call()) {
|
192 | compilation.needAdditionalPass = true;
|
193 |
|
194 | compilation.startTime = this.startTime;
|
195 | compilation.endTime = Date.now();
|
196 | logger.time("done hook");
|
197 | const stats = new Stats(compilation);
|
198 | this.compiler.hooks.done.callAsync(stats, err => {
|
199 | logger.timeEnd("done hook");
|
200 | if (err) return this._done(err, compilation);
|
201 |
|
202 | this.compiler.hooks.additionalPass.callAsync(err => {
|
203 | if (err) return this._done(err, compilation);
|
204 | this.compiler.compile(onCompiled);
|
205 | });
|
206 | });
|
207 | return;
|
208 | }
|
209 | return this._done(null, compilation);
|
210 | });
|
211 | });
|
212 | });
|
213 | };
|
214 | this.compiler.compile(onCompiled);
|
215 | });
|
216 | };
|
217 |
|
218 | run();
|
219 | }
|
220 |
|
221 | |
222 |
|
223 |
|
224 |
|
225 | _getStats(compilation) {
|
226 | const stats = new Stats(compilation);
|
227 | return stats;
|
228 | }
|
229 |
|
230 | |
231 |
|
232 |
|
233 |
|
234 |
|
235 | _done(err, compilation) {
|
236 | this.running = false;
|
237 |
|
238 | const logger = compilation && compilation.getLogger("webpack.Watching");
|
239 |
|
240 | let stats = null;
|
241 |
|
242 | const handleError = (err, cbs) => {
|
243 | this.compiler.hooks.failed.call(err);
|
244 | this.compiler.cache.beginIdle();
|
245 | this.compiler.idle = true;
|
246 | this.handler(err, stats);
|
247 | if (!cbs) {
|
248 | cbs = this.callbacks;
|
249 | this.callbacks = [];
|
250 | }
|
251 | for (const cb of cbs) cb(err);
|
252 | };
|
253 |
|
254 | if (
|
255 | this.invalid &&
|
256 | !this.suspended &&
|
257 | !this.blocked &&
|
258 | !(this._isBlocked() && (this.blocked = true))
|
259 | ) {
|
260 | if (compilation) {
|
261 | logger.time("storeBuildDependencies");
|
262 | this.compiler.cache.storeBuildDependencies(
|
263 | compilation.buildDependencies,
|
264 | err => {
|
265 | logger.timeEnd("storeBuildDependencies");
|
266 | if (err) return handleError(err);
|
267 | this._go();
|
268 | }
|
269 | );
|
270 | } else {
|
271 | this._go();
|
272 | }
|
273 | return;
|
274 | }
|
275 |
|
276 | if (compilation) {
|
277 | compilation.startTime = this.startTime;
|
278 | compilation.endTime = Date.now();
|
279 | stats = new Stats(compilation);
|
280 | }
|
281 | this.startTime = null;
|
282 | if (err) return handleError(err);
|
283 |
|
284 | const cbs = this.callbacks;
|
285 | this.callbacks = [];
|
286 | logger.time("done hook");
|
287 | this.compiler.hooks.done.callAsync(stats, err => {
|
288 | logger.timeEnd("done hook");
|
289 | if (err) return handleError(err, cbs);
|
290 | this.handler(null, stats);
|
291 | logger.time("storeBuildDependencies");
|
292 | this.compiler.cache.storeBuildDependencies(
|
293 | compilation.buildDependencies,
|
294 | err => {
|
295 | logger.timeEnd("storeBuildDependencies");
|
296 | if (err) return handleError(err, cbs);
|
297 | logger.time("beginIdle");
|
298 | this.compiler.cache.beginIdle();
|
299 | this.compiler.idle = true;
|
300 | logger.timeEnd("beginIdle");
|
301 | process.nextTick(() => {
|
302 | if (!this.closed) {
|
303 | this.watch(
|
304 | compilation.fileDependencies,
|
305 | compilation.contextDependencies,
|
306 | compilation.missingDependencies
|
307 | );
|
308 | }
|
309 | });
|
310 | for (const cb of cbs) cb(null);
|
311 | this.compiler.hooks.afterDone.call(stats);
|
312 | }
|
313 | );
|
314 | });
|
315 | }
|
316 |
|
317 | |
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 | watch(files, dirs, missing) {
|
324 | this.pausedWatcher = null;
|
325 | this.watcher = this.compiler.watchFileSystem.watch(
|
326 | files,
|
327 | dirs,
|
328 | missing,
|
329 | this.lastWatcherStartTime,
|
330 | this.watchOptions,
|
331 | (
|
332 | err,
|
333 | fileTimeInfoEntries,
|
334 | contextTimeInfoEntries,
|
335 | changedFiles,
|
336 | removedFiles
|
337 | ) => {
|
338 | if (err) {
|
339 | this.compiler.modifiedFiles = undefined;
|
340 | this.compiler.removedFiles = undefined;
|
341 | this.compiler.fileTimestamps = undefined;
|
342 | this.compiler.contextTimestamps = undefined;
|
343 | this.compiler.fsStartTime = undefined;
|
344 | return this.handler(err);
|
345 | }
|
346 | this._invalidate(
|
347 | fileTimeInfoEntries,
|
348 | contextTimeInfoEntries,
|
349 | changedFiles,
|
350 | removedFiles
|
351 | );
|
352 | this._onChange();
|
353 | },
|
354 | (fileName, changeTime) => {
|
355 | if (!this._invalidReported) {
|
356 | this._invalidReported = true;
|
357 | this.compiler.hooks.invalid.call(fileName, changeTime);
|
358 | }
|
359 | this._onInvalid();
|
360 | }
|
361 | );
|
362 | }
|
363 |
|
364 | |
365 |
|
366 |
|
367 |
|
368 | invalidate(callback) {
|
369 | if (callback) {
|
370 | this.callbacks.push(callback);
|
371 | }
|
372 | if (!this._invalidReported) {
|
373 | this._invalidReported = true;
|
374 | this.compiler.hooks.invalid.call(null, Date.now());
|
375 | }
|
376 | this._onChange();
|
377 | this._invalidate();
|
378 | }
|
379 |
|
380 | _invalidate(
|
381 | fileTimeInfoEntries,
|
382 | contextTimeInfoEntries,
|
383 | changedFiles,
|
384 | removedFiles
|
385 | ) {
|
386 | if (this.suspended || (this._isBlocked() && (this.blocked = true))) {
|
387 | this._mergeWithCollected(changedFiles, removedFiles);
|
388 | return;
|
389 | }
|
390 |
|
391 | if (this.running) {
|
392 | this._mergeWithCollected(changedFiles, removedFiles);
|
393 | this.invalid = true;
|
394 | } else {
|
395 | this._go(
|
396 | fileTimeInfoEntries,
|
397 | contextTimeInfoEntries,
|
398 | changedFiles,
|
399 | removedFiles
|
400 | );
|
401 | }
|
402 | }
|
403 |
|
404 | suspend() {
|
405 | this.suspended = true;
|
406 | }
|
407 |
|
408 | resume() {
|
409 | if (this.suspended) {
|
410 | this.suspended = false;
|
411 | this._invalidate();
|
412 | }
|
413 | }
|
414 |
|
415 | |
416 |
|
417 |
|
418 |
|
419 | close(callback) {
|
420 | if (this._closeCallbacks) {
|
421 | if (callback) {
|
422 | this._closeCallbacks.push(callback);
|
423 | }
|
424 | return;
|
425 | }
|
426 | const finalCallback = (err, compilation) => {
|
427 | this.running = false;
|
428 | this.compiler.running = false;
|
429 | this.compiler.watching = undefined;
|
430 | this.compiler.watchMode = false;
|
431 | this.compiler.modifiedFiles = undefined;
|
432 | this.compiler.removedFiles = undefined;
|
433 | this.compiler.fileTimestamps = undefined;
|
434 | this.compiler.contextTimestamps = undefined;
|
435 | this.compiler.fsStartTime = undefined;
|
436 | const shutdown = err => {
|
437 | this.compiler.hooks.watchClose.call();
|
438 | const closeCallbacks = this._closeCallbacks;
|
439 | this._closeCallbacks = undefined;
|
440 | for (const cb of closeCallbacks) cb(err);
|
441 | };
|
442 | if (compilation) {
|
443 | const logger = compilation.getLogger("webpack.Watching");
|
444 | logger.time("storeBuildDependencies");
|
445 | this.compiler.cache.storeBuildDependencies(
|
446 | compilation.buildDependencies,
|
447 | err2 => {
|
448 | logger.timeEnd("storeBuildDependencies");
|
449 | shutdown(err || err2);
|
450 | }
|
451 | );
|
452 | } else {
|
453 | shutdown(err);
|
454 | }
|
455 | };
|
456 |
|
457 | this.closed = true;
|
458 | if (this.watcher) {
|
459 | this.watcher.close();
|
460 | this.watcher = null;
|
461 | }
|
462 | if (this.pausedWatcher) {
|
463 | this.pausedWatcher.close();
|
464 | this.pausedWatcher = null;
|
465 | }
|
466 | this._closeCallbacks = [];
|
467 | if (callback) {
|
468 | this._closeCallbacks.push(callback);
|
469 | }
|
470 | if (this.running) {
|
471 | this.invalid = true;
|
472 | this._done = finalCallback;
|
473 | } else {
|
474 | finalCallback();
|
475 | }
|
476 | }
|
477 | }
|
478 |
|
479 | module.exports = Watching;
|