UNPKG

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