UNPKG

76.4 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { create: createResolver } = require("enhanced-resolve");
9const asyncLib = require("neo-async");
10const AsyncQueue = require("./util/AsyncQueue");
11const createHash = require("./util/createHash");
12const { join, dirname, relative } = require("./util/fs");
13const makeSerializable = require("./util/makeSerializable");
14
15/** @typedef {import("./WebpackError")} WebpackError */
16/** @typedef {import("./logging/Logger").Logger} Logger */
17/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
18
19const resolveContext = createResolver({
20 resolveToContext: true,
21 exportsFields: []
22});
23const resolve = createResolver({
24 extensions: [".js", ".json", ".node"],
25 conditionNames: ["require"]
26});
27
28let FS_ACCURACY = 2000;
29
30const EMPTY_SET = new Set();
31
32const RBDT_RESOLVE = 0;
33const RBDT_RESOLVE_DIRECTORY = 1;
34const RBDT_RESOLVE_FILE = 2;
35const RBDT_DIRECTORY = 3;
36const RBDT_FILE = 4;
37const RBDT_DIRECTORY_DEPENDENCIES = 5;
38const RBDT_FILE_DEPENDENCIES = 6;
39
40const INVALID = Symbol("invalid");
41
42/**
43 * @typedef {Object} FileSystemInfoEntry
44 * @property {number} safeTime
45 * @property {number=} timestamp
46 * @property {string=} timestampHash
47 */
48
49/**
50 * @typedef {Object} TimestampAndHash
51 * @property {number} safeTime
52 * @property {number=} timestamp
53 * @property {string=} timestampHash
54 * @property {string} hash
55 */
56
57/**
58 * @typedef {Object} SnapshotOptimizationEntry
59 * @property {Snapshot} snapshot
60 * @property {number} shared
61 * @property {Set<string>} snapshotContent
62 * @property {Set<SnapshotOptimizationEntry>} children
63 */
64
65/**
66 * @typedef {Object} ResolveBuildDependenciesResult
67 * @property {Set<string>} files list of files
68 * @property {Set<string>} directories list of directories
69 * @property {Set<string>} missing list of missing entries
70 * @property {Map<string, string>} resolveResults stored resolve results
71 * @property {Object} resolveDependencies dependencies of the resolving
72 * @property {Set<string>} resolveDependencies.files list of files
73 * @property {Set<string>} resolveDependencies.directories list of directories
74 * @property {Set<string>} resolveDependencies.missing list of missing entries
75 */
76
77const DONE_ITERATOR_RESULT = new Set().keys().next();
78
79// cspell:word tshs
80// Tsh = Timestamp + Hash
81// Tshs = Timestamp + Hash combinations
82
83class Snapshot {
84 constructor() {
85 this._flags = 0;
86 /** @type {number | undefined} */
87 this.startTime = undefined;
88 /** @type {Map<string, FileSystemInfoEntry> | undefined} */
89 this.fileTimestamps = undefined;
90 /** @type {Map<string, string> | undefined} */
91 this.fileHashes = undefined;
92 /** @type {Map<string, TimestampAndHash | string> | undefined} */
93 this.fileTshs = undefined;
94 /** @type {Map<string, FileSystemInfoEntry> | undefined} */
95 this.contextTimestamps = undefined;
96 /** @type {Map<string, string> | undefined} */
97 this.contextHashes = undefined;
98 /** @type {Map<string, TimestampAndHash | string> | undefined} */
99 this.contextTshs = undefined;
100 /** @type {Map<string, boolean> | undefined} */
101 this.missingExistence = undefined;
102 /** @type {Map<string, string> | undefined} */
103 this.managedItemInfo = undefined;
104 /** @type {Set<string> | undefined} */
105 this.managedFiles = undefined;
106 /** @type {Set<string> | undefined} */
107 this.managedContexts = undefined;
108 /** @type {Set<string> | undefined} */
109 this.managedMissing = undefined;
110 /** @type {Set<Snapshot> | undefined} */
111 this.children = undefined;
112 }
113
114 hasStartTime() {
115 return (this._flags & 1) !== 0;
116 }
117
118 setStartTime(value) {
119 this._flags = this._flags | 1;
120 this.startTime = value;
121 }
122
123 setMergedStartTime(value, snapshot) {
124 if (value) {
125 if (snapshot.hasStartTime()) {
126 this.setStartTime(Math.min(value, snapshot.startTime));
127 } else {
128 this.setStartTime(value);
129 }
130 } else {
131 if (snapshot.hasStartTime()) this.setStartTime(snapshot.startTime);
132 }
133 }
134
135 hasFileTimestamps() {
136 return (this._flags & 2) !== 0;
137 }
138
139 setFileTimestamps(value) {
140 this._flags = this._flags | 2;
141 this.fileTimestamps = value;
142 }
143
144 hasFileHashes() {
145 return (this._flags & 4) !== 0;
146 }
147
148 setFileHashes(value) {
149 this._flags = this._flags | 4;
150 this.fileHashes = value;
151 }
152
153 hasFileTshs() {
154 return (this._flags & 8) !== 0;
155 }
156
157 setFileTshs(value) {
158 this._flags = this._flags | 8;
159 this.fileTshs = value;
160 }
161
162 hasContextTimestamps() {
163 return (this._flags & 0x10) !== 0;
164 }
165
166 setContextTimestamps(value) {
167 this._flags = this._flags | 0x10;
168 this.contextTimestamps = value;
169 }
170
171 hasContextHashes() {
172 return (this._flags & 0x20) !== 0;
173 }
174
175 setContextHashes(value) {
176 this._flags = this._flags | 0x20;
177 this.contextHashes = value;
178 }
179
180 hasContextTshs() {
181 return (this._flags & 0x40) !== 0;
182 }
183
184 setContextTshs(value) {
185 this._flags = this._flags | 0x40;
186 this.contextTshs = value;
187 }
188
189 hasMissingExistence() {
190 return (this._flags & 0x80) !== 0;
191 }
192
193 setMissingExistence(value) {
194 this._flags = this._flags | 0x80;
195 this.missingExistence = value;
196 }
197
198 hasManagedItemInfo() {
199 return (this._flags & 0x100) !== 0;
200 }
201
202 setManagedItemInfo(value) {
203 this._flags = this._flags | 0x100;
204 this.managedItemInfo = value;
205 }
206
207 hasManagedFiles() {
208 return (this._flags & 0x200) !== 0;
209 }
210
211 setManagedFiles(value) {
212 this._flags = this._flags | 0x200;
213 this.managedFiles = value;
214 }
215
216 hasManagedContexts() {
217 return (this._flags & 0x400) !== 0;
218 }
219
220 setManagedContexts(value) {
221 this._flags = this._flags | 0x400;
222 this.managedContexts = value;
223 }
224
225 hasManagedMissing() {
226 return (this._flags & 0x800) !== 0;
227 }
228
229 setManagedMissing(value) {
230 this._flags = this._flags | 0x800;
231 this.managedMissing = value;
232 }
233
234 hasChildren() {
235 return (this._flags & 0x1000) !== 0;
236 }
237
238 setChildren(value) {
239 this._flags = this._flags | 0x1000;
240 this.children = value;
241 }
242
243 addChild(child) {
244 if (!this.hasChildren()) {
245 this.setChildren(new Set());
246 }
247 this.children.add(child);
248 }
249
250 serialize({ write }) {
251 write(this._flags);
252 if (this.hasStartTime()) write(this.startTime);
253 if (this.hasFileTimestamps()) write(this.fileTimestamps);
254 if (this.hasFileHashes()) write(this.fileHashes);
255 if (this.hasFileTshs()) write(this.fileTshs);
256 if (this.hasContextTimestamps()) write(this.contextTimestamps);
257 if (this.hasContextHashes()) write(this.contextHashes);
258 if (this.hasContextTshs()) write(this.contextTshs);
259 if (this.hasMissingExistence()) write(this.missingExistence);
260 if (this.hasManagedItemInfo()) write(this.managedItemInfo);
261 if (this.hasManagedFiles()) write(this.managedFiles);
262 if (this.hasManagedContexts()) write(this.managedContexts);
263 if (this.hasManagedMissing()) write(this.managedMissing);
264 if (this.hasChildren()) write(this.children);
265 }
266
267 deserialize({ read }) {
268 this._flags = read();
269 if (this.hasStartTime()) this.startTime = read();
270 if (this.hasFileTimestamps()) this.fileTimestamps = read();
271 if (this.hasFileHashes()) this.fileHashes = read();
272 if (this.hasFileTshs()) this.fileTshs = read();
273 if (this.hasContextTimestamps()) this.contextTimestamps = read();
274 if (this.hasContextHashes()) this.contextHashes = read();
275 if (this.hasContextTshs()) this.contextTshs = read();
276 if (this.hasMissingExistence()) this.missingExistence = read();
277 if (this.hasManagedItemInfo()) this.managedItemInfo = read();
278 if (this.hasManagedFiles()) this.managedFiles = read();
279 if (this.hasManagedContexts()) this.managedContexts = read();
280 if (this.hasManagedMissing()) this.managedMissing = read();
281 if (this.hasChildren()) this.children = read();
282 }
283
284 /**
285 * @param {function(Snapshot): (Map<string, any> | Set<string>)[]} getMaps first
286 * @returns {Iterable<string>} iterable
287 */
288 _createIterable(getMaps) {
289 let snapshot = this;
290 return {
291 [Symbol.iterator]() {
292 let state = 0;
293 /** @type {IterableIterator<string>} */
294 let it;
295 let maps = getMaps(snapshot);
296 const queue = [];
297 return {
298 next() {
299 for (;;) {
300 switch (state) {
301 case 0:
302 if (maps.length > 0) {
303 const map = maps.pop();
304 if (map !== undefined) {
305 it = map.keys();
306 state = 1;
307 } else {
308 break;
309 }
310 } else {
311 state = 2;
312 break;
313 }
314 /* falls through */
315 case 1: {
316 const result = it.next();
317 if (!result.done) return result;
318 state = 0;
319 break;
320 }
321 case 2: {
322 const children = snapshot.children;
323 if (children !== undefined) {
324 for (const child of children) {
325 queue.push(child);
326 }
327 }
328 if (queue.length > 0) {
329 snapshot = queue.pop();
330 maps = getMaps(snapshot);
331 state = 0;
332 break;
333 } else {
334 state = 3;
335 }
336 }
337 /* falls through */
338 case 3:
339 return DONE_ITERATOR_RESULT;
340 }
341 }
342 }
343 };
344 }
345 };
346 }
347
348 /**
349 * @returns {Iterable<string>} iterable
350 */
351 getFileIterable() {
352 return this._createIterable(s => [
353 s.fileTimestamps,
354 s.fileHashes,
355 s.fileTshs,
356 s.managedFiles
357 ]);
358 }
359
360 /**
361 * @returns {Iterable<string>} iterable
362 */
363 getContextIterable() {
364 return this._createIterable(s => [
365 s.contextTimestamps,
366 s.contextHashes,
367 s.contextTshs,
368 s.managedContexts
369 ]);
370 }
371
372 /**
373 * @returns {Iterable<string>} iterable
374 */
375 getMissingIterable() {
376 return this._createIterable(s => [s.missingExistence, s.managedMissing]);
377 }
378}
379
380makeSerializable(Snapshot, "webpack/lib/FileSystemInfo", "Snapshot");
381
382const MIN_COMMON_SNAPSHOT_SIZE = 3;
383
384/**
385 * @template T
386 */
387class SnapshotOptimization {
388 /**
389 * @param {function(Snapshot): boolean} has has value
390 * @param {function(Snapshot): Map<string, T> | Set<string>} get get value
391 * @param {function(Snapshot, Map<string, T> | Set<string>): void} set set value
392 * @param {boolean=} isSet value is an Set instead of a Map
393 */
394 constructor(has, get, set, isSet = false) {
395 this._has = has;
396 this._get = get;
397 this._set = set;
398 this._isSet = isSet;
399 /** @type {Map<string, SnapshotOptimizationEntry>} */
400 this._map = new Map();
401 this._statItemsShared = 0;
402 this._statItemsUnshared = 0;
403 this._statSharedSnapshots = 0;
404 this._statReusedSharedSnapshots = 0;
405 }
406
407 getStatisticMessage() {
408 const total = this._statItemsShared + this._statItemsUnshared;
409 if (total === 0) return undefined;
410 return `${
411 this._statItemsShared && Math.round((this._statItemsShared * 100) / total)
412 }% (${this._statItemsShared}/${total}) entries shared via ${
413 this._statSharedSnapshots
414 } shared snapshots (${
415 this._statReusedSharedSnapshots + this._statSharedSnapshots
416 } times referenced)`;
417 }
418
419 storeUnsharedSnapshot(snapshot, locations) {
420 if (locations === undefined) return;
421 const optimizationEntry = {
422 snapshot,
423 shared: 0,
424 snapshotContent: undefined,
425 children: undefined
426 };
427 for (const path of locations) {
428 this._map.set(path, optimizationEntry);
429 }
430 }
431
432 optimize(capturedFiles, startTime, children) {
433 /** @type {Set<string>} */
434 const unsetOptimizationEntries = new Set();
435 /** @type {Set<SnapshotOptimizationEntry>} */
436 const checkedOptimizationEntries = new Set();
437 /**
438 * @param {SnapshotOptimizationEntry} entry optimization entry
439 * @returns {void}
440 */
441 const increaseSharedAndStoreOptimizationEntry = entry => {
442 if (entry.children !== undefined) {
443 entry.children.forEach(increaseSharedAndStoreOptimizationEntry);
444 }
445 entry.shared++;
446 storeOptimizationEntry(entry);
447 };
448 /**
449 * @param {SnapshotOptimizationEntry} entry optimization entry
450 * @returns {void}
451 */
452 const storeOptimizationEntry = entry => {
453 for (const path of entry.snapshotContent) {
454 const old = this._map.get(path);
455 if (old.shared < entry.shared) {
456 this._map.set(path, entry);
457 }
458 capturedFiles.delete(path);
459 }
460 };
461 const capturedFilesSize = capturedFiles.size;
462 capturedFiles: for (const path of capturedFiles) {
463 const optimizationEntry = this._map.get(path);
464 if (optimizationEntry === undefined) {
465 unsetOptimizationEntries.add(path);
466 continue;
467 }
468 if (checkedOptimizationEntries.has(optimizationEntry)) continue;
469 const snapshot = optimizationEntry.snapshot;
470 if (optimizationEntry.shared > 0) {
471 // It's a shared snapshot
472 // We can't change it, so we can only use it when all files match
473 // and startTime is compatible
474 if (
475 startTime &&
476 (!snapshot.startTime || snapshot.startTime > startTime)
477 ) {
478 continue;
479 }
480 const nonSharedFiles = new Set();
481 const snapshotContent = optimizationEntry.snapshotContent;
482 const snapshotEntries = this._get(snapshot);
483 for (const path of snapshotContent) {
484 if (!capturedFiles.has(path)) {
485 if (!snapshotEntries.has(path)) {
486 // File is not shared and can't be removed from the snapshot
487 // because it's in a child of the snapshot
488 checkedOptimizationEntries.add(optimizationEntry);
489 continue capturedFiles;
490 }
491 nonSharedFiles.add(path);
492 continue;
493 }
494 }
495 if (nonSharedFiles.size === 0) {
496 // The complete snapshot is shared
497 // add it as child
498 children.add(snapshot);
499 increaseSharedAndStoreOptimizationEntry(optimizationEntry);
500 this._statReusedSharedSnapshots++;
501 } else {
502 // Only a part of the snapshot is shared
503 const sharedCount = snapshotContent.size - nonSharedFiles.size;
504 if (sharedCount < MIN_COMMON_SNAPSHOT_SIZE) {
505 // Common part it too small
506 checkedOptimizationEntries.add(optimizationEntry);
507 continue capturedFiles;
508 }
509 // Extract common timestamps from both snapshots
510 let commonMap;
511 if (this._isSet) {
512 commonMap = new Set();
513 for (const path of /** @type {Set<string>} */ (snapshotEntries)) {
514 if (nonSharedFiles.has(path)) continue;
515 commonMap.add(path);
516 snapshotEntries.delete(path);
517 }
518 } else {
519 commonMap = new Map();
520 const map = /** @type {Map<string, T>} */ (snapshotEntries);
521 for (const [path, value] of map) {
522 if (nonSharedFiles.has(path)) continue;
523 commonMap.set(path, value);
524 snapshotEntries.delete(path);
525 }
526 }
527 // Create and attach snapshot
528 const commonSnapshot = new Snapshot();
529 commonSnapshot.setMergedStartTime(startTime, snapshot);
530 this._set(commonSnapshot, commonMap);
531 children.add(commonSnapshot);
532 snapshot.addChild(commonSnapshot);
533 // Create optimization entry
534 const newEntry = {
535 snapshot: commonSnapshot,
536 shared: optimizationEntry.shared + 1,
537 snapshotContent: new Set(commonMap.keys()),
538 children: undefined
539 };
540 if (optimizationEntry.children === undefined)
541 optimizationEntry.children = new Set();
542 optimizationEntry.children.add(newEntry);
543 storeOptimizationEntry(newEntry);
544 this._statSharedSnapshots++;
545 }
546 } else {
547 // It's a unshared snapshot
548 // We can extract a common shared snapshot
549 // with all common files
550 const snapshotEntries = this._get(snapshot);
551 let commonMap;
552 if (this._isSet) {
553 commonMap = new Set();
554 const set = /** @type {Set<string>} */ (snapshotEntries);
555 if (capturedFiles.size < set.size) {
556 for (const path of capturedFiles) {
557 if (set.has(path)) commonMap.add(path);
558 }
559 } else {
560 for (const path of set) {
561 if (capturedFiles.has(path)) commonMap.add(path);
562 }
563 }
564 } else {
565 commonMap = new Map();
566 const map = /** @type {Map<string, T>} */ (snapshotEntries);
567 for (const path of capturedFiles) {
568 const ts = map.get(path);
569 if (ts === undefined) continue;
570 commonMap.set(path, ts);
571 }
572 }
573
574 if (commonMap.size < MIN_COMMON_SNAPSHOT_SIZE) {
575 // Common part it too small
576 checkedOptimizationEntries.add(optimizationEntry);
577 continue capturedFiles;
578 }
579 // Create and attach snapshot
580 const commonSnapshot = new Snapshot();
581 commonSnapshot.setMergedStartTime(startTime, snapshot);
582 this._set(commonSnapshot, commonMap);
583 children.add(commonSnapshot);
584 snapshot.addChild(commonSnapshot);
585 // Remove files from snapshot
586 for (const path of commonMap.keys()) snapshotEntries.delete(path);
587 const sharedCount = commonMap.size;
588 this._statItemsUnshared -= sharedCount;
589 this._statItemsShared += sharedCount;
590 // Create optimization entry
591 storeOptimizationEntry({
592 snapshot: commonSnapshot,
593 shared: 2,
594 snapshotContent: new Set(commonMap.keys()),
595 children: undefined
596 });
597 this._statSharedSnapshots++;
598 }
599 checkedOptimizationEntries.add(optimizationEntry);
600 }
601 const unshared = capturedFiles.size;
602 this._statItemsUnshared += unshared;
603 this._statItemsShared += capturedFilesSize - unshared;
604 return unsetOptimizationEntries;
605 }
606}
607
608/* istanbul ignore next */
609/**
610 * @param {number} mtime mtime
611 */
612const applyMtime = mtime => {
613 if (FS_ACCURACY > 1 && mtime % 2 !== 0) FS_ACCURACY = 1;
614 else if (FS_ACCURACY > 10 && mtime % 20 !== 0) FS_ACCURACY = 10;
615 else if (FS_ACCURACY > 100 && mtime % 200 !== 0) FS_ACCURACY = 100;
616 else if (FS_ACCURACY > 1000 && mtime % 2000 !== 0) FS_ACCURACY = 1000;
617};
618
619/**
620 * @template T
621 * @template K
622 * @param {Map<T, K>} a source map
623 * @param {Map<T, K>} b joining map
624 * @returns {Map<T, K>} joined map
625 */
626const mergeMaps = (a, b) => {
627 if (!b || b.size === 0) return a;
628 if (!a || a.size === 0) return b;
629 const map = new Map(a);
630 for (const [key, value] of b) {
631 map.set(key, value);
632 }
633 return map;
634};
635
636/**
637 * @template T
638 * @template K
639 * @param {Set<T, K>} a source map
640 * @param {Set<T, K>} b joining map
641 * @returns {Set<T, K>} joined map
642 */
643const mergeSets = (a, b) => {
644 if (!b || b.size === 0) return a;
645 if (!a || a.size === 0) return b;
646 const map = new Set(a);
647 for (const item of b) {
648 map.add(item);
649 }
650 return map;
651};
652
653/**
654 * Finding file or directory to manage
655 * @param {string} managedPath path that is managing by {@link FileSystemInfo}
656 * @param {string} path path to file or directory
657 * @returns {string|null} managed item
658 * @example
659 * getManagedItem(
660 * '/Users/user/my-project/node_modules/',
661 * '/Users/user/my-project/node_modules/package/index.js'
662 * ) === '/Users/user/my-project/node_modules/package'
663 * getManagedItem(
664 * '/Users/user/my-project/node_modules/',
665 * '/Users/user/my-project/node_modules/package1/node_modules/package2'
666 * ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
667 * getManagedItem(
668 * '/Users/user/my-project/node_modules/',
669 * '/Users/user/my-project/node_modules/.bin/script.js'
670 * ) === null // hidden files are disallowed as managed items
671 * getManagedItem(
672 * '/Users/user/my-project/node_modules/',
673 * '/Users/user/my-project/node_modules/package'
674 * ) === '/Users/user/my-project/node_modules/package'
675 */
676const getManagedItem = (managedPath, path) => {
677 let i = managedPath.length;
678 let slashes = 1;
679 let startingPosition = true;
680 loop: while (i < path.length) {
681 switch (path.charCodeAt(i)) {
682 case 47: // slash
683 case 92: // backslash
684 if (--slashes === 0) break loop;
685 startingPosition = true;
686 break;
687 case 46: // .
688 // hidden files are disallowed as managed items
689 // it's probably .yarn-integrity or .cache
690 if (startingPosition) return null;
691 break;
692 case 64: // @
693 if (!startingPosition) return null;
694 slashes++;
695 break;
696 default:
697 startingPosition = false;
698 break;
699 }
700 i++;
701 }
702 if (i === path.length) slashes--;
703 // return null when path is incomplete
704 if (slashes !== 0) return null;
705 // if (path.slice(i + 1, i + 13) === "node_modules")
706 if (
707 path.length >= i + 13 &&
708 path.charCodeAt(i + 1) === 110 &&
709 path.charCodeAt(i + 2) === 111 &&
710 path.charCodeAt(i + 3) === 100 &&
711 path.charCodeAt(i + 4) === 101 &&
712 path.charCodeAt(i + 5) === 95 &&
713 path.charCodeAt(i + 6) === 109 &&
714 path.charCodeAt(i + 7) === 111 &&
715 path.charCodeAt(i + 8) === 100 &&
716 path.charCodeAt(i + 9) === 117 &&
717 path.charCodeAt(i + 10) === 108 &&
718 path.charCodeAt(i + 11) === 101 &&
719 path.charCodeAt(i + 12) === 115
720 ) {
721 // if this is the end of the path
722 if (path.length === i + 13) {
723 // return the node_modules directory
724 // it's special
725 return path;
726 }
727 const c = path.charCodeAt(i + 13);
728 // if next symbol is slash or backslash
729 if (c === 47 || c === 92) {
730 // Managed subpath
731 return getManagedItem(path.slice(0, i + 14), path);
732 }
733 }
734 return path.slice(0, i);
735};
736
737/**
738 * @param {FileSystemInfoEntry} entry file system info entry
739 * @returns {boolean} existence flag
740 */
741const toExistence = entry => {
742 return Boolean(entry);
743};
744
745/**
746 * Used to access information about the filesystem in a cached way
747 */
748class FileSystemInfo {
749 /**
750 * @param {InputFileSystem} fs file system
751 * @param {Object} options options
752 * @param {Iterable<string>=} options.managedPaths paths that are only managed by a package manager
753 * @param {Iterable<string>=} options.immutablePaths paths that are immutable
754 * @param {Logger=} options.logger logger used to log invalid snapshots
755 */
756 constructor(fs, { managedPaths = [], immutablePaths = [], logger } = {}) {
757 this.fs = fs;
758 this.logger = logger;
759 this._remainingLogs = logger ? 40 : 0;
760 this._loggedPaths = logger ? new Set() : undefined;
761 /** @type {WeakMap<Snapshot, boolean | (function(WebpackError=, boolean=): void)[]>} */
762 this._snapshotCache = new WeakMap();
763 this._fileTimestampsOptimization = new SnapshotOptimization(
764 s => s.hasFileTimestamps(),
765 s => s.fileTimestamps,
766 (s, v) => s.setFileTimestamps(v)
767 );
768 this._fileHashesOptimization = new SnapshotOptimization(
769 s => s.hasFileHashes(),
770 s => s.fileHashes,
771 (s, v) => s.setFileHashes(v)
772 );
773 this._fileTshsOptimization = new SnapshotOptimization(
774 s => s.hasFileTshs(),
775 s => s.fileTshs,
776 (s, v) => s.setFileTshs(v)
777 );
778 this._contextTimestampsOptimization = new SnapshotOptimization(
779 s => s.hasContextTimestamps(),
780 s => s.contextTimestamps,
781 (s, v) => s.setContextTimestamps(v)
782 );
783 this._contextHashesOptimization = new SnapshotOptimization(
784 s => s.hasContextHashes(),
785 s => s.contextHashes,
786 (s, v) => s.setContextHashes(v)
787 );
788 this._contextTshsOptimization = new SnapshotOptimization(
789 s => s.hasContextTshs(),
790 s => s.contextTshs,
791 (s, v) => s.setContextTshs(v)
792 );
793 this._missingExistenceOptimization = new SnapshotOptimization(
794 s => s.hasMissingExistence(),
795 s => s.missingExistence,
796 (s, v) => s.setMissingExistence(v)
797 );
798 this._managedItemInfoOptimization = new SnapshotOptimization(
799 s => s.hasManagedItemInfo(),
800 s => s.managedItemInfo,
801 (s, v) => s.setManagedItemInfo(v)
802 );
803 this._managedFilesOptimization = new SnapshotOptimization(
804 s => s.hasManagedFiles(),
805 s => s.managedFiles,
806 (s, v) => s.setManagedFiles(v),
807 true
808 );
809 this._managedContextsOptimization = new SnapshotOptimization(
810 s => s.hasManagedContexts(),
811 s => s.managedContexts,
812 (s, v) => s.setManagedContexts(v),
813 true
814 );
815 this._managedMissingOptimization = new SnapshotOptimization(
816 s => s.hasManagedMissing(),
817 s => s.managedMissing,
818 (s, v) => s.setManagedMissing(v),
819 true
820 );
821 /** @type {Map<string, FileSystemInfoEntry | "ignore" | null>} */
822 this._fileTimestamps = new Map();
823 /** @type {Map<string, string>} */
824 this._fileHashes = new Map();
825 /** @type {Map<string, TimestampAndHash | string>} */
826 this._fileTshs = new Map();
827 /** @type {Map<string, FileSystemInfoEntry | "ignore" | null>} */
828 this._contextTimestamps = new Map();
829 /** @type {Map<string, string>} */
830 this._contextHashes = new Map();
831 /** @type {Map<string, TimestampAndHash | string>} */
832 this._contextTshs = new Map();
833 /** @type {Map<string, string>} */
834 this._managedItems = new Map();
835 /** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
836 this.fileTimestampQueue = new AsyncQueue({
837 name: "file timestamp",
838 parallelism: 30,
839 processor: this._readFileTimestamp.bind(this)
840 });
841 /** @type {AsyncQueue<string, string, string | null>} */
842 this.fileHashQueue = new AsyncQueue({
843 name: "file hash",
844 parallelism: 10,
845 processor: this._readFileHash.bind(this)
846 });
847 /** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
848 this.contextTimestampQueue = new AsyncQueue({
849 name: "context timestamp",
850 parallelism: 2,
851 processor: this._readContextTimestamp.bind(this)
852 });
853 /** @type {AsyncQueue<string, string, string | null>} */
854 this.contextHashQueue = new AsyncQueue({
855 name: "context hash",
856 parallelism: 2,
857 processor: this._readContextHash.bind(this)
858 });
859 /** @type {AsyncQueue<string, string, string | null>} */
860 this.managedItemQueue = new AsyncQueue({
861 name: "managed item info",
862 parallelism: 10,
863 processor: this._getManagedItemInfo.bind(this)
864 });
865 /** @type {AsyncQueue<string, string, Set<string>>} */
866 this.managedItemDirectoryQueue = new AsyncQueue({
867 name: "managed item directory info",
868 parallelism: 10,
869 processor: this._getManagedItemDirectoryInfo.bind(this)
870 });
871 this.managedPaths = Array.from(managedPaths);
872 this.managedPathsWithSlash = this.managedPaths.map(p =>
873 join(fs, p, "_").slice(0, -1)
874 );
875 this.immutablePaths = Array.from(immutablePaths);
876 this.immutablePathsWithSlash = this.immutablePaths.map(p =>
877 join(fs, p, "_").slice(0, -1)
878 );
879
880 this._cachedDeprecatedFileTimestamps = undefined;
881 this._cachedDeprecatedContextTimestamps = undefined;
882
883 this._statCreatedSnapshots = 0;
884 this._statTestedSnapshotsCached = 0;
885 this._statTestedSnapshotsNotCached = 0;
886 this._statTestedChildrenCached = 0;
887 this._statTestedChildrenNotCached = 0;
888 this._statTestedEntries = 0;
889 }
890
891 logStatistics() {
892 const logWhenMessage = (header, message) => {
893 if (message) {
894 this.logger.log(`${header}: ${message}`);
895 }
896 };
897 this.logger.log(`${this._statCreatedSnapshots} new snapshots created`);
898 this.logger.log(
899 `${
900 this._statTestedSnapshotsNotCached &&
901 Math.round(
902 (this._statTestedSnapshotsNotCached * 100) /
903 (this._statTestedSnapshotsCached +
904 this._statTestedSnapshotsNotCached)
905 )
906 }% root snapshot uncached (${this._statTestedSnapshotsNotCached} / ${
907 this._statTestedSnapshotsCached + this._statTestedSnapshotsNotCached
908 })`
909 );
910 this.logger.log(
911 `${
912 this._statTestedChildrenNotCached &&
913 Math.round(
914 (this._statTestedChildrenNotCached * 100) /
915 (this._statTestedChildrenCached + this._statTestedChildrenNotCached)
916 )
917 }% children snapshot uncached (${this._statTestedChildrenNotCached} / ${
918 this._statTestedChildrenCached + this._statTestedChildrenNotCached
919 })`
920 );
921 this.logger.log(`${this._statTestedEntries} entries tested`);
922 this.logger.log(
923 `File info in cache: ${this._fileTimestamps.size} timestamps ${this._fileHashes.size} hashes ${this._fileTshs.size} timestamp hash combinations`
924 );
925 logWhenMessage(
926 `File timestamp snapshot optimization`,
927 this._fileTimestampsOptimization.getStatisticMessage()
928 );
929 logWhenMessage(
930 `File hash snapshot optimization`,
931 this._fileHashesOptimization.getStatisticMessage()
932 );
933 logWhenMessage(
934 `File timestamp hash combination snapshot optimization`,
935 this._fileTshsOptimization.getStatisticMessage()
936 );
937 this.logger.log(
938 `Directory info in cache: ${this._contextTimestamps.size} timestamps ${this._contextHashes.size} hashes ${this._contextTshs.size} timestamp hash combinations`
939 );
940 logWhenMessage(
941 `Directory timestamp snapshot optimization`,
942 this._contextTimestampsOptimization.getStatisticMessage()
943 );
944 logWhenMessage(
945 `Directory hash snapshot optimization`,
946 this._contextHashesOptimization.getStatisticMessage()
947 );
948 logWhenMessage(
949 `Directory timestamp hash combination snapshot optimization`,
950 this._contextTshsOptimization.getStatisticMessage()
951 );
952 logWhenMessage(
953 `Missing items snapshot optimization`,
954 this._missingExistenceOptimization.getStatisticMessage()
955 );
956 this.logger.log(
957 `Managed items info in cache: ${this._managedItems.size} items`
958 );
959 logWhenMessage(
960 `Managed items snapshot optimization`,
961 this._managedItemInfoOptimization.getStatisticMessage()
962 );
963 logWhenMessage(
964 `Managed files snapshot optimization`,
965 this._managedFilesOptimization.getStatisticMessage()
966 );
967 logWhenMessage(
968 `Managed contexts snapshot optimization`,
969 this._managedContextsOptimization.getStatisticMessage()
970 );
971 logWhenMessage(
972 `Managed missing snapshot optimization`,
973 this._managedMissingOptimization.getStatisticMessage()
974 );
975 }
976
977 _log(path, reason, ...args) {
978 const key = path + reason;
979 if (this._loggedPaths.has(key)) return;
980 this._loggedPaths.add(key);
981 this.logger.debug(`${path} invalidated because ${reason}`, ...args);
982 if (--this._remainingLogs === 0) {
983 this.logger.debug(
984 "Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
985 );
986 }
987 }
988
989 /**
990 * @param {Map<string, FileSystemInfoEntry | "ignore" | null>} map timestamps
991 * @returns {void}
992 */
993 addFileTimestamps(map) {
994 for (const [path, ts] of map) {
995 this._fileTimestamps.set(path, ts);
996 }
997 this._cachedDeprecatedFileTimestamps = undefined;
998 }
999
1000 /**
1001 * @param {Map<string, FileSystemInfoEntry | "ignore" | null>} map timestamps
1002 * @returns {void}
1003 */
1004 addContextTimestamps(map) {
1005 for (const [path, ts] of map) {
1006 this._contextTimestamps.set(path, ts);
1007 }
1008 this._cachedDeprecatedContextTimestamps = undefined;
1009 }
1010
1011 /**
1012 * @param {string} path file path
1013 * @param {function(WebpackError=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1014 * @returns {void}
1015 */
1016 getFileTimestamp(path, callback) {
1017 const cache = this._fileTimestamps.get(path);
1018 if (cache !== undefined) return callback(null, cache);
1019 this.fileTimestampQueue.add(path, callback);
1020 }
1021
1022 /**
1023 * @param {string} path context path
1024 * @param {function(WebpackError=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1025 * @returns {void}
1026 */
1027 getContextTimestamp(path, callback) {
1028 const cache = this._contextTimestamps.get(path);
1029 if (cache !== undefined) return callback(null, cache);
1030 this.contextTimestampQueue.add(path, callback);
1031 }
1032
1033 /**
1034 * @param {string} path file path
1035 * @param {function(WebpackError=, string=): void} callback callback function
1036 * @returns {void}
1037 */
1038 getFileHash(path, callback) {
1039 const cache = this._fileHashes.get(path);
1040 if (cache !== undefined) return callback(null, cache);
1041 this.fileHashQueue.add(path, callback);
1042 }
1043
1044 /**
1045 * @param {string} path context path
1046 * @param {function(WebpackError=, string=): void} callback callback function
1047 * @returns {void}
1048 */
1049 getContextHash(path, callback) {
1050 const cache = this._contextHashes.get(path);
1051 if (cache !== undefined) return callback(null, cache);
1052 this.contextHashQueue.add(path, callback);
1053 }
1054
1055 /**
1056 * @param {string} context context directory
1057 * @param {Iterable<string>} deps dependencies
1058 * @param {function(Error=, ResolveBuildDependenciesResult=): void} callback callback function
1059 * @returns {void}
1060 */
1061 resolveBuildDependencies(context, deps, callback) {
1062 /** @type {Set<string>} */
1063 const files = new Set();
1064 /** @type {Set<string>} */
1065 const directories = new Set();
1066 /** @type {Set<string>} */
1067 const missing = new Set();
1068 /** @type {Set<string>} */
1069 const resolveFiles = new Set();
1070 /** @type {Set<string>} */
1071 const resolveDirectories = new Set();
1072 /** @type {Set<string>} */
1073 const resolveMissing = new Set();
1074 /** @type {Map<string, string>} */
1075 const resolveResults = new Map();
1076 /** @type {asyncLib.QueueObject<{type: number, path: string, context?: string, expected?: string }, Error>} */
1077 const queue = asyncLib.queue(
1078 ({ type, context, path, expected }, callback) => {
1079 const resolveDirectory = path => {
1080 const key = `d\n${context}\n${path}`;
1081 if (resolveResults.has(key)) {
1082 return callback();
1083 }
1084 resolveContext(
1085 context,
1086 path,
1087 {
1088 fileDependencies: resolveFiles,
1089 contextDependencies: resolveDirectories,
1090 missingDependencies: resolveMissing
1091 },
1092 (err, result) => {
1093 if (err) {
1094 if (
1095 err.code === "ENOENT" ||
1096 err.code === "UNDECLARED_DEPENDENCY"
1097 ) {
1098 return callback();
1099 }
1100 err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
1101 return callback(err);
1102 }
1103 resolveResults.set(key, result);
1104 queue.push({
1105 type: RBDT_DIRECTORY,
1106 path: result
1107 });
1108 callback();
1109 }
1110 );
1111 };
1112 const resolveFile = path => {
1113 const key = `f\n${context}\n${path}`;
1114 if (resolveResults.has(key)) {
1115 return callback();
1116 }
1117 resolve(
1118 context,
1119 path,
1120 {
1121 fileDependencies: resolveFiles,
1122 contextDependencies: resolveDirectories,
1123 missingDependencies: resolveMissing
1124 },
1125 (err, result) => {
1126 if (expected) {
1127 if (result === expected) {
1128 resolveResults.set(key, result);
1129 }
1130 } else {
1131 if (err) {
1132 if (
1133 err.code === "ENOENT" ||
1134 err.code === "UNDECLARED_DEPENDENCY"
1135 ) {
1136 return callback();
1137 }
1138 err.message += `\nwhile resolving '${path}' in ${context} as file`;
1139 return callback(err);
1140 }
1141 resolveResults.set(key, result);
1142 queue.push({
1143 type: RBDT_FILE,
1144 path: result
1145 });
1146 }
1147 callback();
1148 }
1149 );
1150 };
1151 switch (type) {
1152 case RBDT_RESOLVE: {
1153 const isDirectory = /[\\/]$/.test(path);
1154 if (isDirectory) {
1155 resolveDirectory(path.slice(0, path.length - 1));
1156 } else {
1157 resolveFile(path);
1158 }
1159 break;
1160 }
1161 case RBDT_RESOLVE_DIRECTORY: {
1162 resolveDirectory(path);
1163 break;
1164 }
1165 case RBDT_RESOLVE_FILE: {
1166 resolveFile(path);
1167 break;
1168 }
1169 case RBDT_FILE: {
1170 if (files.has(path)) {
1171 callback();
1172 break;
1173 }
1174 this.fs.realpath(path, (err, realPath) => {
1175 if (err) return callback(err);
1176 if (realPath !== path) {
1177 resolveFiles.add(path);
1178 }
1179 if (!files.has(realPath)) {
1180 files.add(realPath);
1181 queue.push({
1182 type: RBDT_FILE_DEPENDENCIES,
1183 path: realPath
1184 });
1185 }
1186 callback();
1187 });
1188 break;
1189 }
1190 case RBDT_DIRECTORY: {
1191 if (directories.has(path)) {
1192 callback();
1193 break;
1194 }
1195 this.fs.realpath(path, (err, realPath) => {
1196 if (err) return callback(err);
1197 if (realPath !== path) {
1198 resolveFiles.add(path);
1199 }
1200 if (!directories.has(realPath)) {
1201 directories.add(realPath);
1202 queue.push({
1203 type: RBDT_DIRECTORY_DEPENDENCIES,
1204 path: realPath
1205 });
1206 }
1207 callback();
1208 });
1209 break;
1210 }
1211 case RBDT_FILE_DEPENDENCIES: {
1212 // TODO this probably doesn't work correctly with ESM dependencies
1213 /** @type {NodeModule} */
1214 const module = require.cache[path];
1215 if (module && Array.isArray(module.children)) {
1216 children: for (const child of module.children) {
1217 let childPath = child.filename;
1218 if (childPath) {
1219 queue.push({
1220 type: RBDT_FILE,
1221 path: childPath
1222 });
1223 if (childPath.endsWith(".js"))
1224 childPath = childPath.slice(0, -3);
1225 const context = dirname(this.fs, path);
1226 for (const modulePath of module.paths) {
1227 if (childPath.startsWith(modulePath)) {
1228 const request = childPath.slice(modulePath.length + 1);
1229 queue.push({
1230 type: RBDT_RESOLVE_FILE,
1231 context,
1232 path: request,
1233 expected: childPath
1234 });
1235 continue children;
1236 }
1237 }
1238 let request = relative(this.fs, context, childPath);
1239 request = request.replace(/\\/g, "/");
1240 if (!request.startsWith("../")) request = `./${request}`;
1241 queue.push({
1242 type: RBDT_RESOLVE_FILE,
1243 context,
1244 path: request,
1245 expected: child.filename
1246 });
1247 }
1248 }
1249 } else {
1250 // Unable to get dependencies from module system
1251 // This may be because of an incomplete require.cache implementation like in jest
1252 // Assume requires stay in directory and add the whole directory
1253 const directory = dirname(this.fs, path);
1254 queue.push({
1255 type: RBDT_DIRECTORY,
1256 path: directory
1257 });
1258 }
1259 process.nextTick(callback);
1260 break;
1261 }
1262 case RBDT_DIRECTORY_DEPENDENCIES: {
1263 const match = /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(
1264 path
1265 );
1266 const packagePath = match ? match[1] : path;
1267 const packageJson = join(this.fs, packagePath, "package.json");
1268 this.fs.readFile(packageJson, (err, content) => {
1269 if (err) {
1270 if (err.code === "ENOENT") {
1271 resolveMissing.add(packageJson);
1272 const parent = dirname(this.fs, packagePath);
1273 if (parent !== packagePath) {
1274 queue.push({
1275 type: RBDT_DIRECTORY_DEPENDENCIES,
1276 path: parent
1277 });
1278 }
1279 callback();
1280 return;
1281 }
1282 return callback(err);
1283 }
1284 resolveFiles.add(packageJson);
1285 let packageData;
1286 try {
1287 packageData = JSON.parse(content.toString("utf-8"));
1288 } catch (e) {
1289 return callback(e);
1290 }
1291 const depsObject = packageData.dependencies;
1292 if (typeof depsObject === "object" && depsObject) {
1293 for (const dep of Object.keys(depsObject)) {
1294 queue.push({
1295 type: RBDT_RESOLVE_DIRECTORY,
1296 context: packagePath,
1297 path: dep
1298 });
1299 }
1300 }
1301 callback();
1302 });
1303 break;
1304 }
1305 }
1306 },
1307 50
1308 );
1309 queue.drain = () => {
1310 callback(null, {
1311 files,
1312 directories,
1313 missing,
1314 resolveResults,
1315 resolveDependencies: {
1316 files: resolveFiles,
1317 directories: resolveDirectories,
1318 missing: resolveMissing
1319 }
1320 });
1321 };
1322 queue.error = err => {
1323 callback(err);
1324 callback = () => {};
1325 };
1326 let jobQueued = false;
1327 for (const dep of deps) {
1328 queue.push({
1329 type: RBDT_RESOLVE,
1330 context,
1331 path: dep
1332 });
1333 jobQueued = true;
1334 }
1335 if (!jobQueued) {
1336 // queue won't call drain when no jobs are queue
1337 queue.drain();
1338 }
1339 }
1340
1341 /**
1342 * @param {Map<string, string>} resolveResults results from resolving
1343 * @param {function(Error=, boolean=): void} callback callback with true when resolveResults resolve the same way
1344 * @returns {void}
1345 */
1346 checkResolveResultsValid(resolveResults, callback) {
1347 asyncLib.eachLimit(
1348 resolveResults,
1349 20,
1350 ([key, expectedResult], callback) => {
1351 const [type, context, path] = key.split("\n");
1352 switch (type) {
1353 case "d":
1354 resolveContext(context, path, {}, (err, result) => {
1355 if (err) return callback(err);
1356 if (result !== expectedResult) return callback(INVALID);
1357 callback();
1358 });
1359 break;
1360 case "f":
1361 resolve(context, path, {}, (err, result) => {
1362 if (err) return callback(err);
1363 if (result !== expectedResult) return callback(INVALID);
1364 callback();
1365 });
1366 break;
1367 default:
1368 callback(new Error("Unexpected type in resolve result key"));
1369 break;
1370 }
1371 },
1372 /**
1373 * @param {Error | typeof INVALID=} err error or invalid flag
1374 * @returns {void}
1375 */
1376 err => {
1377 if (err === INVALID) {
1378 return callback(null, false);
1379 }
1380 if (err) {
1381 return callback(err);
1382 }
1383 return callback(null, true);
1384 }
1385 );
1386 }
1387
1388 /**
1389 *
1390 * @param {number} startTime when processing the files has started
1391 * @param {Iterable<string>} files all files
1392 * @param {Iterable<string>} directories all directories
1393 * @param {Iterable<string>} missing all missing files or directories
1394 * @param {Object} options options object (for future extensions)
1395 * @param {boolean=} options.hash should use hash to snapshot
1396 * @param {boolean=} options.timestamp should use timestamp to snapshot
1397 * @param {function(WebpackError=, Snapshot=): void} callback callback function
1398 * @returns {void}
1399 */
1400 createSnapshot(startTime, files, directories, missing, options, callback) {
1401 /** @type {Map<string, FileSystemInfoEntry>} */
1402 const fileTimestamps = new Map();
1403 /** @type {Map<string, string>} */
1404 const fileHashes = new Map();
1405 /** @type {Map<string, TimestampAndHash | string>} */
1406 const fileTshs = new Map();
1407 /** @type {Map<string, FileSystemInfoEntry>} */
1408 const contextTimestamps = new Map();
1409 /** @type {Map<string, string>} */
1410 const contextHashes = new Map();
1411 /** @type {Map<string, TimestampAndHash | string>} */
1412 const contextTshs = new Map();
1413 /** @type {Map<string, boolean>} */
1414 const missingExistence = new Map();
1415 /** @type {Map<string, string>} */
1416 const managedItemInfo = new Map();
1417 /** @type {Set<string>} */
1418 const managedFiles = new Set();
1419 /** @type {Set<string>} */
1420 const managedContexts = new Set();
1421 /** @type {Set<string>} */
1422 const managedMissing = new Set();
1423 /** @type {Set<Snapshot>} */
1424 const children = new Set();
1425
1426 /** @type {Set<string>} */
1427 let unsharedFileTimestamps;
1428 /** @type {Set<string>} */
1429 let unsharedFileHashes;
1430 /** @type {Set<string>} */
1431 let unsharedFileTshs;
1432 /** @type {Set<string>} */
1433 let unsharedContextTimestamps;
1434 /** @type {Set<string>} */
1435 let unsharedContextHashes;
1436 /** @type {Set<string>} */
1437 let unsharedContextTshs;
1438 /** @type {Set<string>} */
1439 let unsharedMissingExistence;
1440 /** @type {Set<string>} */
1441 let unsharedManagedItemInfo;
1442
1443 /** @type {Set<string>} */
1444 const managedItems = new Set();
1445
1446 /** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
1447 const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
1448
1449 let jobs = 1;
1450 const jobDone = () => {
1451 if (--jobs === 0) {
1452 const snapshot = new Snapshot();
1453 if (startTime) snapshot.setStartTime(startTime);
1454 if (fileTimestamps.size !== 0) {
1455 snapshot.setFileTimestamps(fileTimestamps);
1456 this._fileTimestampsOptimization.storeUnsharedSnapshot(
1457 snapshot,
1458 unsharedFileTimestamps
1459 );
1460 }
1461 if (fileHashes.size !== 0) {
1462 snapshot.setFileHashes(fileHashes);
1463 this._fileHashesOptimization.storeUnsharedSnapshot(
1464 snapshot,
1465 unsharedFileHashes
1466 );
1467 }
1468 if (fileTshs.size !== 0) {
1469 snapshot.setFileTshs(fileTshs);
1470 this._fileTshsOptimization.storeUnsharedSnapshot(
1471 snapshot,
1472 unsharedFileTshs
1473 );
1474 }
1475 if (contextTimestamps.size !== 0) {
1476 snapshot.setContextTimestamps(contextTimestamps);
1477 this._contextTimestampsOptimization.storeUnsharedSnapshot(
1478 snapshot,
1479 unsharedContextTimestamps
1480 );
1481 }
1482 if (contextHashes.size !== 0) {
1483 snapshot.setContextHashes(contextHashes);
1484 this._contextHashesOptimization.storeUnsharedSnapshot(
1485 snapshot,
1486 unsharedContextHashes
1487 );
1488 }
1489 if (contextTshs.size !== 0) {
1490 snapshot.setContextTshs(contextTshs);
1491 this._contextTshsOptimization.storeUnsharedSnapshot(
1492 snapshot,
1493 unsharedContextTshs
1494 );
1495 }
1496 if (missingExistence.size !== 0) {
1497 snapshot.setMissingExistence(missingExistence);
1498 this._missingExistenceOptimization.storeUnsharedSnapshot(
1499 snapshot,
1500 unsharedMissingExistence
1501 );
1502 }
1503 if (managedItemInfo.size !== 0) {
1504 snapshot.setManagedItemInfo(managedItemInfo);
1505 this._managedItemInfoOptimization.storeUnsharedSnapshot(
1506 snapshot,
1507 unsharedManagedItemInfo
1508 );
1509 }
1510 const unsharedManagedFiles = this._managedFilesOptimization.optimize(
1511 managedFiles,
1512 undefined,
1513 children
1514 );
1515 if (managedFiles.size !== 0) {
1516 snapshot.setManagedFiles(managedFiles);
1517 this._managedFilesOptimization.storeUnsharedSnapshot(
1518 snapshot,
1519 unsharedManagedFiles
1520 );
1521 }
1522 const unsharedManagedContexts = this._managedContextsOptimization.optimize(
1523 managedContexts,
1524 undefined,
1525 children
1526 );
1527 if (managedContexts.size !== 0) {
1528 snapshot.setManagedContexts(managedContexts);
1529 this._managedContextsOptimization.storeUnsharedSnapshot(
1530 snapshot,
1531 unsharedManagedContexts
1532 );
1533 }
1534 const unsharedManagedMissing = this._managedMissingOptimization.optimize(
1535 managedMissing,
1536 undefined,
1537 children
1538 );
1539 if (managedMissing.size !== 0) {
1540 snapshot.setManagedMissing(managedMissing);
1541 this._managedMissingOptimization.storeUnsharedSnapshot(
1542 snapshot,
1543 unsharedManagedMissing
1544 );
1545 }
1546 if (children.size !== 0) {
1547 snapshot.setChildren(children);
1548 }
1549 this._snapshotCache.set(snapshot, true);
1550 this._statCreatedSnapshots++;
1551
1552 callback(null, snapshot);
1553 }
1554 };
1555 const jobError = () => {
1556 if (jobs > 0) {
1557 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
1558 jobs = -100000000;
1559 callback(null, null);
1560 }
1561 };
1562 const checkManaged = (path, managedSet) => {
1563 for (const immutablePath of this.immutablePathsWithSlash) {
1564 if (path.startsWith(immutablePath)) {
1565 managedSet.add(path);
1566 return true;
1567 }
1568 }
1569 for (const managedPath of this.managedPathsWithSlash) {
1570 if (path.startsWith(managedPath)) {
1571 const managedItem = getManagedItem(managedPath, path);
1572 if (managedItem) {
1573 managedItems.add(managedItem);
1574 managedSet.add(path);
1575 return true;
1576 }
1577 }
1578 }
1579 return false;
1580 };
1581 const captureNonManaged = (items, managedSet) => {
1582 const capturedItems = new Set();
1583 for (const path of items) {
1584 if (!checkManaged(path, managedSet)) capturedItems.add(path);
1585 }
1586 return capturedItems;
1587 };
1588 if (files) {
1589 const capturedFiles = captureNonManaged(files, managedFiles);
1590 switch (mode) {
1591 case 3:
1592 unsharedFileTshs = this._fileTshsOptimization.optimize(
1593 capturedFiles,
1594 undefined,
1595 children
1596 );
1597 for (const path of capturedFiles) {
1598 const cache = this._fileTshs.get(path);
1599 if (cache !== undefined) {
1600 fileTshs.set(path, cache);
1601 } else {
1602 jobs++;
1603 this._getFileTimestampAndHash(path, (err, entry) => {
1604 if (err) {
1605 if (this.logger) {
1606 this.logger.debug(
1607 `Error snapshotting file timestamp hash combination of ${path}: ${err}`
1608 );
1609 }
1610 jobError();
1611 } else {
1612 fileTshs.set(path, entry);
1613 jobDone();
1614 }
1615 });
1616 }
1617 }
1618 break;
1619 case 2:
1620 unsharedFileHashes = this._fileHashesOptimization.optimize(
1621 capturedFiles,
1622 undefined,
1623 children
1624 );
1625 for (const path of capturedFiles) {
1626 const cache = this._fileHashes.get(path);
1627 if (cache !== undefined) {
1628 fileHashes.set(path, cache);
1629 } else {
1630 jobs++;
1631 this.fileHashQueue.add(path, (err, entry) => {
1632 if (err) {
1633 if (this.logger) {
1634 this.logger.debug(
1635 `Error snapshotting file hash of ${path}: ${err}`
1636 );
1637 }
1638 jobError();
1639 } else {
1640 fileHashes.set(path, entry);
1641 jobDone();
1642 }
1643 });
1644 }
1645 }
1646 break;
1647 case 1:
1648 unsharedFileTimestamps = this._fileTimestampsOptimization.optimize(
1649 capturedFiles,
1650 startTime,
1651 children
1652 );
1653 for (const path of capturedFiles) {
1654 const cache = this._fileTimestamps.get(path);
1655 if (cache !== undefined) {
1656 if (cache !== "ignore") {
1657 fileTimestamps.set(path, cache);
1658 }
1659 } else {
1660 jobs++;
1661 this.fileTimestampQueue.add(path, (err, entry) => {
1662 if (err) {
1663 if (this.logger) {
1664 this.logger.debug(
1665 `Error snapshotting file timestamp of ${path}: ${err}`
1666 );
1667 }
1668 jobError();
1669 } else {
1670 fileTimestamps.set(path, entry);
1671 jobDone();
1672 }
1673 });
1674 }
1675 }
1676 break;
1677 }
1678 }
1679 if (directories) {
1680 const capturedDirectories = captureNonManaged(
1681 directories,
1682 managedContexts
1683 );
1684 switch (mode) {
1685 case 3:
1686 unsharedContextTshs = this._contextTshsOptimization.optimize(
1687 capturedDirectories,
1688 undefined,
1689 children
1690 );
1691 for (const path of capturedDirectories) {
1692 const cache = this._contextTshs.get(path);
1693 if (cache !== undefined) {
1694 contextTshs.set(path, cache);
1695 } else {
1696 jobs++;
1697 this._getContextTimestampAndHash(path, (err, entry) => {
1698 if (err) {
1699 if (this.logger) {
1700 this.logger.debug(
1701 `Error snapshotting context timestamp hash combination of ${path}: ${err}`
1702 );
1703 }
1704 jobError();
1705 } else {
1706 contextTshs.set(path, entry);
1707 jobDone();
1708 }
1709 });
1710 }
1711 }
1712 break;
1713 case 2:
1714 unsharedContextHashes = this._contextHashesOptimization.optimize(
1715 capturedDirectories,
1716 undefined,
1717 children
1718 );
1719 for (const path of capturedDirectories) {
1720 const cache = this._contextHashes.get(path);
1721 if (cache !== undefined) {
1722 contextHashes.set(path, cache);
1723 } else {
1724 jobs++;
1725 this.contextHashQueue.add(path, (err, entry) => {
1726 if (err) {
1727 if (this.logger) {
1728 this.logger.debug(
1729 `Error snapshotting context hash of ${path}: ${err}`
1730 );
1731 }
1732 jobError();
1733 } else {
1734 contextHashes.set(path, entry);
1735 jobDone();
1736 }
1737 });
1738 }
1739 }
1740 break;
1741 case 1:
1742 unsharedContextTimestamps = this._contextTimestampsOptimization.optimize(
1743 capturedDirectories,
1744 startTime,
1745 children
1746 );
1747 for (const path of capturedDirectories) {
1748 const cache = this._contextTimestamps.get(path);
1749 if (cache !== undefined) {
1750 if (cache !== "ignore") {
1751 contextTimestamps.set(path, cache);
1752 }
1753 } else {
1754 jobs++;
1755 this.contextTimestampQueue.add(path, (err, entry) => {
1756 if (err) {
1757 if (this.logger) {
1758 this.logger.debug(
1759 `Error snapshotting context timestamp of ${path}: ${err}`
1760 );
1761 }
1762 jobError();
1763 } else {
1764 contextTimestamps.set(path, entry);
1765 jobDone();
1766 }
1767 });
1768 }
1769 }
1770 break;
1771 }
1772 }
1773 if (missing) {
1774 const capturedMissing = captureNonManaged(missing, managedMissing);
1775 unsharedMissingExistence = this._missingExistenceOptimization.optimize(
1776 capturedMissing,
1777 startTime,
1778 children
1779 );
1780 for (const path of capturedMissing) {
1781 const cache = this._fileTimestamps.get(path);
1782 if (cache !== undefined) {
1783 if (cache !== "ignore") {
1784 missingExistence.set(path, toExistence(cache));
1785 }
1786 } else {
1787 jobs++;
1788 this.fileTimestampQueue.add(path, (err, entry) => {
1789 if (err) {
1790 if (this.logger) {
1791 this.logger.debug(
1792 `Error snapshotting missing timestamp of ${path}: ${err}`
1793 );
1794 }
1795 jobError();
1796 } else {
1797 missingExistence.set(path, toExistence(entry));
1798 jobDone();
1799 }
1800 });
1801 }
1802 }
1803 }
1804 unsharedManagedItemInfo = this._managedItemInfoOptimization.optimize(
1805 managedItems,
1806 undefined,
1807 children
1808 );
1809 for (const path of managedItems) {
1810 const cache = this._managedItems.get(path);
1811 if (cache !== undefined) {
1812 managedItemInfo.set(path, cache);
1813 } else {
1814 jobs++;
1815 this.managedItemQueue.add(path, (err, entry) => {
1816 if (err) {
1817 if (this.logger) {
1818 this.logger.debug(
1819 `Error snapshotting managed item ${path}: ${err}`
1820 );
1821 }
1822 jobError();
1823 } else {
1824 managedItemInfo.set(path, entry);
1825 jobDone();
1826 }
1827 });
1828 }
1829 }
1830 jobDone();
1831 }
1832
1833 /**
1834 * @param {Snapshot} snapshot1 a snapshot
1835 * @param {Snapshot} snapshot2 a snapshot
1836 * @returns {Snapshot} merged snapshot
1837 */
1838 mergeSnapshots(snapshot1, snapshot2) {
1839 const snapshot = new Snapshot();
1840 if (snapshot1.hasStartTime() && snapshot2.hasStartTime())
1841 snapshot.setStartTime(Math.min(snapshot1.startTime, snapshot2.startTime));
1842 else if (snapshot2.hasStartTime()) snapshot.startTime = snapshot2.startTime;
1843 else if (snapshot1.hasStartTime()) snapshot.startTime = snapshot1.startTime;
1844 if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
1845 snapshot.setFileTimestamps(
1846 mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
1847 );
1848 }
1849 if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
1850 snapshot.setFileHashes(
1851 mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
1852 );
1853 }
1854 if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
1855 snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
1856 }
1857 if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
1858 snapshot.setContextTimestamps(
1859 mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
1860 );
1861 }
1862 if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
1863 snapshot.setContextHashes(
1864 mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
1865 );
1866 }
1867 if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
1868 snapshot.setContextTshs(
1869 mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
1870 );
1871 }
1872 if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
1873 snapshot.setMissingExistence(
1874 mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
1875 );
1876 }
1877 if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
1878 snapshot.setManagedItemInfo(
1879 mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
1880 );
1881 }
1882 if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
1883 snapshot.setManagedFiles(
1884 mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
1885 );
1886 }
1887 if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
1888 snapshot.setManagedContexts(
1889 mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
1890 );
1891 }
1892 if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
1893 snapshot.setManagedMissing(
1894 mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
1895 );
1896 }
1897 if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
1898 snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
1899 }
1900 if (
1901 this._snapshotCache.get(snapshot1) === true &&
1902 this._snapshotCache.get(snapshot2) === true
1903 ) {
1904 this._snapshotCache.set(snapshot, true);
1905 }
1906 return snapshot;
1907 }
1908
1909 /**
1910 * @param {Snapshot} snapshot the snapshot made
1911 * @param {function(WebpackError=, boolean=): void} callback callback function
1912 * @returns {void}
1913 */
1914 checkSnapshotValid(snapshot, callback) {
1915 const cachedResult = this._snapshotCache.get(snapshot);
1916 if (cachedResult !== undefined) {
1917 this._statTestedSnapshotsCached++;
1918 if (typeof cachedResult === "boolean") {
1919 callback(null, cachedResult);
1920 } else {
1921 cachedResult.push(callback);
1922 }
1923 return;
1924 }
1925 this._statTestedSnapshotsNotCached++;
1926 this._checkSnapshotValidNoCache(snapshot, callback);
1927 }
1928
1929 /**
1930 * @param {Snapshot} snapshot the snapshot made
1931 * @param {function(WebpackError=, boolean=): void} callback callback function
1932 * @returns {void}
1933 */
1934 _checkSnapshotValidNoCache(snapshot, callback) {
1935 /** @type {number | undefined} */
1936 let startTime = undefined;
1937 if (snapshot.hasStartTime()) {
1938 startTime = snapshot.startTime;
1939 }
1940 let jobs = 1;
1941 const jobDone = () => {
1942 if (--jobs === 0) {
1943 this._snapshotCache.set(snapshot, true);
1944 callback(null, true);
1945 }
1946 };
1947 const invalid = () => {
1948 if (jobs > 0) {
1949 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
1950 jobs = -100000000;
1951 this._snapshotCache.set(snapshot, false);
1952 callback(null, false);
1953 }
1954 };
1955 const invalidWithError = (path, err) => {
1956 if (this._remainingLogs > 0) {
1957 this._log(path, `error occurred: %s`, err);
1958 }
1959 invalid();
1960 };
1961 /**
1962 * @param {string} path file path
1963 * @param {string} current current hash
1964 * @param {string} snap snapshot hash
1965 * @returns {boolean} true, if ok
1966 */
1967 const checkHash = (path, current, snap) => {
1968 if (current !== snap) {
1969 // If hash differ it's invalid
1970 if (this._remainingLogs > 0) {
1971 this._log(path, `hashes differ (%s != %s)`, current, snap);
1972 }
1973 return false;
1974 }
1975 return true;
1976 };
1977 /**
1978 * @param {string} path file path
1979 * @param {boolean} current current entry
1980 * @param {boolean} snap entry from snapshot
1981 * @returns {boolean} true, if ok
1982 */
1983 const checkExistence = (path, current, snap) => {
1984 if (!current !== !snap) {
1985 // If existence of item differs
1986 // it's invalid
1987 if (this._remainingLogs > 0) {
1988 this._log(
1989 path,
1990 current ? "it didn't exist before" : "it does no longer exist"
1991 );
1992 }
1993 return false;
1994 }
1995 return true;
1996 };
1997 /**
1998 * @param {string} path file path
1999 * @param {FileSystemInfoEntry} current current entry
2000 * @param {FileSystemInfoEntry} snap entry from snapshot
2001 * @param {boolean} log log reason
2002 * @returns {boolean} true, if ok
2003 */
2004 const checkFile = (path, current, snap, log = true) => {
2005 if (current === snap) return true;
2006 if (!current !== !snap) {
2007 // If existence of item differs
2008 // it's invalid
2009 if (log && this._remainingLogs > 0) {
2010 this._log(
2011 path,
2012 current ? "it didn't exist before" : "it does no longer exist"
2013 );
2014 }
2015 return false;
2016 }
2017 if (current) {
2018 // For existing items only
2019 if (typeof startTime === "number" && current.safeTime > startTime) {
2020 // If a change happened after starting reading the item
2021 // this may no longer be valid
2022 if (log && this._remainingLogs > 0) {
2023 this._log(
2024 path,
2025 `it may have changed (%d) after the start time of the snapshot (%d)`,
2026 current.safeTime,
2027 startTime
2028 );
2029 }
2030 return false;
2031 }
2032 if (
2033 snap.timestamp !== undefined &&
2034 current.timestamp !== snap.timestamp
2035 ) {
2036 // If we have a timestamp (it was a file or symlink) and it differs from current timestamp
2037 // it's invalid
2038 if (log && this._remainingLogs > 0) {
2039 this._log(
2040 path,
2041 `timestamps differ (%d != %d)`,
2042 current.timestamp,
2043 snap.timestamp
2044 );
2045 }
2046 return false;
2047 }
2048 if (
2049 snap.timestampHash !== undefined &&
2050 current.timestampHash !== snap.timestampHash
2051 ) {
2052 // If we have a timestampHash (it was a directory) and it differs from current timestampHash
2053 // it's invalid
2054 if (log && this._remainingLogs > 0) {
2055 this._log(
2056 path,
2057 `timestamps hashes differ (%s != %s)`,
2058 current.timestampHash,
2059 snap.timestampHash
2060 );
2061 }
2062 return false;
2063 }
2064 }
2065 return true;
2066 };
2067 if (snapshot.hasChildren()) {
2068 const childCallback = (err, result) => {
2069 if (err || !result) return invalid();
2070 else jobDone();
2071 };
2072 for (const child of snapshot.children) {
2073 const cache = this._snapshotCache.get(child);
2074 if (cache !== undefined) {
2075 this._statTestedChildrenCached++;
2076 /* istanbul ignore else */
2077 if (typeof cache === "boolean") {
2078 if (cache === false) {
2079 invalid();
2080 return;
2081 }
2082 } else {
2083 jobs++;
2084 cache.push(childCallback);
2085 }
2086 } else {
2087 this._statTestedChildrenNotCached++;
2088 jobs++;
2089 this._checkSnapshotValidNoCache(child, childCallback);
2090 }
2091 }
2092 }
2093 if (snapshot.hasFileTimestamps()) {
2094 const { fileTimestamps } = snapshot;
2095 this._statTestedEntries += fileTimestamps.size;
2096 for (const [path, ts] of fileTimestamps) {
2097 const cache = this._fileTimestamps.get(path);
2098 if (cache !== undefined) {
2099 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2100 invalid();
2101 return;
2102 }
2103 } else {
2104 jobs++;
2105 this.fileTimestampQueue.add(path, (err, entry) => {
2106 if (err) return invalidWithError(path, err);
2107 if (!checkFile(path, entry, ts)) {
2108 invalid();
2109 } else {
2110 jobDone();
2111 }
2112 });
2113 }
2114 }
2115 }
2116 const processFileHashSnapshot = (path, hash) => {
2117 const cache = this._fileHashes.get(path);
2118 if (cache !== undefined) {
2119 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2120 invalid();
2121 return;
2122 }
2123 } else {
2124 jobs++;
2125 this.fileHashQueue.add(path, (err, entry) => {
2126 if (err) return invalidWithError(path, err);
2127 if (!checkHash(path, entry, hash)) {
2128 invalid();
2129 } else {
2130 jobDone();
2131 }
2132 });
2133 }
2134 };
2135 if (snapshot.hasFileHashes()) {
2136 const { fileHashes } = snapshot;
2137 this._statTestedEntries += fileHashes.size;
2138 for (const [path, hash] of fileHashes) {
2139 processFileHashSnapshot(path, hash);
2140 }
2141 }
2142 if (snapshot.hasFileTshs()) {
2143 const { fileTshs } = snapshot;
2144 this._statTestedEntries += fileTshs.size;
2145 for (const [path, tsh] of fileTshs) {
2146 if (typeof tsh === "string") {
2147 processFileHashSnapshot(path, tsh);
2148 } else {
2149 const cache = this._fileTimestamps.get(path);
2150 if (cache !== undefined) {
2151 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2152 processFileHashSnapshot(path, tsh.hash);
2153 }
2154 } else {
2155 jobs++;
2156 this.fileTimestampQueue.add(path, (err, entry) => {
2157 if (err) return invalidWithError(path, err);
2158 if (!checkFile(path, entry, tsh, false)) {
2159 processFileHashSnapshot(path, tsh.hash);
2160 }
2161 jobDone();
2162 });
2163 }
2164 }
2165 }
2166 }
2167 if (snapshot.hasContextTimestamps()) {
2168 const { contextTimestamps } = snapshot;
2169 this._statTestedEntries += contextTimestamps.size;
2170 for (const [path, ts] of contextTimestamps) {
2171 const cache = this._contextTimestamps.get(path);
2172 if (cache !== undefined) {
2173 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2174 invalid();
2175 return;
2176 }
2177 } else {
2178 jobs++;
2179 this.contextTimestampQueue.add(path, (err, entry) => {
2180 if (err) return invalidWithError(path, err);
2181 if (!checkFile(path, entry, ts)) {
2182 invalid();
2183 } else {
2184 jobDone();
2185 }
2186 });
2187 }
2188 }
2189 }
2190 const processContextHashSnapshot = (path, hash) => {
2191 const cache = this._contextHashes.get(path);
2192 if (cache !== undefined) {
2193 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2194 invalid();
2195 return;
2196 }
2197 } else {
2198 jobs++;
2199 this.contextHashQueue.add(path, (err, entry) => {
2200 if (err) return invalidWithError(path, err);
2201 if (!checkHash(path, entry, hash)) {
2202 invalid();
2203 } else {
2204 jobDone();
2205 }
2206 });
2207 }
2208 };
2209 if (snapshot.hasContextHashes()) {
2210 const { contextHashes } = snapshot;
2211 this._statTestedEntries += contextHashes.size;
2212 for (const [path, hash] of contextHashes) {
2213 processContextHashSnapshot(path, hash);
2214 }
2215 }
2216 if (snapshot.hasContextTshs()) {
2217 const { contextTshs } = snapshot;
2218 this._statTestedEntries += contextTshs.size;
2219 for (const [path, tsh] of contextTshs) {
2220 if (typeof tsh === "string") {
2221 processContextHashSnapshot(path, tsh);
2222 } else {
2223 const cache = this._contextTimestamps.get(path);
2224 if (cache !== undefined) {
2225 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2226 processContextHashSnapshot(path, tsh.hash);
2227 }
2228 } else {
2229 jobs++;
2230 this.contextTimestampQueue.add(path, (err, entry) => {
2231 if (err) return invalidWithError(path, err);
2232 if (!checkFile(path, entry, tsh, false)) {
2233 processContextHashSnapshot(path, tsh.hash);
2234 }
2235 jobDone();
2236 });
2237 }
2238 }
2239 }
2240 }
2241 if (snapshot.hasMissingExistence()) {
2242 const { missingExistence } = snapshot;
2243 this._statTestedEntries += missingExistence.size;
2244 for (const [path, existence] of missingExistence) {
2245 const cache = this._fileTimestamps.get(path);
2246 if (cache !== undefined) {
2247 if (
2248 cache !== "ignore" &&
2249 !checkExistence(path, toExistence(cache), existence)
2250 ) {
2251 invalid();
2252 return;
2253 }
2254 } else {
2255 jobs++;
2256 this.fileTimestampQueue.add(path, (err, entry) => {
2257 if (err) return invalidWithError(path, err);
2258 if (!checkExistence(path, toExistence(entry), existence)) {
2259 invalid();
2260 } else {
2261 jobDone();
2262 }
2263 });
2264 }
2265 }
2266 }
2267 if (snapshot.hasManagedItemInfo()) {
2268 const { managedItemInfo } = snapshot;
2269 this._statTestedEntries += managedItemInfo.size;
2270 for (const [path, info] of managedItemInfo) {
2271 const cache = this._managedItems.get(path);
2272 if (cache !== undefined) {
2273 if (!checkHash(path, cache, info)) {
2274 invalid();
2275 return;
2276 }
2277 } else {
2278 jobs++;
2279 this.managedItemQueue.add(path, (err, entry) => {
2280 if (err) return invalidWithError(path, err);
2281 if (!checkHash(path, entry, info)) {
2282 invalid();
2283 } else {
2284 jobDone();
2285 }
2286 });
2287 }
2288 }
2289 }
2290 jobDone();
2291
2292 // if there was an async action
2293 // try to join multiple concurrent request for this snapshot
2294 if (jobs > 0) {
2295 const callbacks = [callback];
2296 callback = (err, result) => {
2297 for (const callback of callbacks) callback(err, result);
2298 };
2299 this._snapshotCache.set(snapshot, callbacks);
2300 }
2301 }
2302
2303 _readFileTimestamp(path, callback) {
2304 this.fs.stat(path, (err, stat) => {
2305 if (err) {
2306 if (err.code === "ENOENT") {
2307 this._fileTimestamps.set(path, null);
2308 this._cachedDeprecatedFileTimestamps = undefined;
2309 return callback(null, null);
2310 }
2311 return callback(err);
2312 }
2313
2314 let ts;
2315 if (stat.isDirectory()) {
2316 ts = {
2317 safeTime: 0,
2318 timestamp: undefined
2319 };
2320 } else {
2321 const mtime = +stat.mtime;
2322
2323 if (mtime) applyMtime(mtime);
2324
2325 ts = {
2326 safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
2327 timestamp: mtime
2328 };
2329 }
2330
2331 this._fileTimestamps.set(path, ts);
2332 this._cachedDeprecatedFileTimestamps = undefined;
2333
2334 callback(null, ts);
2335 });
2336 }
2337
2338 _readFileHash(path, callback) {
2339 this.fs.readFile(path, (err, content) => {
2340 if (err) {
2341 if (err.code === "EISDIR") {
2342 this._fileHashes.set(path, "directory");
2343 return callback(null, "directory");
2344 }
2345 if (err.code === "ENOENT") {
2346 this._fileHashes.set(path, null);
2347 return callback(null, null);
2348 }
2349 return callback(err);
2350 }
2351
2352 const hash = createHash("md4");
2353
2354 hash.update(content);
2355
2356 const digest = /** @type {string} */ (hash.digest("hex"));
2357
2358 this._fileHashes.set(path, digest);
2359
2360 callback(null, digest);
2361 });
2362 }
2363
2364 _getFileTimestampAndHash(path, callback) {
2365 const continueWithHash = hash => {
2366 const cache = this._fileTimestamps.get(path);
2367 if (cache !== undefined) {
2368 if (cache !== "ignore") {
2369 const result = {
2370 ...cache,
2371 hash
2372 };
2373 this._fileTshs.set(path, result);
2374 return callback(null, result);
2375 } else {
2376 this._fileTshs.set(path, hash);
2377 return callback(null, hash);
2378 }
2379 } else {
2380 this.fileTimestampQueue.add(path, (err, entry) => {
2381 if (err) {
2382 return callback(err);
2383 }
2384 const result = {
2385 ...entry,
2386 hash
2387 };
2388 this._fileTshs.set(path, result);
2389 return callback(null, result);
2390 });
2391 }
2392 };
2393
2394 const cache = this._fileHashes.get(path);
2395 if (cache !== undefined) {
2396 continueWithHash(cache);
2397 } else {
2398 this.fileHashQueue.add(path, (err, entry) => {
2399 if (err) {
2400 return callback(err);
2401 }
2402 continueWithHash(entry);
2403 });
2404 }
2405 }
2406
2407 _readContextTimestamp(path, callback) {
2408 this.fs.readdir(path, (err, files) => {
2409 if (err) {
2410 if (err.code === "ENOENT") {
2411 this._contextTimestamps.set(path, null);
2412 this._cachedDeprecatedContextTimestamps = undefined;
2413 return callback(null, null);
2414 }
2415 return callback(err);
2416 }
2417 files = files
2418 .map(file => file.normalize("NFC"))
2419 .filter(file => !/^\./.test(file))
2420 .sort();
2421 asyncLib.map(
2422 files,
2423 (file, callback) => {
2424 const child = join(this.fs, path, file);
2425 this.fs.stat(child, (err, stat) => {
2426 if (err) return callback(err);
2427
2428 for (const immutablePath of this.immutablePathsWithSlash) {
2429 if (path.startsWith(immutablePath)) {
2430 // ignore any immutable path for timestamping
2431 return callback(null, null);
2432 }
2433 }
2434 for (const managedPath of this.managedPathsWithSlash) {
2435 if (path.startsWith(managedPath)) {
2436 const managedItem = getManagedItem(managedPath, child);
2437 if (managedItem) {
2438 // construct timestampHash from managed info
2439 return this.managedItemQueue.add(managedItem, (err, info) => {
2440 if (err) return callback(err);
2441 return callback(null, {
2442 safeTime: 0,
2443 timestampHash: info
2444 });
2445 });
2446 }
2447 }
2448 }
2449
2450 if (stat.isFile()) {
2451 return this.getFileTimestamp(child, callback);
2452 }
2453 if (stat.isDirectory()) {
2454 this.contextTimestampQueue.increaseParallelism();
2455 this.getContextTimestamp(child, (err, tsEntry) => {
2456 this.contextTimestampQueue.decreaseParallelism();
2457 callback(err, tsEntry);
2458 });
2459 return;
2460 }
2461 callback(null, null);
2462 });
2463 },
2464 (err, tsEntries) => {
2465 if (err) return callback(err);
2466 const hash = createHash("md4");
2467
2468 for (const file of files) hash.update(file);
2469 let safeTime = 0;
2470 for (const entry of tsEntries) {
2471 if (!entry) {
2472 hash.update("n");
2473 continue;
2474 }
2475 if (entry.timestamp) {
2476 hash.update("f");
2477 hash.update(`${entry.timestamp}`);
2478 } else if (entry.timestampHash) {
2479 hash.update("d");
2480 hash.update(`${entry.timestampHash}`);
2481 }
2482 if (entry.safeTime) {
2483 safeTime = Math.max(safeTime, entry.safeTime);
2484 }
2485 }
2486
2487 const digest = /** @type {string} */ (hash.digest("hex"));
2488
2489 const result = {
2490 safeTime,
2491 timestampHash: digest
2492 };
2493
2494 this._contextTimestamps.set(path, result);
2495 this._cachedDeprecatedContextTimestamps = undefined;
2496
2497 callback(null, result);
2498 }
2499 );
2500 });
2501 }
2502
2503 _readContextHash(path, callback) {
2504 this.fs.readdir(path, (err, files) => {
2505 if (err) {
2506 if (err.code === "ENOENT") {
2507 this._contextHashes.set(path, null);
2508 return callback(null, null);
2509 }
2510 return callback(err);
2511 }
2512 files = files
2513 .map(file => file.normalize("NFC"))
2514 .filter(file => !/^\./.test(file))
2515 .sort();
2516 asyncLib.map(
2517 files,
2518 (file, callback) => {
2519 const child = join(this.fs, path, file);
2520 this.fs.stat(child, (err, stat) => {
2521 if (err) return callback(err);
2522
2523 for (const immutablePath of this.immutablePathsWithSlash) {
2524 if (path.startsWith(immutablePath)) {
2525 // ignore any immutable path for hashing
2526 return callback(null, "");
2527 }
2528 }
2529 for (const managedPath of this.managedPathsWithSlash) {
2530 if (path.startsWith(managedPath)) {
2531 const managedItem = getManagedItem(managedPath, child);
2532 if (managedItem) {
2533 // construct hash from managed info
2534 return this.managedItemQueue.add(managedItem, (err, info) => {
2535 if (err) return callback(err);
2536 callback(null, info || "");
2537 });
2538 }
2539 }
2540 }
2541
2542 if (stat.isFile()) {
2543 return this.getFileHash(child, (err, hash) => {
2544 callback(err, hash || "");
2545 });
2546 }
2547 if (stat.isDirectory()) {
2548 this.contextHashQueue.increaseParallelism();
2549 this.getContextHash(child, (err, hash) => {
2550 this.contextHashQueue.decreaseParallelism();
2551 callback(err, hash || "");
2552 });
2553 return;
2554 }
2555 callback(null, "");
2556 });
2557 },
2558 (err, fileHashes) => {
2559 if (err) return callback(err);
2560 const hash = createHash("md4");
2561
2562 for (const file of files) hash.update(file);
2563 for (const h of fileHashes) hash.update(h);
2564
2565 const digest = /** @type {string} */ (hash.digest("hex"));
2566
2567 this._contextHashes.set(path, digest);
2568
2569 callback(null, digest);
2570 }
2571 );
2572 });
2573 }
2574
2575 _getContextTimestampAndHash(path, callback) {
2576 const continueWithHash = hash => {
2577 const cache = this._contextTimestamps.get(path);
2578 if (cache !== undefined) {
2579 if (cache !== "ignore") {
2580 const result = {
2581 ...cache,
2582 hash
2583 };
2584 this._contextTshs.set(path, result);
2585 return callback(null, result);
2586 } else {
2587 this._contextTshs.set(path, hash);
2588 return callback(null, hash);
2589 }
2590 } else {
2591 this.contextTimestampQueue.add(path, (err, entry) => {
2592 if (err) {
2593 return callback(err);
2594 }
2595 const result = {
2596 ...entry,
2597 hash
2598 };
2599 this._contextTshs.set(path, result);
2600 return callback(null, result);
2601 });
2602 }
2603 };
2604
2605 const cache = this._contextHashes.get(path);
2606 if (cache !== undefined) {
2607 continueWithHash(cache);
2608 } else {
2609 this.contextHashQueue.add(path, (err, entry) => {
2610 if (err) {
2611 return callback(err);
2612 }
2613 continueWithHash(entry);
2614 });
2615 }
2616 }
2617
2618 _getManagedItemDirectoryInfo(path, callback) {
2619 this.fs.readdir(path, (err, elements) => {
2620 if (err) {
2621 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2622 return callback(null, EMPTY_SET);
2623 }
2624 return callback(err);
2625 }
2626 const set = new Set(
2627 elements.map(element => join(this.fs, path, element))
2628 );
2629 callback(null, set);
2630 });
2631 }
2632
2633 _getManagedItemInfo(path, callback) {
2634 const dir = dirname(this.fs, path);
2635 this.managedItemDirectoryQueue.add(dir, (err, elements) => {
2636 if (err) {
2637 return callback(err);
2638 }
2639 if (!elements.has(path)) {
2640 // file or directory doesn't exist
2641 this._managedItems.set(path, "missing");
2642 return callback(null, "missing");
2643 }
2644 // something exists
2645 // it may be a file or directory
2646 if (
2647 path.endsWith("node_modules") &&
2648 (path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
2649 ) {
2650 // we are only interested in existence of this special directory
2651 this._managedItems.set(path, "exists");
2652 return callback(null, "exists");
2653 }
2654
2655 // we assume it's a directory, as files shouldn't occur in managed paths
2656 const packageJsonPath = join(this.fs, path, "package.json");
2657 this.fs.readFile(packageJsonPath, (err, content) => {
2658 if (err) {
2659 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2660 // no package.json or path is not a directory
2661 this.fs.readdir(path, (err, elements) => {
2662 if (
2663 !err &&
2664 elements.length === 1 &&
2665 elements[0] === "node_modules"
2666 ) {
2667 // This is only a grouping folder e. g. used by yarn
2668 // we are only interested in existence of this special directory
2669 this._managedItems.set(path, "nested");
2670 return callback(null, "nested");
2671 }
2672 const problem = `Managed item ${path} isn't a directory or doesn't contain a package.json`;
2673 this.logger.warn(problem);
2674 return callback(new Error(problem));
2675 });
2676 return;
2677 }
2678 return callback(err);
2679 }
2680 let data;
2681 try {
2682 data = JSON.parse(content.toString("utf-8"));
2683 } catch (e) {
2684 return callback(e);
2685 }
2686 const info = `${data.name || ""}@${data.version || ""}`;
2687 this._managedItems.set(path, info);
2688 callback(null, info);
2689 });
2690 });
2691 }
2692
2693 getDeprecatedFileTimestamps() {
2694 if (this._cachedDeprecatedFileTimestamps !== undefined)
2695 return this._cachedDeprecatedFileTimestamps;
2696 const map = new Map();
2697 for (const [path, info] of this._fileTimestamps) {
2698 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
2699 }
2700 return (this._cachedDeprecatedFileTimestamps = map);
2701 }
2702
2703 getDeprecatedContextTimestamps() {
2704 if (this._cachedDeprecatedContextTimestamps !== undefined)
2705 return this._cachedDeprecatedContextTimestamps;
2706 const map = new Map();
2707 for (const [path, info] of this._contextTimestamps) {
2708 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
2709 }
2710 return (this._cachedDeprecatedContextTimestamps = map);
2711 }
2712}
2713
2714module.exports = FileSystemInfo;
2715module.exports.Snapshot = Snapshot;
2716
\No newline at end of file