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 {
1164 resolveContext,
1165 resolveEsm,
1166 resolveCjs,
1167 resolveCjsAsChild
1168 } = this._createBuildDependenciesResolvers();
1169
1170 /** @type {Set<string>} */
1171 const files = new Set();
1172 /** @type {Set<string>} */
1173 const fileSymlinks = new Set();
1174 /** @type {Set<string>} */
1175 const directories = new Set();
1176 /** @type {Set<string>} */
1177 const directorySymlinks = new Set();
1178 /** @type {Set<string>} */
1179 const missing = new Set();
1180 /** @type {Set<string>} */
1181 const resolveFiles = new Set();
1182 /** @type {Set<string>} */
1183 const resolveDirectories = new Set();
1184 /** @type {Set<string>} */
1185 const resolveMissing = new Set();
1186 /** @type {Map<string, string | false>} */
1187 const resolveResults = new Map();
1188 const invalidResolveResults = new Set();
1189 const resolverContext = {
1190 fileDependencies: resolveFiles,
1191 contextDependencies: resolveDirectories,
1192 missingDependencies: resolveMissing
1193 };
1194 const expectedToString = expected => {
1195 return expected ? ` (expected ${expected})` : "";
1196 };
1197 const jobToString = job => {
1198 switch (job.type) {
1199 case RBDT_RESOLVE_CJS:
1200 return `resolve commonjs ${job.path}${expectedToString(
1201 job.expected
1202 )}`;
1203 case RBDT_RESOLVE_ESM:
1204 return `resolve esm ${job.path}${expectedToString(job.expected)}`;
1205 case RBDT_RESOLVE_DIRECTORY:
1206 return `resolve directory ${job.path}`;
1207 case RBDT_RESOLVE_CJS_FILE:
1208 return `resolve commonjs file ${job.path}${expectedToString(
1209 job.expected
1210 )}`;
1211 case RBDT_RESOLVE_ESM_FILE:
1212 return `resolve esm file ${job.path}${expectedToString(
1213 job.expected
1214 )}`;
1215 case RBDT_DIRECTORY:
1216 return `directory ${job.path}`;
1217 case RBDT_FILE:
1218 return `file ${job.path}`;
1219 case RBDT_DIRECTORY_DEPENDENCIES:
1220 return `directory dependencies ${job.path}`;
1221 case RBDT_FILE_DEPENDENCIES:
1222 return `file dependencies ${job.path}`;
1223 }
1224 return `unknown ${job.type} ${job.path}`;
1225 };
1226 const pathToString = job => {
1227 let result = ` at ${jobToString(job)}`;
1228 job = job.issuer;
1229 while (job !== undefined) {
1230 result += `\n at ${jobToString(job)}`;
1231 job = job.issuer;
1232 }
1233 return result;
1234 };
1235 processAsyncTree(
1236 Array.from(deps, dep => ({
1237 type: RBDT_RESOLVE_CJS,
1238 context,
1239 path: dep,
1240 expected: undefined,
1241 issuer: undefined
1242 })),
1243 20,
1244 (job, push, callback) => {
1245 const { type, context, path, expected } = job;
1246 const resolveDirectory = path => {
1247 const key = `d\n${context}\n${path}`;
1248 if (resolveResults.has(key)) {
1249 return callback();
1250 }
1251 resolveResults.set(key, undefined);
1252 resolveContext(context, path, resolverContext, (err, _, result) => {
1253 if (err) {
1254 if (expected === false) {
1255 resolveResults.set(key, false);
1256 return callback();
1257 }
1258 invalidResolveResults.add(key);
1259 err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
1260 return callback(err);
1261 }
1262 const resultPath = result.path;
1263 resolveResults.set(key, resultPath);
1264 push({
1265 type: RBDT_DIRECTORY,
1266 context: undefined,
1267 path: resultPath,
1268 expected: undefined,
1269 issuer: job
1270 });
1271 callback();
1272 });
1273 };
1274 const resolveFile = (path, symbol, resolve) => {
1275 const key = `${symbol}\n${context}\n${path}`;
1276 if (resolveResults.has(key)) {
1277 return callback();
1278 }
1279 resolveResults.set(key, undefined);
1280 resolve(context, path, resolverContext, (err, _, result) => {
1281 if (typeof expected === "string") {
1282 if (!err && result && result.path === expected) {
1283 resolveResults.set(key, result.path);
1284 } else {
1285 invalidResolveResults.add(key);
1286 this.logger.warn(
1287 `Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
1288 err || (result && result.path)
1289 }' instead. Resolving dependencies are ignored for this path.\n${pathToString(
1290 job
1291 )}`
1292 );
1293 }
1294 } else {
1295 if (err) {
1296 if (expected === false) {
1297 resolveResults.set(key, false);
1298 return callback();
1299 }
1300 invalidResolveResults.add(key);
1301 err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
1302 job
1303 )}`;
1304 return callback(err);
1305 }
1306 const resultPath = result.path;
1307 resolveResults.set(key, resultPath);
1308 push({
1309 type: RBDT_FILE,
1310 context: undefined,
1311 path: resultPath,
1312 expected: undefined,
1313 issuer: job
1314 });
1315 }
1316 callback();
1317 });
1318 };
1319 switch (type) {
1320 case RBDT_RESOLVE_CJS: {
1321 const isDirectory = /[\\/]$/.test(path);
1322 if (isDirectory) {
1323 resolveDirectory(path.slice(0, path.length - 1));
1324 } else {
1325 resolveFile(path, "f", resolveCjs);
1326 }
1327 break;
1328 }
1329 case RBDT_RESOLVE_ESM: {
1330 const isDirectory = /[\\/]$/.test(path);
1331 if (isDirectory) {
1332 resolveDirectory(path.slice(0, path.length - 1));
1333 } else {
1334 resolveFile(path);
1335 }
1336 break;
1337 }
1338 case RBDT_RESOLVE_DIRECTORY: {
1339 resolveDirectory(path);
1340 break;
1341 }
1342 case RBDT_RESOLVE_CJS_FILE: {
1343 resolveFile(path, "f", resolveCjs);
1344 break;
1345 }
1346 case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
1347 resolveFile(path, "c", resolveCjsAsChild);
1348 break;
1349 }
1350 case RBDT_RESOLVE_ESM_FILE: {
1351 resolveFile(path, "e", resolveEsm);
1352 break;
1353 }
1354 case RBDT_FILE: {
1355 if (files.has(path)) {
1356 callback();
1357 break;
1358 }
1359 files.add(path);
1360 this.fs.realpath(path, (err, _realPath) => {
1361 if (err) return callback(err);
1362 const realPath = /** @type {string} */ (_realPath);
1363 if (realPath !== path) {
1364 fileSymlinks.add(path);
1365 resolveFiles.add(path);
1366 if (files.has(realPath)) return callback();
1367 files.add(realPath);
1368 }
1369 push({
1370 type: RBDT_FILE_DEPENDENCIES,
1371 context: undefined,
1372 path: realPath,
1373 expected: undefined,
1374 issuer: job
1375 });
1376 callback();
1377 });
1378 break;
1379 }
1380 case RBDT_DIRECTORY: {
1381 if (directories.has(path)) {
1382 callback();
1383 break;
1384 }
1385 directories.add(path);
1386 this.fs.realpath(path, (err, _realPath) => {
1387 if (err) return callback(err);
1388 const realPath = /** @type {string} */ (_realPath);
1389 if (realPath !== path) {
1390 directorySymlinks.add(path);
1391 resolveFiles.add(path);
1392 if (directories.has(realPath)) return callback();
1393 directories.add(realPath);
1394 }
1395 push({
1396 type: RBDT_DIRECTORY_DEPENDENCIES,
1397 context: undefined,
1398 path: realPath,
1399 expected: undefined,
1400 issuer: job
1401 });
1402 callback();
1403 });
1404 break;
1405 }
1406 case RBDT_FILE_DEPENDENCIES: {
1407 // Check for known files without dependencies
1408 if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
1409 process.nextTick(callback);
1410 break;
1411 }
1412 // Check commonjs cache for the module
1413 /** @type {NodeModule} */
1414 const module = require.cache[path];
1415 if (module && Array.isArray(module.children)) {
1416 children: for (const child of module.children) {
1417 let childPath = child.filename;
1418 if (childPath) {
1419 push({
1420 type: RBDT_FILE,
1421 context: undefined,
1422 path: childPath,
1423 expected: undefined,
1424 issuer: job
1425 });
1426 const context = dirname(this.fs, path);
1427 for (const modulePath of module.paths) {
1428 if (childPath.startsWith(modulePath)) {
1429 let subPath = childPath.slice(modulePath.length + 1);
1430 const packageMatch = /^(@[^\\/]+[\\/])[^\\/]+/.exec(
1431 subPath
1432 );
1433 if (packageMatch) {
1434 push({
1435 type: RBDT_FILE,
1436 context: undefined,
1437 path:
1438 modulePath +
1439 childPath[modulePath.length] +
1440 packageMatch[0] +
1441 childPath[modulePath.length] +
1442 "package.json",
1443 expected: false,
1444 issuer: job
1445 });
1446 }
1447 let request = subPath.replace(/\\/g, "/");
1448 if (request.endsWith(".js"))
1449 request = request.slice(0, -3);
1450 push({
1451 type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
1452 context,
1453 path: request,
1454 expected: child.filename,
1455 issuer: job
1456 });
1457 continue children;
1458 }
1459 }
1460 let request = relative(this.fs, context, childPath);
1461 if (request.endsWith(".js")) request = request.slice(0, -3);
1462 request = request.replace(/\\/g, "/");
1463 if (!request.startsWith("../")) request = `./${request}`;
1464 push({
1465 type: RBDT_RESOLVE_CJS_FILE,
1466 context,
1467 path: request,
1468 expected: child.filename,
1469 issuer: job
1470 });
1471 }
1472 }
1473 } else if (supportsEsm && /\.m?js$/.test(path)) {
1474 if (!this._warnAboutExperimentalEsmTracking) {
1475 this.logger.log(
1476 "Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
1477 "Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
1478 "As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
1479 );
1480 this._warnAboutExperimentalEsmTracking = true;
1481 }
1482 const lexer = require("es-module-lexer");
1483 lexer.init.then(() => {
1484 this.fs.readFile(path, (err, content) => {
1485 if (err) return callback(err);
1486 try {
1487 const context = dirname(this.fs, path);
1488 const source = content.toString();
1489 const [imports] = lexer.parse(source);
1490 for (const imp of imports) {
1491 try {
1492 let dependency;
1493 if (imp.d === -1) {
1494 // import ... from "..."
1495 dependency = JSON.parse(
1496 source.substring(imp.s - 1, imp.e + 1)
1497 );
1498 } else if (imp.d > -1) {
1499 // import()
1500 let expr = source.substring(imp.s, imp.e).trim();
1501 if (expr[0] === "'")
1502 expr = `"${expr
1503 .slice(1, -1)
1504 .replace(/"/g, '\\"')}"`;
1505 dependency = JSON.parse(expr);
1506 } else {
1507 // e.g. import.meta
1508 continue;
1509 }
1510 push({
1511 type: RBDT_RESOLVE_ESM_FILE,
1512 context,
1513 path: dependency,
1514 expected: undefined,
1515 issuer: job
1516 });
1517 } catch (e) {
1518 this.logger.warn(
1519 `Parsing of ${path} for build dependencies failed at 'import(${source.substring(
1520 imp.s,
1521 imp.e
1522 )})'.\n` +
1523 "Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
1524 );
1525 this.logger.debug(pathToString(job));
1526 this.logger.debug(e.stack);
1527 }
1528 }
1529 } catch (e) {
1530 this.logger.warn(
1531 `Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
1532 );
1533 this.logger.debug(pathToString(job));
1534 this.logger.debug(e.stack);
1535 }
1536 process.nextTick(callback);
1537 });
1538 }, callback);
1539 break;
1540 } else {
1541 this.logger.log(
1542 `Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
1543 );
1544 this.logger.debug(pathToString(job));
1545 }
1546 process.nextTick(callback);
1547 break;
1548 }
1549 case RBDT_DIRECTORY_DEPENDENCIES: {
1550 const match = /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(
1551 path
1552 );
1553 const packagePath = match ? match[1] : path;
1554 const packageJson = join(this.fs, packagePath, "package.json");
1555 this.fs.readFile(packageJson, (err, content) => {
1556 if (err) {
1557 if (err.code === "ENOENT") {
1558 resolveMissing.add(packageJson);
1559 const parent = dirname(this.fs, packagePath);
1560 if (parent !== packagePath) {
1561 push({
1562 type: RBDT_DIRECTORY_DEPENDENCIES,
1563 context: undefined,
1564 path: parent,
1565 expected: undefined,
1566 issuer: job
1567 });
1568 }
1569 callback();
1570 return;
1571 }
1572 return callback(err);
1573 }
1574 resolveFiles.add(packageJson);
1575 let packageData;
1576 try {
1577 packageData = JSON.parse(content.toString("utf-8"));
1578 } catch (e) {
1579 return callback(e);
1580 }
1581 const depsObject = packageData.dependencies;
1582 const optionalDepsObject = packageData.optionalDependencies;
1583 const allDeps = new Set();
1584 const optionalDeps = new Set();
1585 if (typeof depsObject === "object" && depsObject) {
1586 for (const dep of Object.keys(depsObject)) {
1587 allDeps.add(dep);
1588 }
1589 }
1590 if (
1591 typeof optionalDepsObject === "object" &&
1592 optionalDepsObject
1593 ) {
1594 for (const dep of Object.keys(optionalDepsObject)) {
1595 allDeps.add(dep);
1596 optionalDeps.add(dep);
1597 }
1598 }
1599 for (const dep of allDeps) {
1600 push({
1601 type: RBDT_RESOLVE_DIRECTORY,
1602 context: packagePath,
1603 path: dep,
1604 expected: !optionalDeps.has(dep),
1605 issuer: job
1606 });
1607 }
1608 callback();
1609 });
1610 break;
1611 }
1612 }
1613 },
1614 err => {
1615 if (err) return callback(err);
1616 for (const l of fileSymlinks) files.delete(l);
1617 for (const l of directorySymlinks) directories.delete(l);
1618 for (const k of invalidResolveResults) resolveResults.delete(k);
1619 callback(null, {
1620 files,
1621 directories,
1622 missing,
1623 resolveResults,
1624 resolveDependencies: {
1625 files: resolveFiles,
1626 directories: resolveDirectories,
1627 missing: resolveMissing
1628 }
1629 });
1630 }
1631 );
1632 }
1633
1634 /**
1635 * @param {Map<string, string | false>} resolveResults results from resolving
1636 * @param {function(Error=, boolean=): void} callback callback with true when resolveResults resolve the same way
1637 * @returns {void}
1638 */
1639 checkResolveResultsValid(resolveResults, callback) {
1640 const {
1641 resolveCjs,
1642 resolveCjsAsChild,
1643 resolveEsm,
1644 resolveContext
1645 } = this._createBuildDependenciesResolvers();
1646 asyncLib.eachLimit(
1647 resolveResults,
1648 20,
1649 ([key, expectedResult], callback) => {
1650 const [type, context, path] = key.split("\n");
1651 switch (type) {
1652 case "d":
1653 resolveContext(context, path, {}, (err, _, result) => {
1654 if (expectedResult === false)
1655 return callback(err ? undefined : INVALID);
1656 if (err) return callback(err);
1657 const resultPath = result.path;
1658 if (resultPath !== expectedResult) return callback(INVALID);
1659 callback();
1660 });
1661 break;
1662 case "f":
1663 resolveCjs(context, path, {}, (err, _, result) => {
1664 if (expectedResult === false)
1665 return callback(err ? undefined : INVALID);
1666 if (err) return callback(err);
1667 const resultPath = result.path;
1668 if (resultPath !== expectedResult) return callback(INVALID);
1669 callback();
1670 });
1671 break;
1672 case "c":
1673 resolveCjsAsChild(context, path, {}, (err, _, result) => {
1674 if (expectedResult === false)
1675 return callback(err ? undefined : INVALID);
1676 if (err) return callback(err);
1677 const resultPath = result.path;
1678 if (resultPath !== expectedResult) return callback(INVALID);
1679 callback();
1680 });
1681 break;
1682 case "e":
1683 resolveEsm(context, path, {}, (err, _, result) => {
1684 if (expectedResult === false)
1685 return callback(err ? undefined : INVALID);
1686 if (err) return callback(err);
1687 const resultPath = result.path;
1688 if (resultPath !== expectedResult) return callback(INVALID);
1689 callback();
1690 });
1691 break;
1692 default:
1693 callback(new Error("Unexpected type in resolve result key"));
1694 break;
1695 }
1696 },
1697 /**
1698 * @param {Error | typeof INVALID=} err error or invalid flag
1699 * @returns {void}
1700 */
1701 err => {
1702 if (err === INVALID) {
1703 return callback(null, false);
1704 }
1705 if (err) {
1706 return callback(err);
1707 }
1708 return callback(null, true);
1709 }
1710 );
1711 }
1712
1713 /**
1714 *
1715 * @param {number} startTime when processing the files has started
1716 * @param {Iterable<string>} files all files
1717 * @param {Iterable<string>} directories all directories
1718 * @param {Iterable<string>} missing all missing files or directories
1719 * @param {Object} options options object (for future extensions)
1720 * @param {boolean=} options.hash should use hash to snapshot
1721 * @param {boolean=} options.timestamp should use timestamp to snapshot
1722 * @param {function(WebpackError=, Snapshot=): void} callback callback function
1723 * @returns {void}
1724 */
1725 createSnapshot(startTime, files, directories, missing, options, callback) {
1726 /** @type {Map<string, FileSystemInfoEntry>} */
1727 const fileTimestamps = new Map();
1728 /** @type {Map<string, string>} */
1729 const fileHashes = new Map();
1730 /** @type {Map<string, TimestampAndHash | string>} */
1731 const fileTshs = new Map();
1732 /** @type {Map<string, FileSystemInfoEntry>} */
1733 const contextTimestamps = new Map();
1734 /** @type {Map<string, string>} */
1735 const contextHashes = new Map();
1736 /** @type {Map<string, TimestampAndHash | string>} */
1737 const contextTshs = new Map();
1738 /** @type {Map<string, boolean>} */
1739 const missingExistence = new Map();
1740 /** @type {Map<string, string>} */
1741 const managedItemInfo = new Map();
1742 /** @type {Set<string>} */
1743 const managedFiles = new Set();
1744 /** @type {Set<string>} */
1745 const managedContexts = new Set();
1746 /** @type {Set<string>} */
1747 const managedMissing = new Set();
1748 /** @type {Set<Snapshot>} */
1749 const children = new Set();
1750
1751 /** @type {Set<string>} */
1752 let unsharedFileTimestamps;
1753 /** @type {Set<string>} */
1754 let unsharedFileHashes;
1755 /** @type {Set<string>} */
1756 let unsharedFileTshs;
1757 /** @type {Set<string>} */
1758 let unsharedContextTimestamps;
1759 /** @type {Set<string>} */
1760 let unsharedContextHashes;
1761 /** @type {Set<string>} */
1762 let unsharedContextTshs;
1763 /** @type {Set<string>} */
1764 let unsharedMissingExistence;
1765 /** @type {Set<string>} */
1766 let unsharedManagedItemInfo;
1767
1768 /** @type {Set<string>} */
1769 const managedItems = new Set();
1770
1771 /** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
1772 const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
1773
1774 let jobs = 1;
1775 const jobDone = () => {
1776 if (--jobs === 0) {
1777 const snapshot = new Snapshot();
1778 if (startTime) snapshot.setStartTime(startTime);
1779 if (fileTimestamps.size !== 0) {
1780 snapshot.setFileTimestamps(fileTimestamps);
1781 this._fileTimestampsOptimization.storeUnsharedSnapshot(
1782 snapshot,
1783 unsharedFileTimestamps
1784 );
1785 }
1786 if (fileHashes.size !== 0) {
1787 snapshot.setFileHashes(fileHashes);
1788 this._fileHashesOptimization.storeUnsharedSnapshot(
1789 snapshot,
1790 unsharedFileHashes
1791 );
1792 }
1793 if (fileTshs.size !== 0) {
1794 snapshot.setFileTshs(fileTshs);
1795 this._fileTshsOptimization.storeUnsharedSnapshot(
1796 snapshot,
1797 unsharedFileTshs
1798 );
1799 }
1800 if (contextTimestamps.size !== 0) {
1801 snapshot.setContextTimestamps(contextTimestamps);
1802 this._contextTimestampsOptimization.storeUnsharedSnapshot(
1803 snapshot,
1804 unsharedContextTimestamps
1805 );
1806 }
1807 if (contextHashes.size !== 0) {
1808 snapshot.setContextHashes(contextHashes);
1809 this._contextHashesOptimization.storeUnsharedSnapshot(
1810 snapshot,
1811 unsharedContextHashes
1812 );
1813 }
1814 if (contextTshs.size !== 0) {
1815 snapshot.setContextTshs(contextTshs);
1816 this._contextTshsOptimization.storeUnsharedSnapshot(
1817 snapshot,
1818 unsharedContextTshs
1819 );
1820 }
1821 if (missingExistence.size !== 0) {
1822 snapshot.setMissingExistence(missingExistence);
1823 this._missingExistenceOptimization.storeUnsharedSnapshot(
1824 snapshot,
1825 unsharedMissingExistence
1826 );
1827 }
1828 if (managedItemInfo.size !== 0) {
1829 snapshot.setManagedItemInfo(managedItemInfo);
1830 this._managedItemInfoOptimization.storeUnsharedSnapshot(
1831 snapshot,
1832 unsharedManagedItemInfo
1833 );
1834 }
1835 const unsharedManagedFiles = this._managedFilesOptimization.optimize(
1836 managedFiles,
1837 undefined,
1838 children
1839 );
1840 if (managedFiles.size !== 0) {
1841 snapshot.setManagedFiles(managedFiles);
1842 this._managedFilesOptimization.storeUnsharedSnapshot(
1843 snapshot,
1844 unsharedManagedFiles
1845 );
1846 }
1847 const unsharedManagedContexts = this._managedContextsOptimization.optimize(
1848 managedContexts,
1849 undefined,
1850 children
1851 );
1852 if (managedContexts.size !== 0) {
1853 snapshot.setManagedContexts(managedContexts);
1854 this._managedContextsOptimization.storeUnsharedSnapshot(
1855 snapshot,
1856 unsharedManagedContexts
1857 );
1858 }
1859 const unsharedManagedMissing = this._managedMissingOptimization.optimize(
1860 managedMissing,
1861 undefined,
1862 children
1863 );
1864 if (managedMissing.size !== 0) {
1865 snapshot.setManagedMissing(managedMissing);
1866 this._managedMissingOptimization.storeUnsharedSnapshot(
1867 snapshot,
1868 unsharedManagedMissing
1869 );
1870 }
1871 if (children.size !== 0) {
1872 snapshot.setChildren(children);
1873 }
1874 this._snapshotCache.set(snapshot, true);
1875 this._statCreatedSnapshots++;
1876
1877 callback(null, snapshot);
1878 }
1879 };
1880 const jobError = () => {
1881 if (jobs > 0) {
1882 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
1883 jobs = -100000000;
1884 callback(null, null);
1885 }
1886 };
1887 const checkManaged = (path, managedSet) => {
1888 for (const immutablePath of this.immutablePathsWithSlash) {
1889 if (path.startsWith(immutablePath)) {
1890 managedSet.add(path);
1891 return true;
1892 }
1893 }
1894 for (const managedPath of this.managedPathsWithSlash) {
1895 if (path.startsWith(managedPath)) {
1896 const managedItem = getManagedItem(managedPath, path);
1897 if (managedItem) {
1898 managedItems.add(managedItem);
1899 managedSet.add(path);
1900 return true;
1901 }
1902 }
1903 }
1904 return false;
1905 };
1906 const captureNonManaged = (items, managedSet) => {
1907 const capturedItems = new Set();
1908 for (const path of items) {
1909 if (!checkManaged(path, managedSet)) capturedItems.add(path);
1910 }
1911 return capturedItems;
1912 };
1913 if (files) {
1914 const capturedFiles = captureNonManaged(files, managedFiles);
1915 switch (mode) {
1916 case 3:
1917 unsharedFileTshs = this._fileTshsOptimization.optimize(
1918 capturedFiles,
1919 undefined,
1920 children
1921 );
1922 for (const path of capturedFiles) {
1923 const cache = this._fileTshs.get(path);
1924 if (cache !== undefined) {
1925 fileTshs.set(path, cache);
1926 } else {
1927 jobs++;
1928 this._getFileTimestampAndHash(path, (err, entry) => {
1929 if (err) {
1930 if (this.logger) {
1931 this.logger.debug(
1932 `Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
1933 );
1934 }
1935 jobError();
1936 } else {
1937 fileTshs.set(path, entry);
1938 jobDone();
1939 }
1940 });
1941 }
1942 }
1943 break;
1944 case 2:
1945 unsharedFileHashes = this._fileHashesOptimization.optimize(
1946 capturedFiles,
1947 undefined,
1948 children
1949 );
1950 for (const path of capturedFiles) {
1951 const cache = this._fileHashes.get(path);
1952 if (cache !== undefined) {
1953 fileHashes.set(path, cache);
1954 } else {
1955 jobs++;
1956 this.fileHashQueue.add(path, (err, entry) => {
1957 if (err) {
1958 if (this.logger) {
1959 this.logger.debug(
1960 `Error snapshotting file hash of ${path}: ${err.stack}`
1961 );
1962 }
1963 jobError();
1964 } else {
1965 fileHashes.set(path, entry);
1966 jobDone();
1967 }
1968 });
1969 }
1970 }
1971 break;
1972 case 1:
1973 unsharedFileTimestamps = this._fileTimestampsOptimization.optimize(
1974 capturedFiles,
1975 startTime,
1976 children
1977 );
1978 for (const path of capturedFiles) {
1979 const cache = this._fileTimestamps.get(path);
1980 if (cache !== undefined) {
1981 if (cache !== "ignore") {
1982 fileTimestamps.set(path, cache);
1983 }
1984 } else {
1985 jobs++;
1986 this.fileTimestampQueue.add(path, (err, entry) => {
1987 if (err) {
1988 if (this.logger) {
1989 this.logger.debug(
1990 `Error snapshotting file timestamp of ${path}: ${err.stack}`
1991 );
1992 }
1993 jobError();
1994 } else {
1995 fileTimestamps.set(path, entry);
1996 jobDone();
1997 }
1998 });
1999 }
2000 }
2001 break;
2002 }
2003 }
2004 if (directories) {
2005 const capturedDirectories = captureNonManaged(
2006 directories,
2007 managedContexts
2008 );
2009 switch (mode) {
2010 case 3:
2011 unsharedContextTshs = this._contextTshsOptimization.optimize(
2012 capturedDirectories,
2013 undefined,
2014 children
2015 );
2016 for (const path of capturedDirectories) {
2017 const cache = this._contextTshs.get(path);
2018 if (cache !== undefined) {
2019 contextTshs.set(path, cache);
2020 } else {
2021 jobs++;
2022 this._getContextTimestampAndHash(path, (err, entry) => {
2023 if (err) {
2024 if (this.logger) {
2025 this.logger.debug(
2026 `Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
2027 );
2028 }
2029 jobError();
2030 } else {
2031 contextTshs.set(path, entry);
2032 jobDone();
2033 }
2034 });
2035 }
2036 }
2037 break;
2038 case 2:
2039 unsharedContextHashes = this._contextHashesOptimization.optimize(
2040 capturedDirectories,
2041 undefined,
2042 children
2043 );
2044 for (const path of capturedDirectories) {
2045 const cache = this._contextHashes.get(path);
2046 if (cache !== undefined) {
2047 contextHashes.set(path, cache);
2048 } else {
2049 jobs++;
2050 this.contextHashQueue.add(path, (err, entry) => {
2051 if (err) {
2052 if (this.logger) {
2053 this.logger.debug(
2054 `Error snapshotting context hash of ${path}: ${err.stack}`
2055 );
2056 }
2057 jobError();
2058 } else {
2059 contextHashes.set(path, entry);
2060 jobDone();
2061 }
2062 });
2063 }
2064 }
2065 break;
2066 case 1:
2067 unsharedContextTimestamps = this._contextTimestampsOptimization.optimize(
2068 capturedDirectories,
2069 startTime,
2070 children
2071 );
2072 for (const path of capturedDirectories) {
2073 const cache = this._contextTimestamps.get(path);
2074 if (cache !== undefined) {
2075 if (cache !== "ignore") {
2076 contextTimestamps.set(path, cache);
2077 }
2078 } else {
2079 jobs++;
2080 this.contextTimestampQueue.add(path, (err, entry) => {
2081 if (err) {
2082 if (this.logger) {
2083 this.logger.debug(
2084 `Error snapshotting context timestamp of ${path}: ${err.stack}`
2085 );
2086 }
2087 jobError();
2088 } else {
2089 contextTimestamps.set(path, entry);
2090 jobDone();
2091 }
2092 });
2093 }
2094 }
2095 break;
2096 }
2097 }
2098 if (missing) {
2099 const capturedMissing = captureNonManaged(missing, managedMissing);
2100 unsharedMissingExistence = this._missingExistenceOptimization.optimize(
2101 capturedMissing,
2102 startTime,
2103 children
2104 );
2105 for (const path of capturedMissing) {
2106 const cache = this._fileTimestamps.get(path);
2107 if (cache !== undefined) {
2108 if (cache !== "ignore") {
2109 missingExistence.set(path, toExistence(cache));
2110 }
2111 } else {
2112 jobs++;
2113 this.fileTimestampQueue.add(path, (err, entry) => {
2114 if (err) {
2115 if (this.logger) {
2116 this.logger.debug(
2117 `Error snapshotting missing timestamp of ${path}: ${err.stack}`
2118 );
2119 }
2120 jobError();
2121 } else {
2122 missingExistence.set(path, toExistence(entry));
2123 jobDone();
2124 }
2125 });
2126 }
2127 }
2128 }
2129 unsharedManagedItemInfo = this._managedItemInfoOptimization.optimize(
2130 managedItems,
2131 undefined,
2132 children
2133 );
2134 for (const path of managedItems) {
2135 const cache = this._managedItems.get(path);
2136 if (cache !== undefined) {
2137 managedItemInfo.set(path, cache);
2138 } else {
2139 jobs++;
2140 this.managedItemQueue.add(path, (err, entry) => {
2141 if (err) {
2142 if (this.logger) {
2143 this.logger.debug(
2144 `Error snapshotting managed item ${path}: ${err.stack}`
2145 );
2146 }
2147 jobError();
2148 } else {
2149 managedItemInfo.set(path, entry);
2150 jobDone();
2151 }
2152 });
2153 }
2154 }
2155 jobDone();
2156 }
2157
2158 /**
2159 * @param {Snapshot} snapshot1 a snapshot
2160 * @param {Snapshot} snapshot2 a snapshot
2161 * @returns {Snapshot} merged snapshot
2162 */
2163 mergeSnapshots(snapshot1, snapshot2) {
2164 const snapshot = new Snapshot();
2165 if (snapshot1.hasStartTime() && snapshot2.hasStartTime())
2166 snapshot.setStartTime(Math.min(snapshot1.startTime, snapshot2.startTime));
2167 else if (snapshot2.hasStartTime()) snapshot.startTime = snapshot2.startTime;
2168 else if (snapshot1.hasStartTime()) snapshot.startTime = snapshot1.startTime;
2169 if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
2170 snapshot.setFileTimestamps(
2171 mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
2172 );
2173 }
2174 if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
2175 snapshot.setFileHashes(
2176 mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
2177 );
2178 }
2179 if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
2180 snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
2181 }
2182 if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
2183 snapshot.setContextTimestamps(
2184 mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
2185 );
2186 }
2187 if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
2188 snapshot.setContextHashes(
2189 mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
2190 );
2191 }
2192 if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
2193 snapshot.setContextTshs(
2194 mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
2195 );
2196 }
2197 if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
2198 snapshot.setMissingExistence(
2199 mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
2200 );
2201 }
2202 if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
2203 snapshot.setManagedItemInfo(
2204 mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
2205 );
2206 }
2207 if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
2208 snapshot.setManagedFiles(
2209 mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
2210 );
2211 }
2212 if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
2213 snapshot.setManagedContexts(
2214 mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
2215 );
2216 }
2217 if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
2218 snapshot.setManagedMissing(
2219 mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
2220 );
2221 }
2222 if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
2223 snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
2224 }
2225 if (
2226 this._snapshotCache.get(snapshot1) === true &&
2227 this._snapshotCache.get(snapshot2) === true
2228 ) {
2229 this._snapshotCache.set(snapshot, true);
2230 }
2231 return snapshot;
2232 }
2233
2234 /**
2235 * @param {Snapshot} snapshot the snapshot made
2236 * @param {function(WebpackError=, boolean=): void} callback callback function
2237 * @returns {void}
2238 */
2239 checkSnapshotValid(snapshot, callback) {
2240 const cachedResult = this._snapshotCache.get(snapshot);
2241 if (cachedResult !== undefined) {
2242 this._statTestedSnapshotsCached++;
2243 if (typeof cachedResult === "boolean") {
2244 callback(null, cachedResult);
2245 } else {
2246 cachedResult.push(callback);
2247 }
2248 return;
2249 }
2250 this._statTestedSnapshotsNotCached++;
2251 this._checkSnapshotValidNoCache(snapshot, callback);
2252 }
2253
2254 /**
2255 * @param {Snapshot} snapshot the snapshot made
2256 * @param {function(WebpackError=, boolean=): void} callback callback function
2257 * @returns {void}
2258 */
2259 _checkSnapshotValidNoCache(snapshot, callback) {
2260 /** @type {number | undefined} */
2261 let startTime = undefined;
2262 if (snapshot.hasStartTime()) {
2263 startTime = snapshot.startTime;
2264 }
2265 let jobs = 1;
2266 const jobDone = () => {
2267 if (--jobs === 0) {
2268 this._snapshotCache.set(snapshot, true);
2269 callback(null, true);
2270 }
2271 };
2272 const invalid = () => {
2273 if (jobs > 0) {
2274 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
2275 jobs = -100000000;
2276 this._snapshotCache.set(snapshot, false);
2277 callback(null, false);
2278 }
2279 };
2280 const invalidWithError = (path, err) => {
2281 if (this._remainingLogs > 0) {
2282 this._log(path, `error occurred: %s`, err);
2283 }
2284 invalid();
2285 };
2286 /**
2287 * @param {string} path file path
2288 * @param {string} current current hash
2289 * @param {string} snap snapshot hash
2290 * @returns {boolean} true, if ok
2291 */
2292 const checkHash = (path, current, snap) => {
2293 if (current !== snap) {
2294 // If hash differ it's invalid
2295 if (this._remainingLogs > 0) {
2296 this._log(path, `hashes differ (%s != %s)`, current, snap);
2297 }
2298 return false;
2299 }
2300 return true;
2301 };
2302 /**
2303 * @param {string} path file path
2304 * @param {boolean} current current entry
2305 * @param {boolean} snap entry from snapshot
2306 * @returns {boolean} true, if ok
2307 */
2308 const checkExistence = (path, current, snap) => {
2309 if (!current !== !snap) {
2310 // If existence of item differs
2311 // it's invalid
2312 if (this._remainingLogs > 0) {
2313 this._log(
2314 path,
2315 current ? "it didn't exist before" : "it does no longer exist"
2316 );
2317 }
2318 return false;
2319 }
2320 return true;
2321 };
2322 /**
2323 * @param {string} path file path
2324 * @param {FileSystemInfoEntry} current current entry
2325 * @param {FileSystemInfoEntry} snap entry from snapshot
2326 * @param {boolean} log log reason
2327 * @returns {boolean} true, if ok
2328 */
2329 const checkFile = (path, current, snap, log = true) => {
2330 if (current === snap) return true;
2331 if (!current !== !snap) {
2332 // If existence of item differs
2333 // it's invalid
2334 if (log && this._remainingLogs > 0) {
2335 this._log(
2336 path,
2337 current ? "it didn't exist before" : "it does no longer exist"
2338 );
2339 }
2340 return false;
2341 }
2342 if (current) {
2343 // For existing items only
2344 if (typeof startTime === "number" && current.safeTime > startTime) {
2345 // If a change happened after starting reading the item
2346 // this may no longer be valid
2347 if (log && this._remainingLogs > 0) {
2348 this._log(
2349 path,
2350 `it may have changed (%d) after the start time of the snapshot (%d)`,
2351 current.safeTime,
2352 startTime
2353 );
2354 }
2355 return false;
2356 }
2357 if (
2358 snap.timestamp !== undefined &&
2359 current.timestamp !== snap.timestamp
2360 ) {
2361 // If we have a timestamp (it was a file or symlink) and it differs from current timestamp
2362 // it's invalid
2363 if (log && this._remainingLogs > 0) {
2364 this._log(
2365 path,
2366 `timestamps differ (%d != %d)`,
2367 current.timestamp,
2368 snap.timestamp
2369 );
2370 }
2371 return false;
2372 }
2373 if (
2374 snap.timestampHash !== undefined &&
2375 current.timestampHash !== snap.timestampHash
2376 ) {
2377 // If we have a timestampHash (it was a directory) and it differs from current timestampHash
2378 // it's invalid
2379 if (log && this._remainingLogs > 0) {
2380 this._log(
2381 path,
2382 `timestamps hashes differ (%s != %s)`,
2383 current.timestampHash,
2384 snap.timestampHash
2385 );
2386 }
2387 return false;
2388 }
2389 }
2390 return true;
2391 };
2392 if (snapshot.hasChildren()) {
2393 const childCallback = (err, result) => {
2394 if (err || !result) return invalid();
2395 else jobDone();
2396 };
2397 for (const child of snapshot.children) {
2398 const cache = this._snapshotCache.get(child);
2399 if (cache !== undefined) {
2400 this._statTestedChildrenCached++;
2401 /* istanbul ignore else */
2402 if (typeof cache === "boolean") {
2403 if (cache === false) {
2404 invalid();
2405 return;
2406 }
2407 } else {
2408 jobs++;
2409 cache.push(childCallback);
2410 }
2411 } else {
2412 this._statTestedChildrenNotCached++;
2413 jobs++;
2414 this._checkSnapshotValidNoCache(child, childCallback);
2415 }
2416 }
2417 }
2418 if (snapshot.hasFileTimestamps()) {
2419 const { fileTimestamps } = snapshot;
2420 this._statTestedEntries += fileTimestamps.size;
2421 for (const [path, ts] of fileTimestamps) {
2422 const cache = this._fileTimestamps.get(path);
2423 if (cache !== undefined) {
2424 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2425 invalid();
2426 return;
2427 }
2428 } else {
2429 jobs++;
2430 this.fileTimestampQueue.add(path, (err, entry) => {
2431 if (err) return invalidWithError(path, err);
2432 if (!checkFile(path, entry, ts)) {
2433 invalid();
2434 } else {
2435 jobDone();
2436 }
2437 });
2438 }
2439 }
2440 }
2441 const processFileHashSnapshot = (path, hash) => {
2442 const cache = this._fileHashes.get(path);
2443 if (cache !== undefined) {
2444 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2445 invalid();
2446 return;
2447 }
2448 } else {
2449 jobs++;
2450 this.fileHashQueue.add(path, (err, entry) => {
2451 if (err) return invalidWithError(path, err);
2452 if (!checkHash(path, entry, hash)) {
2453 invalid();
2454 } else {
2455 jobDone();
2456 }
2457 });
2458 }
2459 };
2460 if (snapshot.hasFileHashes()) {
2461 const { fileHashes } = snapshot;
2462 this._statTestedEntries += fileHashes.size;
2463 for (const [path, hash] of fileHashes) {
2464 processFileHashSnapshot(path, hash);
2465 }
2466 }
2467 if (snapshot.hasFileTshs()) {
2468 const { fileTshs } = snapshot;
2469 this._statTestedEntries += fileTshs.size;
2470 for (const [path, tsh] of fileTshs) {
2471 if (typeof tsh === "string") {
2472 processFileHashSnapshot(path, tsh);
2473 } else {
2474 const cache = this._fileTimestamps.get(path);
2475 if (cache !== undefined) {
2476 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2477 processFileHashSnapshot(path, tsh.hash);
2478 }
2479 } else {
2480 jobs++;
2481 this.fileTimestampQueue.add(path, (err, entry) => {
2482 if (err) return invalidWithError(path, err);
2483 if (!checkFile(path, entry, tsh, false)) {
2484 processFileHashSnapshot(path, tsh.hash);
2485 }
2486 jobDone();
2487 });
2488 }
2489 }
2490 }
2491 }
2492 if (snapshot.hasContextTimestamps()) {
2493 const { contextTimestamps } = snapshot;
2494 this._statTestedEntries += contextTimestamps.size;
2495 for (const [path, ts] of contextTimestamps) {
2496 const cache = this._contextTimestamps.get(path);
2497 if (cache !== undefined) {
2498 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2499 invalid();
2500 return;
2501 }
2502 } else {
2503 jobs++;
2504 this.contextTimestampQueue.add(path, (err, entry) => {
2505 if (err) return invalidWithError(path, err);
2506 if (!checkFile(path, entry, ts)) {
2507 invalid();
2508 } else {
2509 jobDone();
2510 }
2511 });
2512 }
2513 }
2514 }
2515 const processContextHashSnapshot = (path, hash) => {
2516 const cache = this._contextHashes.get(path);
2517 if (cache !== undefined) {
2518 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2519 invalid();
2520 return;
2521 }
2522 } else {
2523 jobs++;
2524 this.contextHashQueue.add(path, (err, entry) => {
2525 if (err) return invalidWithError(path, err);
2526 if (!checkHash(path, entry, hash)) {
2527 invalid();
2528 } else {
2529 jobDone();
2530 }
2531 });
2532 }
2533 };
2534 if (snapshot.hasContextHashes()) {
2535 const { contextHashes } = snapshot;
2536 this._statTestedEntries += contextHashes.size;
2537 for (const [path, hash] of contextHashes) {
2538 processContextHashSnapshot(path, hash);
2539 }
2540 }
2541 if (snapshot.hasContextTshs()) {
2542 const { contextTshs } = snapshot;
2543 this._statTestedEntries += contextTshs.size;
2544 for (const [path, tsh] of contextTshs) {
2545 if (typeof tsh === "string") {
2546 processContextHashSnapshot(path, tsh);
2547 } else {
2548 const cache = this._contextTimestamps.get(path);
2549 if (cache !== undefined) {
2550 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2551 processContextHashSnapshot(path, tsh.hash);
2552 }
2553 } else {
2554 jobs++;
2555 this.contextTimestampQueue.add(path, (err, entry) => {
2556 if (err) return invalidWithError(path, err);
2557 if (!checkFile(path, entry, tsh, false)) {
2558 processContextHashSnapshot(path, tsh.hash);
2559 }
2560 jobDone();
2561 });
2562 }
2563 }
2564 }
2565 }
2566 if (snapshot.hasMissingExistence()) {
2567 const { missingExistence } = snapshot;
2568 this._statTestedEntries += missingExistence.size;
2569 for (const [path, existence] of missingExistence) {
2570 const cache = this._fileTimestamps.get(path);
2571 if (cache !== undefined) {
2572 if (
2573 cache !== "ignore" &&
2574 !checkExistence(path, toExistence(cache), existence)
2575 ) {
2576 invalid();
2577 return;
2578 }
2579 } else {
2580 jobs++;
2581 this.fileTimestampQueue.add(path, (err, entry) => {
2582 if (err) return invalidWithError(path, err);
2583 if (!checkExistence(path, toExistence(entry), existence)) {
2584 invalid();
2585 } else {
2586 jobDone();
2587 }
2588 });
2589 }
2590 }
2591 }
2592 if (snapshot.hasManagedItemInfo()) {
2593 const { managedItemInfo } = snapshot;
2594 this._statTestedEntries += managedItemInfo.size;
2595 for (const [path, info] of managedItemInfo) {
2596 const cache = this._managedItems.get(path);
2597 if (cache !== undefined) {
2598 if (!checkHash(path, cache, info)) {
2599 invalid();
2600 return;
2601 }
2602 } else {
2603 jobs++;
2604 this.managedItemQueue.add(path, (err, entry) => {
2605 if (err) return invalidWithError(path, err);
2606 if (!checkHash(path, entry, info)) {
2607 invalid();
2608 } else {
2609 jobDone();
2610 }
2611 });
2612 }
2613 }
2614 }
2615 jobDone();
2616
2617 // if there was an async action
2618 // try to join multiple concurrent request for this snapshot
2619 if (jobs > 0) {
2620 const callbacks = [callback];
2621 callback = (err, result) => {
2622 for (const callback of callbacks) callback(err, result);
2623 };
2624 this._snapshotCache.set(snapshot, callbacks);
2625 }
2626 }
2627
2628 _readFileTimestamp(path, callback) {
2629 this.fs.stat(path, (err, stat) => {
2630 if (err) {
2631 if (err.code === "ENOENT") {
2632 this._fileTimestamps.set(path, null);
2633 this._cachedDeprecatedFileTimestamps = undefined;
2634 return callback(null, null);
2635 }
2636 return callback(err);
2637 }
2638
2639 let ts;
2640 if (stat.isDirectory()) {
2641 ts = {
2642 safeTime: 0,
2643 timestamp: undefined
2644 };
2645 } else {
2646 const mtime = +stat.mtime;
2647
2648 if (mtime) applyMtime(mtime);
2649
2650 ts = {
2651 safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
2652 timestamp: mtime
2653 };
2654 }
2655
2656 this._fileTimestamps.set(path, ts);
2657 this._cachedDeprecatedFileTimestamps = undefined;
2658
2659 callback(null, ts);
2660 });
2661 }
2662
2663 _readFileHash(path, callback) {
2664 this.fs.readFile(path, (err, content) => {
2665 if (err) {
2666 if (err.code === "EISDIR") {
2667 this._fileHashes.set(path, "directory");
2668 return callback(null, "directory");
2669 }
2670 if (err.code === "ENOENT") {
2671 this._fileHashes.set(path, null);
2672 return callback(null, null);
2673 }
2674 if (err.code === "ERR_FS_FILE_TOO_LARGE") {
2675 this.logger.warn(`Ignoring ${path} for hashing as it's very large`);
2676 this._fileHashes.set(path, "too large");
2677 return callback(null, "too large");
2678 }
2679 return callback(err);
2680 }
2681
2682 const hash = createHash("md4");
2683
2684 hash.update(content);
2685
2686 const digest = /** @type {string} */ (hash.digest("hex"));
2687
2688 this._fileHashes.set(path, digest);
2689
2690 callback(null, digest);
2691 });
2692 }
2693
2694 _getFileTimestampAndHash(path, callback) {
2695 const continueWithHash = hash => {
2696 const cache = this._fileTimestamps.get(path);
2697 if (cache !== undefined) {
2698 if (cache !== "ignore") {
2699 const result = {
2700 ...cache,
2701 hash
2702 };
2703 this._fileTshs.set(path, result);
2704 return callback(null, result);
2705 } else {
2706 this._fileTshs.set(path, hash);
2707 return callback(null, hash);
2708 }
2709 } else {
2710 this.fileTimestampQueue.add(path, (err, entry) => {
2711 if (err) {
2712 return callback(err);
2713 }
2714 const result = {
2715 ...entry,
2716 hash
2717 };
2718 this._fileTshs.set(path, result);
2719 return callback(null, result);
2720 });
2721 }
2722 };
2723
2724 const cache = this._fileHashes.get(path);
2725 if (cache !== undefined) {
2726 continueWithHash(cache);
2727 } else {
2728 this.fileHashQueue.add(path, (err, entry) => {
2729 if (err) {
2730 return callback(err);
2731 }
2732 continueWithHash(entry);
2733 });
2734 }
2735 }
2736
2737 _readContextTimestamp(path, callback) {
2738 this.fs.readdir(path, (err, _files) => {
2739 if (err) {
2740 if (err.code === "ENOENT") {
2741 this._contextTimestamps.set(path, null);
2742 this._cachedDeprecatedContextTimestamps = undefined;
2743 return callback(null, null);
2744 }
2745 return callback(err);
2746 }
2747 const files = /** @type {string[]} */ (_files)
2748 .map(file => file.normalize("NFC"))
2749 .filter(file => !/^\./.test(file))
2750 .sort();
2751 asyncLib.map(
2752 files,
2753 (file, callback) => {
2754 const child = join(this.fs, path, file);
2755 this.fs.stat(child, (err, stat) => {
2756 if (err) return callback(err);
2757
2758 for (const immutablePath of this.immutablePathsWithSlash) {
2759 if (path.startsWith(immutablePath)) {
2760 // ignore any immutable path for timestamping
2761 return callback(null, null);
2762 }
2763 }
2764 for (const managedPath of this.managedPathsWithSlash) {
2765 if (path.startsWith(managedPath)) {
2766 const managedItem = getManagedItem(managedPath, child);
2767 if (managedItem) {
2768 // construct timestampHash from managed info
2769 return this.managedItemQueue.add(managedItem, (err, info) => {
2770 if (err) return callback(err);
2771 return callback(null, {
2772 safeTime: 0,
2773 timestampHash: info
2774 });
2775 });
2776 }
2777 }
2778 }
2779
2780 if (stat.isFile()) {
2781 return this.getFileTimestamp(child, callback);
2782 }
2783 if (stat.isDirectory()) {
2784 this.contextTimestampQueue.increaseParallelism();
2785 this.getContextTimestamp(child, (err, tsEntry) => {
2786 this.contextTimestampQueue.decreaseParallelism();
2787 callback(err, tsEntry);
2788 });
2789 return;
2790 }
2791 callback(null, null);
2792 });
2793 },
2794 (err, tsEntries) => {
2795 if (err) return callback(err);
2796 const hash = createHash("md4");
2797
2798 for (const file of files) hash.update(file);
2799 let safeTime = 0;
2800 for (const entry of tsEntries) {
2801 if (!entry) {
2802 hash.update("n");
2803 continue;
2804 }
2805 if (entry.timestamp) {
2806 hash.update("f");
2807 hash.update(`${entry.timestamp}`);
2808 } else if (entry.timestampHash) {
2809 hash.update("d");
2810 hash.update(`${entry.timestampHash}`);
2811 }
2812 if (entry.safeTime) {
2813 safeTime = Math.max(safeTime, entry.safeTime);
2814 }
2815 }
2816
2817 const digest = /** @type {string} */ (hash.digest("hex"));
2818
2819 const result = {
2820 safeTime,
2821 timestampHash: digest
2822 };
2823
2824 this._contextTimestamps.set(path, result);
2825 this._cachedDeprecatedContextTimestamps = undefined;
2826
2827 callback(null, result);
2828 }
2829 );
2830 });
2831 }
2832
2833 _readContextHash(path, callback) {
2834 this.fs.readdir(path, (err, _files) => {
2835 if (err) {
2836 if (err.code === "ENOENT") {
2837 this._contextHashes.set(path, null);
2838 return callback(null, null);
2839 }
2840 return callback(err);
2841 }
2842 const files = /** @type {string[]} */ (_files)
2843 .map(file => file.normalize("NFC"))
2844 .filter(file => !/^\./.test(file))
2845 .sort();
2846 asyncLib.map(
2847 files,
2848 (file, callback) => {
2849 const child = join(this.fs, path, file);
2850 this.fs.stat(child, (err, stat) => {
2851 if (err) return callback(err);
2852
2853 for (const immutablePath of this.immutablePathsWithSlash) {
2854 if (path.startsWith(immutablePath)) {
2855 // ignore any immutable path for hashing
2856 return callback(null, "");
2857 }
2858 }
2859 for (const managedPath of this.managedPathsWithSlash) {
2860 if (path.startsWith(managedPath)) {
2861 const managedItem = getManagedItem(managedPath, child);
2862 if (managedItem) {
2863 // construct hash from managed info
2864 return this.managedItemQueue.add(managedItem, (err, info) => {
2865 if (err) return callback(err);
2866 callback(null, info || "");
2867 });
2868 }
2869 }
2870 }
2871
2872 if (stat.isFile()) {
2873 return this.getFileHash(child, (err, hash) => {
2874 callback(err, hash || "");
2875 });
2876 }
2877 if (stat.isDirectory()) {
2878 this.contextHashQueue.increaseParallelism();
2879 this.getContextHash(child, (err, hash) => {
2880 this.contextHashQueue.decreaseParallelism();
2881 callback(err, hash || "");
2882 });
2883 return;
2884 }
2885 callback(null, "");
2886 });
2887 },
2888 (err, fileHashes) => {
2889 if (err) return callback(err);
2890 const hash = createHash("md4");
2891
2892 for (const file of files) hash.update(file);
2893 for (const h of fileHashes) hash.update(h);
2894
2895 const digest = /** @type {string} */ (hash.digest("hex"));
2896
2897 this._contextHashes.set(path, digest);
2898
2899 callback(null, digest);
2900 }
2901 );
2902 });
2903 }
2904
2905 _getContextTimestampAndHash(path, callback) {
2906 const continueWithHash = hash => {
2907 const cache = this._contextTimestamps.get(path);
2908 if (cache !== undefined) {
2909 if (cache !== "ignore") {
2910 const result = {
2911 ...cache,
2912 hash
2913 };
2914 this._contextTshs.set(path, result);
2915 return callback(null, result);
2916 } else {
2917 this._contextTshs.set(path, hash);
2918 return callback(null, hash);
2919 }
2920 } else {
2921 this.contextTimestampQueue.add(path, (err, entry) => {
2922 if (err) {
2923 return callback(err);
2924 }
2925 const result = {
2926 ...entry,
2927 hash
2928 };
2929 this._contextTshs.set(path, result);
2930 return callback(null, result);
2931 });
2932 }
2933 };
2934
2935 const cache = this._contextHashes.get(path);
2936 if (cache !== undefined) {
2937 continueWithHash(cache);
2938 } else {
2939 this.contextHashQueue.add(path, (err, entry) => {
2940 if (err) {
2941 return callback(err);
2942 }
2943 continueWithHash(entry);
2944 });
2945 }
2946 }
2947
2948 _getManagedItemDirectoryInfo(path, callback) {
2949 this.fs.readdir(path, (err, elements) => {
2950 if (err) {
2951 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2952 return callback(null, EMPTY_SET);
2953 }
2954 return callback(err);
2955 }
2956 const set = new Set(
2957 /** @type {string[]} */ (elements).map(element =>
2958 join(this.fs, path, element)
2959 )
2960 );
2961 callback(null, set);
2962 });
2963 }
2964
2965 _getManagedItemInfo(path, callback) {
2966 const dir = dirname(this.fs, path);
2967 this.managedItemDirectoryQueue.add(dir, (err, elements) => {
2968 if (err) {
2969 return callback(err);
2970 }
2971 if (!elements.has(path)) {
2972 // file or directory doesn't exist
2973 this._managedItems.set(path, "missing");
2974 return callback(null, "missing");
2975 }
2976 // something exists
2977 // it may be a file or directory
2978 if (
2979 path.endsWith("node_modules") &&
2980 (path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
2981 ) {
2982 // we are only interested in existence of this special directory
2983 this._managedItems.set(path, "exists");
2984 return callback(null, "exists");
2985 }
2986
2987 // we assume it's a directory, as files shouldn't occur in managed paths
2988 const packageJsonPath = join(this.fs, path, "package.json");
2989 this.fs.readFile(packageJsonPath, (err, content) => {
2990 if (err) {
2991 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2992 // no package.json or path is not a directory
2993 this.fs.readdir(path, (err, elements) => {
2994 if (
2995 !err &&
2996 elements.length === 1 &&
2997 elements[0] === "node_modules"
2998 ) {
2999 // This is only a grouping folder e. g. used by yarn
3000 // we are only interested in existence of this special directory
3001 this._managedItems.set(path, "nested");
3002 return callback(null, "nested");
3003 }
3004 const problem = `Managed item ${path} isn't a directory or doesn't contain a package.json`;
3005 this.logger.warn(problem);
3006 return callback(new Error(problem));
3007 });
3008 return;
3009 }
3010 return callback(err);
3011 }
3012 let data;
3013 try {
3014 data = JSON.parse(content.toString("utf-8"));
3015 } catch (e) {
3016 return callback(e);
3017 }
3018 const info = `${data.name || ""}@${data.version || ""}`;
3019 this._managedItems.set(path, info);
3020 callback(null, info);
3021 });
3022 });
3023 }
3024
3025 getDeprecatedFileTimestamps() {
3026 if (this._cachedDeprecatedFileTimestamps !== undefined)
3027 return this._cachedDeprecatedFileTimestamps;
3028 const map = new Map();
3029 for (const [path, info] of this._fileTimestamps) {
3030 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
3031 }
3032 return (this._cachedDeprecatedFileTimestamps = map);
3033 }
3034
3035 getDeprecatedContextTimestamps() {
3036 if (this._cachedDeprecatedContextTimestamps !== undefined)
3037 return this._cachedDeprecatedContextTimestamps;
3038 const map = new Map();
3039 for (const [path, info] of this._contextTimestamps) {
3040 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
3041 }
3042 return (this._cachedDeprecatedContextTimestamps = map);
3043 }
3044}
3045
3046module.exports = FileSystemInfo;
3047module.exports.Snapshot = Snapshot;