UNPKG

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