source: frontend/node_modules/webpack/lib/FileSystemInfo.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 127.0 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const nodeModule = require("module");
9const { isAbsolute } = require("path");
10const { create: createResolver } = require("enhanced-resolve");
11const asyncLib = require("neo-async");
12const { DEFAULTS } = require("./config/defaults");
13const AsyncQueue = require("./util/AsyncQueue");
14const StackedCacheMap = require("./util/StackedCacheMap");
15const createHash = require("./util/createHash");
16const { dirname, join, lstatReadlinkAbsolute, relative } = require("./util/fs");
17const makeSerializable = require("./util/makeSerializable");
18const memoize = require("./util/memoize");
19const processAsyncTree = require("./util/processAsyncTree");
20
21/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
22/** @typedef {import("enhanced-resolve").ResolveFunctionAsync} ResolveFunctionAsync */
23/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
24/** @typedef {import("./logging/Logger").Logger} Logger */
25/** @typedef {import("./errors/WebpackError")} WebpackError */
26/** @typedef {import("./util/fs").JsonObject} JsonObject */
27/** @typedef {import("./util/fs").IStats} IStats */
28/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
29/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
30/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
31/**
32 * Defines the processor callback type used by this module.
33 * @template T
34 * @typedef {import("./util/AsyncQueue").Callback<T>} ProcessorCallback
35 */
36/**
37 * Defines the processor type used by this module.
38 * @template T, R
39 * @typedef {import("./util/AsyncQueue").Processor<T, R>} Processor
40 */
41
42const supportsEsm = Number(process.versions.modules) >= 83;
43
44/** @type {Set<string>} */
45const builtinModules = new Set(nodeModule.builtinModules);
46
47let FS_ACCURACY = 2000;
48
49/** @type {Set<string>} */
50const EMPTY_SET = new Set();
51
52const RBDT_RESOLVE_INITIAL = 0;
53const RBDT_RESOLVE_FILE = 1;
54const RBDT_RESOLVE_DIRECTORY = 2;
55const RBDT_RESOLVE_CJS_FILE = 3;
56const RBDT_RESOLVE_CJS_FILE_AS_CHILD = 4;
57const RBDT_RESOLVE_ESM_FILE = 5;
58const RBDT_DIRECTORY = 6;
59const RBDT_FILE = 7;
60const RBDT_DIRECTORY_DEPENDENCIES = 8;
61const RBDT_FILE_DEPENDENCIES = 9;
62
63/** @typedef {RBDT_RESOLVE_INITIAL | RBDT_RESOLVE_FILE | RBDT_RESOLVE_DIRECTORY | RBDT_RESOLVE_CJS_FILE | RBDT_RESOLVE_CJS_FILE_AS_CHILD | RBDT_RESOLVE_ESM_FILE | RBDT_DIRECTORY | RBDT_FILE | RBDT_DIRECTORY_DEPENDENCIES | RBDT_FILE_DEPENDENCIES} JobType */
64
65const INVALID = Symbol("invalid");
66
67// eslint-disable-next-line jsdoc/ts-no-empty-object-type
68/** @typedef {{ }} ExistenceOnlyTimeEntry */
69
70/**
71 * Defines the file system info entry type used by this module.
72 * @typedef {object} FileSystemInfoEntry
73 * @property {number} safeTime
74 * @property {number=} timestamp
75 */
76
77/**
78 * Defines the resolved context file system info entry type used by this module.
79 * @typedef {object} ResolvedContextFileSystemInfoEntry
80 * @property {number} safeTime
81 * @property {string=} timestampHash
82 */
83
84/** @typedef {Set<string>} Symlinks */
85
86/**
87 * Defines the context file system info entry type used by this module.
88 * @typedef {object} ContextFileSystemInfoEntry
89 * @property {number} safeTime
90 * @property {string=} timestampHash
91 * @property {ResolvedContextFileSystemInfoEntry=} resolved
92 * @property {Symlinks=} symlinks
93 */
94
95/**
96 * Defines the timestamp and hash type used by this module.
97 * @typedef {object} TimestampAndHash
98 * @property {number} safeTime
99 * @property {number=} timestamp
100 * @property {string} hash
101 */
102
103/**
104 * Defines the resolved context timestamp and hash type used by this module.
105 * @typedef {object} ResolvedContextTimestampAndHash
106 * @property {number} safeTime
107 * @property {string=} timestampHash
108 * @property {string} hash
109 */
110
111/**
112 * Defines the context timestamp and hash type used by this module.
113 * @typedef {object} ContextTimestampAndHash
114 * @property {number} safeTime
115 * @property {string=} timestampHash
116 * @property {string} hash
117 * @property {ResolvedContextTimestampAndHash=} resolved
118 * @property {Symlinks=} symlinks
119 */
120
121/**
122 * Defines the context hash type used by this module.
123 * @typedef {object} ContextHash
124 * @property {string} hash
125 * @property {string=} resolved
126 * @property {Symlinks=} symlinks
127 */
128
129/** @typedef {Set<string>} SnapshotContent */
130
131/**
132 * Defines the snapshot optimization entry type used by this module.
133 * @typedef {object} SnapshotOptimizationEntry
134 * @property {Snapshot} snapshot
135 * @property {number} shared
136 * @property {SnapshotContent | undefined} snapshotContent
137 * @property {Set<SnapshotOptimizationEntry> | undefined} children
138 */
139
140/** @typedef {Map<string, string | false | undefined>} ResolveResults */
141
142/** @typedef {Set<string>} Files */
143/** @typedef {Set<string>} Directories */
144/** @typedef {Set<string>} Missing */
145
146/**
147 * Defines the resolve dependencies type used by this module.
148 * @typedef {object} ResolveDependencies
149 * @property {Files} files list of files
150 * @property {Directories} directories list of directories
151 * @property {Missing} missing list of missing entries
152 */
153
154/**
155 * Defines the resolve build dependencies result type used by this module.
156 * @typedef {object} ResolveBuildDependenciesResult
157 * @property {Files} files list of files
158 * @property {Directories} directories list of directories
159 * @property {Missing} missing list of missing entries
160 * @property {ResolveResults} resolveResults stored resolve results
161 * @property {ResolveDependencies} resolveDependencies dependencies of the resolving
162 */
163
164/**
165 * Defines the snapshot options type used by this module.
166 * @typedef {object} SnapshotOptions
167 * @property {boolean=} hash should use hash to snapshot
168 * @property {boolean=} timestamp should use timestamp to snapshot
169 */
170
171const DONE_ITERATOR_RESULT = new Set().keys().next();
172
173// cspell:word tshs
174// Tsh = Timestamp + Hash
175// Tshs = Timestamp + Hash combinations
176
177class SnapshotIterator {
178 /**
179 * Creates an instance of SnapshotIterator.
180 * @param {() => IteratorResult<string>} next next
181 */
182 constructor(next) {
183 this.next = next;
184 }
185}
186
187/**
188 * Defines the get maps function type used by this module.
189 * @template T
190 * @typedef {(snapshot: Snapshot) => T[]} GetMapsFunction
191 */
192
193/**
194 * Represents SnapshotIterable.
195 * @template T
196 */
197class SnapshotIterable {
198 /**
199 * Creates an instance of SnapshotIterable.
200 * @param {Snapshot} snapshot snapshot
201 * @param {GetMapsFunction<T>} getMaps get maps function
202 */
203 constructor(snapshot, getMaps) {
204 this.snapshot = snapshot;
205 this.getMaps = getMaps;
206 }
207
208 [Symbol.iterator]() {
209 let state = 0;
210 /** @type {IterableIterator<string>} */
211 let it;
212 /** @type {GetMapsFunction<T>} */
213 let getMaps;
214 /** @type {T[]} */
215 let maps;
216 /** @type {Snapshot} */
217 let snapshot;
218 /** @type {Snapshot[] | undefined} */
219 let queue;
220 return new SnapshotIterator(() => {
221 for (;;) {
222 switch (state) {
223 case 0:
224 snapshot = this.snapshot;
225 getMaps = this.getMaps;
226 maps = getMaps(snapshot);
227 state = 1;
228 /* falls through */
229 case 1:
230 if (maps.length > 0) {
231 const map = maps.pop();
232 if (map !== undefined) {
233 it =
234 /** @type {Set<string> | Map<string, EXPECTED_ANY>} */
235 (map).keys();
236 state = 2;
237 } else {
238 break;
239 }
240 } else {
241 state = 3;
242 break;
243 }
244 /* falls through */
245 case 2: {
246 const result = it.next();
247 if (!result.done) return result;
248 state = 1;
249 break;
250 }
251 case 3: {
252 const children = snapshot.children;
253 if (children !== undefined) {
254 if (children.size === 1) {
255 // shortcut for a single child
256 // avoids allocation of queue
257 for (const child of children) snapshot = child;
258 maps = getMaps(snapshot);
259 state = 1;
260 break;
261 }
262 if (queue === undefined) queue = [];
263 for (const child of children) {
264 queue.push(child);
265 }
266 }
267 if (queue !== undefined && queue.length > 0) {
268 snapshot = /** @type {Snapshot} */ (queue.pop());
269 maps = getMaps(snapshot);
270 state = 1;
271 break;
272 } else {
273 state = 4;
274 }
275 }
276 /* falls through */
277 case 4:
278 return DONE_ITERATOR_RESULT;
279 }
280 }
281 });
282 }
283}
284
285/** @typedef {Map<string, FileSystemInfoEntry | null>} FileTimestamps */
286/** @typedef {Map<string, string | null>} FileHashes */
287/** @typedef {Map<string, TimestampAndHash | string | null>} FileTshs */
288/** @typedef {Map<string, ResolvedContextFileSystemInfoEntry | null>} ContextTimestamps */
289/** @typedef {Map<string, string | null>} ContextHashes */
290/** @typedef {Map<string, ResolvedContextTimestampAndHash | null>} ContextTshs */
291/** @typedef {Map<string, boolean>} MissingExistence */
292/** @typedef {Map<string, string>} ManagedItemInfo */
293/** @typedef {Set<string>} ManagedFiles */
294/** @typedef {Set<string>} ManagedContexts */
295/** @typedef {Set<string>} ManagedMissing */
296/** @typedef {Set<Snapshot>} Children */
297
298class Snapshot {
299 constructor() {
300 this._flags = 0;
301 /** @type {Iterable<string> | undefined} */
302 this._cachedFileIterable = undefined;
303 /** @type {Iterable<string> | undefined} */
304 this._cachedContextIterable = undefined;
305 /** @type {Iterable<string> | undefined} */
306 this._cachedMissingIterable = undefined;
307 /** @type {number | undefined} */
308 this.startTime = undefined;
309 /** @type {FileTimestamps | undefined} */
310 this.fileTimestamps = undefined;
311 /** @type {FileHashes | undefined} */
312 this.fileHashes = undefined;
313 /** @type {FileTshs | undefined} */
314 this.fileTshs = undefined;
315 /** @type {ContextTimestamps | undefined} */
316 this.contextTimestamps = undefined;
317 /** @type {ContextHashes | undefined} */
318 this.contextHashes = undefined;
319 /** @type {ContextTshs | undefined} */
320 this.contextTshs = undefined;
321 /** @type {MissingExistence | undefined} */
322 this.missingExistence = undefined;
323 /** @type {ManagedItemInfo | undefined} */
324 this.managedItemInfo = undefined;
325 /** @type {ManagedFiles | undefined} */
326 this.managedFiles = undefined;
327 /** @type {ManagedContexts | undefined} */
328 this.managedContexts = undefined;
329 /** @type {ManagedMissing | undefined} */
330 this.managedMissing = undefined;
331 /** @type {Children | undefined} */
332 this.children = undefined;
333 }
334
335 hasStartTime() {
336 return (this._flags & 1) !== 0;
337 }
338
339 /**
340 * Updates start time using the provided value.
341 * @param {number} value start value
342 */
343 setStartTime(value) {
344 this._flags |= 1;
345 this.startTime = value;
346 }
347
348 /**
349 * Sets merged start time.
350 * @param {number | undefined} value value
351 * @param {Snapshot} snapshot snapshot
352 */
353 setMergedStartTime(value, snapshot) {
354 if (value) {
355 if (snapshot.hasStartTime()) {
356 this.setStartTime(
357 Math.min(
358 value,
359 /** @type {NonNullable<Snapshot["startTime"]>} */
360 (snapshot.startTime)
361 )
362 );
363 } else {
364 this.setStartTime(value);
365 }
366 } else if (snapshot.hasStartTime()) {
367 this.setStartTime(
368 /** @type {NonNullable<Snapshot["startTime"]>} */
369 (snapshot.startTime)
370 );
371 }
372 }
373
374 hasFileTimestamps() {
375 return (this._flags & 2) !== 0;
376 }
377
378 /**
379 * Sets file timestamps.
380 * @param {FileTimestamps} value file timestamps
381 */
382 setFileTimestamps(value) {
383 this._flags |= 2;
384 this.fileTimestamps = value;
385 }
386
387 hasFileHashes() {
388 return (this._flags & 4) !== 0;
389 }
390
391 /**
392 * Updates file hashes using the provided value.
393 * @param {FileHashes} value file hashes
394 */
395 setFileHashes(value) {
396 this._flags |= 4;
397 this.fileHashes = value;
398 }
399
400 hasFileTshs() {
401 return (this._flags & 8) !== 0;
402 }
403
404 /**
405 * Updates file tshs using the provided value.
406 * @param {FileTshs} value file tshs
407 */
408 setFileTshs(value) {
409 this._flags |= 8;
410 this.fileTshs = value;
411 }
412
413 hasContextTimestamps() {
414 return (this._flags & 0x10) !== 0;
415 }
416
417 /**
418 * Sets context timestamps.
419 * @param {ContextTimestamps} value context timestamps
420 */
421 setContextTimestamps(value) {
422 this._flags |= 0x10;
423 this.contextTimestamps = value;
424 }
425
426 hasContextHashes() {
427 return (this._flags & 0x20) !== 0;
428 }
429
430 /**
431 * Sets context hashes.
432 * @param {ContextHashes} value context hashes
433 */
434 setContextHashes(value) {
435 this._flags |= 0x20;
436 this.contextHashes = value;
437 }
438
439 hasContextTshs() {
440 return (this._flags & 0x40) !== 0;
441 }
442
443 /**
444 * Updates context tshs using the provided value.
445 * @param {ContextTshs} value context tshs
446 */
447 setContextTshs(value) {
448 this._flags |= 0x40;
449 this.contextTshs = value;
450 }
451
452 hasMissingExistence() {
453 return (this._flags & 0x80) !== 0;
454 }
455
456 /**
457 * Sets missing existence.
458 * @param {MissingExistence} value context tshs
459 */
460 setMissingExistence(value) {
461 this._flags |= 0x80;
462 this.missingExistence = value;
463 }
464
465 hasManagedItemInfo() {
466 return (this._flags & 0x100) !== 0;
467 }
468
469 /**
470 * Sets managed item info.
471 * @param {ManagedItemInfo} value managed item info
472 */
473 setManagedItemInfo(value) {
474 this._flags |= 0x100;
475 this.managedItemInfo = value;
476 }
477
478 hasManagedFiles() {
479 return (this._flags & 0x200) !== 0;
480 }
481
482 /**
483 * Sets managed files.
484 * @param {ManagedFiles} value managed files
485 */
486 setManagedFiles(value) {
487 this._flags |= 0x200;
488 this.managedFiles = value;
489 }
490
491 hasManagedContexts() {
492 return (this._flags & 0x400) !== 0;
493 }
494
495 /**
496 * Sets managed contexts.
497 * @param {ManagedContexts} value managed contexts
498 */
499 setManagedContexts(value) {
500 this._flags |= 0x400;
501 this.managedContexts = value;
502 }
503
504 hasManagedMissing() {
505 return (this._flags & 0x800) !== 0;
506 }
507
508 /**
509 * Sets managed missing.
510 * @param {ManagedMissing} value managed missing
511 */
512 setManagedMissing(value) {
513 this._flags |= 0x800;
514 this.managedMissing = value;
515 }
516
517 hasChildren() {
518 return (this._flags & 0x1000) !== 0;
519 }
520
521 /**
522 * Updates children using the provided value.
523 * @param {Children} value children
524 */
525 setChildren(value) {
526 this._flags |= 0x1000;
527 this.children = value;
528 }
529
530 /**
531 * Adds the provided child to the snapshot.
532 * @param {Snapshot} child children
533 */
534 addChild(child) {
535 if (!this.hasChildren()) {
536 this.setChildren(new Set());
537 }
538 /** @type {Children} */
539 (this.children).add(child);
540 }
541
542 /**
543 * Serializes this instance into the provided serializer context.
544 * @param {ObjectSerializerContext} context context
545 */
546 serialize({ write }) {
547 write(this._flags);
548 if (this.hasStartTime()) write(this.startTime);
549 if (this.hasFileTimestamps()) write(this.fileTimestamps);
550 if (this.hasFileHashes()) write(this.fileHashes);
551 if (this.hasFileTshs()) write(this.fileTshs);
552 if (this.hasContextTimestamps()) write(this.contextTimestamps);
553 if (this.hasContextHashes()) write(this.contextHashes);
554 if (this.hasContextTshs()) write(this.contextTshs);
555 if (this.hasMissingExistence()) write(this.missingExistence);
556 if (this.hasManagedItemInfo()) write(this.managedItemInfo);
557 if (this.hasManagedFiles()) write(this.managedFiles);
558 if (this.hasManagedContexts()) write(this.managedContexts);
559 if (this.hasManagedMissing()) write(this.managedMissing);
560 if (this.hasChildren()) write(this.children);
561 }
562
563 /**
564 * Restores this instance from the provided deserializer context.
565 * @param {ObjectDeserializerContext} context context
566 */
567 deserialize({ read }) {
568 this._flags = read();
569 if (this.hasStartTime()) this.startTime = read();
570 if (this.hasFileTimestamps()) this.fileTimestamps = read();
571 if (this.hasFileHashes()) this.fileHashes = read();
572 if (this.hasFileTshs()) this.fileTshs = read();
573 if (this.hasContextTimestamps()) this.contextTimestamps = read();
574 if (this.hasContextHashes()) this.contextHashes = read();
575 if (this.hasContextTshs()) this.contextTshs = read();
576 if (this.hasMissingExistence()) this.missingExistence = read();
577 if (this.hasManagedItemInfo()) this.managedItemInfo = read();
578 if (this.hasManagedFiles()) this.managedFiles = read();
579 if (this.hasManagedContexts()) this.managedContexts = read();
580 if (this.hasManagedMissing()) this.managedMissing = read();
581 if (this.hasChildren()) this.children = read();
582 }
583
584 /**
585 * Creates an iterable from the provided get map.
586 * @template T
587 * @param {GetMapsFunction<T>} getMaps first
588 * @returns {SnapshotIterable<T>} iterable
589 */
590 _createIterable(getMaps) {
591 return new SnapshotIterable(this, getMaps);
592 }
593
594 /**
595 * Gets file iterable.
596 * @returns {Iterable<string>} iterable
597 */
598 getFileIterable() {
599 if (this._cachedFileIterable === undefined) {
600 this._cachedFileIterable = this._createIterable((s) => [
601 s.fileTimestamps,
602 s.fileHashes,
603 s.fileTshs,
604 s.managedFiles
605 ]);
606 }
607 return this._cachedFileIterable;
608 }
609
610 /**
611 * Gets context iterable.
612 * @returns {Iterable<string>} iterable
613 */
614 getContextIterable() {
615 if (this._cachedContextIterable === undefined) {
616 this._cachedContextIterable = this._createIterable((s) => [
617 s.contextTimestamps,
618 s.contextHashes,
619 s.contextTshs,
620 s.managedContexts
621 ]);
622 }
623 return this._cachedContextIterable;
624 }
625
626 /**
627 * Gets missing iterable.
628 * @returns {Iterable<string>} iterable
629 */
630 getMissingIterable() {
631 if (this._cachedMissingIterable === undefined) {
632 this._cachedMissingIterable = this._createIterable((s) => [
633 s.missingExistence,
634 s.managedMissing
635 ]);
636 }
637 return this._cachedMissingIterable;
638 }
639}
640
641makeSerializable(Snapshot, "webpack/lib/FileSystemInfo", "Snapshot");
642
643const MIN_COMMON_SNAPSHOT_SIZE = 3;
644
645/**
646 * Defines the snapshot optimization value type used by this module.
647 * @template U, T
648 * @typedef {U extends true ? Set<string> : Map<string, T>} SnapshotOptimizationValue
649 */
650
651/**
652 * Represents SnapshotOptimization.
653 * @template T
654 * @template {boolean} [U=false]
655 */
656class SnapshotOptimization {
657 /**
658 * Creates an instance of SnapshotOptimization.
659 * @param {(snapshot: Snapshot) => boolean} has has value
660 * @param {(snapshot: Snapshot) => SnapshotOptimizationValue<U, T> | undefined} get get value
661 * @param {(snapshot: Snapshot, value: SnapshotOptimizationValue<U, T>) => void} set set value
662 * @param {boolean=} useStartTime use the start time of snapshots
663 * @param {U=} isSet value is an Set instead of a Map
664 */
665 constructor(
666 has,
667 get,
668 set,
669 useStartTime = true,
670 isSet = /** @type {U} */ (false)
671 ) {
672 this._has = has;
673 this._get = get;
674 this._set = set;
675 this._useStartTime = useStartTime;
676 /** @type {U} */
677 this._isSet = isSet;
678 /** @type {Map<string, SnapshotOptimizationEntry>} */
679 this._map = new Map();
680 this._statItemsShared = 0;
681 this._statItemsUnshared = 0;
682 this._statSharedSnapshots = 0;
683 this._statReusedSharedSnapshots = 0;
684 }
685
686 getStatisticMessage() {
687 const total = this._statItemsShared + this._statItemsUnshared;
688 if (total === 0) return;
689 return `${
690 this._statItemsShared && Math.round((this._statItemsShared * 100) / total)
691 }% (${this._statItemsShared}/${total}) entries shared via ${
692 this._statSharedSnapshots
693 } shared snapshots (${
694 this._statReusedSharedSnapshots + this._statSharedSnapshots
695 } times referenced)`;
696 }
697
698 clear() {
699 this._map.clear();
700 this._statItemsShared = 0;
701 this._statItemsUnshared = 0;
702 this._statSharedSnapshots = 0;
703 this._statReusedSharedSnapshots = 0;
704 }
705
706 /**
707 * Processes the provided new snapshot.
708 * @param {Snapshot} newSnapshot snapshot
709 * @param {Set<string>} capturedFiles files to snapshot/share
710 * @returns {void}
711 */
712 optimize(newSnapshot, capturedFiles) {
713 if (capturedFiles.size === 0) {
714 return;
715 }
716 /**
717 * Increase shared and store optimization entry.
718 * @param {SnapshotOptimizationEntry} entry optimization entry
719 * @returns {void}
720 */
721 const increaseSharedAndStoreOptimizationEntry = (entry) => {
722 if (entry.children !== undefined) {
723 for (const child of entry.children) {
724 increaseSharedAndStoreOptimizationEntry(child);
725 }
726 }
727 entry.shared++;
728 storeOptimizationEntry(entry);
729 };
730 /**
731 * Stores optimization entry.
732 * @param {SnapshotOptimizationEntry} entry optimization entry
733 * @returns {void}
734 */
735 const storeOptimizationEntry = (entry) => {
736 for (const path of /** @type {SnapshotContent} */ (
737 entry.snapshotContent
738 )) {
739 const old =
740 /** @type {SnapshotOptimizationEntry} */
741 (this._map.get(path));
742 if (old.shared < entry.shared) {
743 this._map.set(path, entry);
744 }
745 capturedFiles.delete(path);
746 }
747 };
748
749 /** @type {SnapshotOptimizationEntry | undefined} */
750 let newOptimizationEntry;
751
752 const capturedFilesSize = capturedFiles.size;
753
754 /** @type {Set<SnapshotOptimizationEntry> | undefined} */
755 const optimizationEntries = new Set();
756
757 for (const path of capturedFiles) {
758 const optimizationEntry = this._map.get(path);
759 if (optimizationEntry === undefined) {
760 if (newOptimizationEntry === undefined) {
761 newOptimizationEntry = {
762 snapshot: newSnapshot,
763 shared: 0,
764 snapshotContent: undefined,
765 children: undefined
766 };
767 }
768 this._map.set(path, newOptimizationEntry);
769 } else {
770 optimizationEntries.add(optimizationEntry);
771 }
772 }
773
774 optimizationEntriesLabel: for (const optimizationEntry of optimizationEntries) {
775 const snapshot = optimizationEntry.snapshot;
776 if (optimizationEntry.shared > 0) {
777 // It's a shared snapshot
778 // We can't change it, so we can only use it when all files match
779 // and startTime is compatible
780 if (
781 this._useStartTime &&
782 newSnapshot.startTime &&
783 (!snapshot.startTime || snapshot.startTime > newSnapshot.startTime)
784 ) {
785 continue;
786 }
787 /** @type {Set<string>} */
788 const nonSharedFiles = new Set();
789 const snapshotContent =
790 /** @type {NonNullable<SnapshotOptimizationEntry["snapshotContent"]>} */
791 (optimizationEntry.snapshotContent);
792 const snapshotEntries =
793 /** @type {SnapshotOptimizationValue<U, T>} */
794 (this._get(snapshot));
795 for (const path of snapshotContent) {
796 if (!capturedFiles.has(path)) {
797 if (!snapshotEntries.has(path)) {
798 // File is not shared and can't be removed from the snapshot
799 // because it's in a child of the snapshot
800 continue optimizationEntriesLabel;
801 }
802 nonSharedFiles.add(path);
803 }
804 }
805 if (nonSharedFiles.size === 0) {
806 // The complete snapshot is shared
807 // add it as child
808 newSnapshot.addChild(snapshot);
809 increaseSharedAndStoreOptimizationEntry(optimizationEntry);
810 this._statReusedSharedSnapshots++;
811 } else {
812 // Only a part of the snapshot is shared
813 const sharedCount = snapshotContent.size - nonSharedFiles.size;
814 if (sharedCount < MIN_COMMON_SNAPSHOT_SIZE) {
815 // Common part it too small
816 continue;
817 }
818 // Extract common timestamps from both snapshots
819 /** @type {Set<string> | Map<string, T>} */
820 let commonMap;
821 if (this._isSet) {
822 commonMap = new Set();
823 for (const path of /** @type {Set<string>} */ (snapshotEntries)) {
824 if (nonSharedFiles.has(path)) continue;
825 commonMap.add(path);
826 snapshotEntries.delete(path);
827 }
828 } else {
829 commonMap = new Map();
830 const map = /** @type {Map<string, T>} */ (snapshotEntries);
831 for (const [path, value] of map) {
832 if (nonSharedFiles.has(path)) continue;
833 commonMap.set(path, value);
834 snapshotEntries.delete(path);
835 }
836 }
837 // Create and attach snapshot
838 const commonSnapshot = new Snapshot();
839 if (this._useStartTime) {
840 commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
841 }
842 this._set(
843 commonSnapshot,
844 /** @type {SnapshotOptimizationValue<U, T>} */ (commonMap)
845 );
846 newSnapshot.addChild(commonSnapshot);
847 snapshot.addChild(commonSnapshot);
848 // Create optimization entry
849 const newEntry = {
850 snapshot: commonSnapshot,
851 shared: optimizationEntry.shared + 1,
852 snapshotContent: new Set(commonMap.keys()),
853 children: undefined
854 };
855 if (optimizationEntry.children === undefined) {
856 optimizationEntry.children = new Set();
857 }
858 optimizationEntry.children.add(newEntry);
859 storeOptimizationEntry(newEntry);
860 this._statSharedSnapshots++;
861 }
862 } else {
863 // It's a unshared snapshot
864 // We can extract a common shared snapshot
865 // with all common files
866 const snapshotEntries = this._get(snapshot);
867 if (snapshotEntries === undefined) {
868 // Incomplete snapshot, that can't be used
869 continue;
870 }
871 /** @type {Set<string> | Map<string, T>} */
872 let commonMap;
873 if (this._isSet) {
874 commonMap = new Set();
875 const set = /** @type {Set<string>} */ (snapshotEntries);
876 if (capturedFiles.size < set.size) {
877 for (const path of capturedFiles) {
878 if (set.has(path)) commonMap.add(path);
879 }
880 } else {
881 for (const path of set) {
882 if (capturedFiles.has(path)) commonMap.add(path);
883 }
884 }
885 } else {
886 commonMap = new Map();
887 const map = /** @type {Map<string, T>} */ (snapshotEntries);
888 for (const path of capturedFiles) {
889 const ts = map.get(path);
890 if (ts === undefined) continue;
891 commonMap.set(path, ts);
892 }
893 }
894
895 if (commonMap.size < MIN_COMMON_SNAPSHOT_SIZE) {
896 // Common part it too small
897 continue;
898 }
899 // Create and attach snapshot
900 const commonSnapshot = new Snapshot();
901 if (this._useStartTime) {
902 commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
903 }
904 this._set(
905 commonSnapshot,
906 /** @type {SnapshotOptimizationValue<U, T>} */
907 (commonMap)
908 );
909 newSnapshot.addChild(commonSnapshot);
910 snapshot.addChild(commonSnapshot);
911 // Remove files from snapshot
912 for (const path of commonMap.keys()) snapshotEntries.delete(path);
913 const sharedCount = commonMap.size;
914 this._statItemsUnshared -= sharedCount;
915 this._statItemsShared += sharedCount;
916 // Create optimization entry
917 storeOptimizationEntry({
918 snapshot: commonSnapshot,
919 shared: 2,
920 snapshotContent: new Set(commonMap.keys()),
921 children: undefined
922 });
923 this._statSharedSnapshots++;
924 }
925 }
926 const unshared = capturedFiles.size;
927 this._statItemsUnshared += unshared;
928 this._statItemsShared += capturedFilesSize - unshared;
929 }
930}
931
932/**
933 * Returns result.
934 * @param {string} str input
935 * @returns {string} result
936 */
937const parseString = (str) => {
938 if (str[0] === "'" || str[0] === "`") {
939 str = `"${str.slice(1, -1).replace(/"/g, '\\"')}"`;
940 }
941 return JSON.parse(str);
942};
943
944/* istanbul ignore next */
945/**
946 * Processes the provided mtime.
947 * @param {number} mtime mtime
948 */
949const applyMtime = (mtime) => {
950 if (FS_ACCURACY > 1 && mtime % 2 !== 0) FS_ACCURACY = 1;
951 else if (FS_ACCURACY > 10 && mtime % 20 !== 0) FS_ACCURACY = 10;
952 else if (FS_ACCURACY > 100 && mtime % 200 !== 0) FS_ACCURACY = 100;
953 else if (FS_ACCURACY > 1000 && mtime % 2000 !== 0) FS_ACCURACY = 1000;
954};
955
956/**
957 * Merges the provided values into a single result.
958 * @template T
959 * @template K
960 * @param {Map<T, K> | undefined} a source map
961 * @param {Map<T, K> | undefined} b joining map
962 * @returns {Map<T, K>} joined map
963 */
964const mergeMaps = (a, b) => {
965 if (!b || b.size === 0) return /** @type {Map<T, K>} */ (a);
966 if (!a || a.size === 0) return /** @type {Map<T, K>} */ (b);
967 /** @type {Map<T, K>} */
968 const map = new Map(a);
969 for (const [key, value] of b) {
970 map.set(key, value);
971 }
972 return map;
973};
974
975/**
976 * Merges the provided values into a single result.
977 * @template T
978 * @param {Set<T> | undefined} a source map
979 * @param {Set<T> | undefined} b joining map
980 * @returns {Set<T>} joined map
981 */
982const mergeSets = (a, b) => {
983 if (!b || b.size === 0) return /** @type {Set<T>} */ (a);
984 if (!a || a.size === 0) return /** @type {Set<T>} */ (b);
985 /** @type {Set<T>} */
986 const map = new Set(a);
987 for (const item of b) {
988 map.add(item);
989 }
990 return map;
991};
992
993/**
994 * Finding file or directory to manage
995 * @param {string} managedPath path that is managing by {@link FileSystemInfo}
996 * @param {string} path path to file or directory
997 * @returns {string | null} managed item
998 * @example
999 * getManagedItem(
1000 * '/Users/user/my-project/node_modules/',
1001 * '/Users/user/my-project/node_modules/package/index.js'
1002 * ) === '/Users/user/my-project/node_modules/package'
1003 * getManagedItem(
1004 * '/Users/user/my-project/node_modules/',
1005 * '/Users/user/my-project/node_modules/package1/node_modules/package2'
1006 * ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
1007 * getManagedItem(
1008 * '/Users/user/my-project/node_modules/',
1009 * '/Users/user/my-project/node_modules/.bin/script.js'
1010 * ) === null // hidden files are disallowed as managed items
1011 * getManagedItem(
1012 * '/Users/user/my-project/node_modules/',
1013 * '/Users/user/my-project/node_modules/package'
1014 * ) === '/Users/user/my-project/node_modules/package'
1015 */
1016const getManagedItem = (managedPath, path) => {
1017 let i = managedPath.length;
1018 let slashes = 1;
1019 let startingPosition = true;
1020 loop: while (i < path.length) {
1021 switch (path.charCodeAt(i)) {
1022 case 47: // slash
1023 case 92: // backslash
1024 if (--slashes === 0) break loop;
1025 startingPosition = true;
1026 break;
1027 case 46: // .
1028 // hidden files are disallowed as managed items
1029 // it's probably .yarn-integrity or .cache
1030 if (startingPosition) return null;
1031 break;
1032 case 64: // @
1033 if (!startingPosition) return null;
1034 slashes++;
1035 break;
1036 default:
1037 startingPosition = false;
1038 break;
1039 }
1040 i++;
1041 }
1042 if (i === path.length) slashes--;
1043 // return null when path is incomplete
1044 if (slashes !== 0) return null;
1045 // if (path.slice(i + 1, i + 13) === "node_modules")
1046 if (
1047 path.length >= i + 13 &&
1048 path.charCodeAt(i + 1) === 110 &&
1049 path.charCodeAt(i + 2) === 111 &&
1050 path.charCodeAt(i + 3) === 100 &&
1051 path.charCodeAt(i + 4) === 101 &&
1052 path.charCodeAt(i + 5) === 95 &&
1053 path.charCodeAt(i + 6) === 109 &&
1054 path.charCodeAt(i + 7) === 111 &&
1055 path.charCodeAt(i + 8) === 100 &&
1056 path.charCodeAt(i + 9) === 117 &&
1057 path.charCodeAt(i + 10) === 108 &&
1058 path.charCodeAt(i + 11) === 101 &&
1059 path.charCodeAt(i + 12) === 115
1060 ) {
1061 // if this is the end of the path
1062 if (path.length === i + 13) {
1063 // return the node_modules directory
1064 // it's special
1065 return path;
1066 }
1067 const c = path.charCodeAt(i + 13);
1068 // if next symbol is slash or backslash
1069 if (c === 47 || c === 92) {
1070 // Managed subpath
1071 return getManagedItem(path.slice(0, i + 14), path);
1072 }
1073 }
1074 return path.slice(0, i);
1075};
1076
1077/**
1078 * Gets resolved timestamp.
1079 * @template {ContextFileSystemInfoEntry | ContextTimestampAndHash} T
1080 * @param {T | null} entry entry
1081 * @returns {T["resolved"] | null | undefined} the resolved entry
1082 */
1083const getResolvedTimestamp = (entry) => {
1084 if (entry === null) return null;
1085 if (entry.resolved !== undefined) return entry.resolved;
1086 return entry.symlinks === undefined ? entry : undefined;
1087};
1088
1089/**
1090 * Gets resolved hash.
1091 * @param {ContextHash | null} entry entry
1092 * @returns {string | null | undefined} the resolved entry
1093 */
1094const getResolvedHash = (entry) => {
1095 if (entry === null) return null;
1096 if (entry.resolved !== undefined) return entry.resolved;
1097 return entry.symlinks === undefined ? entry.hash : undefined;
1098};
1099
1100/**
1101 * Adds the provided source to the snapshot optimization.
1102 * @template T
1103 * @param {Set<T>} source source
1104 * @param {Set<T>} target target
1105 */
1106const addAll = (source, target) => {
1107 for (const key of source) target.add(key);
1108};
1109
1110const getEsModuleLexer = memoize(() => require("es-module-lexer"));
1111
1112/** @typedef {Set<string>} LoggedPaths */
1113
1114/** @typedef {FileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} FileTimestamp */
1115/** @typedef {ContextFileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} ContextTimestamp */
1116/** @typedef {ResolvedContextFileSystemInfoEntry | "ignore" | null} ResolvedContextTimestamp */
1117
1118/**
1119 * `watchpack` may report `{}` (existence-only) for files and directories it
1120 * is watching but has no time information for. Such entries cannot be used
1121 * for snapshot comparison, so cache lookups treat them as "no cached value"
1122 * and fall back to a fresh on-disk read.
1123 * @param {FileTimestamp | ContextTimestamp | undefined} entry cache entry
1124 * @returns {entry is ExistenceOnlyTimeEntry} true if the entry exists but carries no time info
1125 */
1126const isExistenceOnly = (entry) => {
1127 if (entry === undefined || entry === null || entry === "ignore") return false;
1128 return (
1129 /** @type {Partial<FileSystemInfoEntry> & Partial<ContextFileSystemInfoEntry>} */
1130 (entry).safeTime === undefined
1131 );
1132};
1133
1134/** @typedef {(err?: WebpackError | null, result?: boolean) => void} CheckSnapshotValidCallback */
1135
1136/**
1137 * Used to access information about the filesystem in a cached way
1138 */
1139class FileSystemInfo {
1140 /**
1141 * Creates an instance of FileSystemInfo.
1142 * @param {InputFileSystem} fs file system
1143 * @param {object} options options
1144 * @param {Iterable<string | RegExp>=} options.unmanagedPaths paths that are not managed by a package manager and the contents are subject to change
1145 * @param {Iterable<string | RegExp>=} options.managedPaths paths that are only managed by a package manager
1146 * @param {Iterable<string | RegExp>=} options.immutablePaths paths that are immutable
1147 * @param {Logger=} options.logger logger used to log invalid snapshots
1148 * @param {HashFunction=} options.hashFunction the hash function to use
1149 */
1150 constructor(
1151 fs,
1152 {
1153 unmanagedPaths = [],
1154 managedPaths = [],
1155 immutablePaths = [],
1156 logger,
1157 hashFunction = DEFAULTS.HASH_FUNCTION
1158 } = {}
1159 ) {
1160 this.fs = fs;
1161 this.logger = logger;
1162 this._remainingLogs = logger ? 40 : 0;
1163 /** @type {LoggedPaths | undefined} */
1164 this._loggedPaths = logger ? new Set() : undefined;
1165 this._hashFunction = hashFunction;
1166 /** @type {WeakMap<Snapshot, boolean | CheckSnapshotValidCallback[]>} */
1167 this._snapshotCache = new WeakMap();
1168 this._fileTimestampsOptimization = new SnapshotOptimization(
1169 (s) => s.hasFileTimestamps(),
1170 (s) => s.fileTimestamps,
1171 (s, v) => s.setFileTimestamps(v)
1172 );
1173 this._fileHashesOptimization = new SnapshotOptimization(
1174 (s) => s.hasFileHashes(),
1175 (s) => s.fileHashes,
1176 (s, v) => s.setFileHashes(v),
1177 false
1178 );
1179 this._fileTshsOptimization = new SnapshotOptimization(
1180 (s) => s.hasFileTshs(),
1181 (s) => s.fileTshs,
1182 (s, v) => s.setFileTshs(v)
1183 );
1184 this._contextTimestampsOptimization = new SnapshotOptimization(
1185 (s) => s.hasContextTimestamps(),
1186 (s) => s.contextTimestamps,
1187 (s, v) => s.setContextTimestamps(v)
1188 );
1189 this._contextHashesOptimization = new SnapshotOptimization(
1190 (s) => s.hasContextHashes(),
1191 (s) => s.contextHashes,
1192 (s, v) => s.setContextHashes(v),
1193 false
1194 );
1195 this._contextTshsOptimization = new SnapshotOptimization(
1196 (s) => s.hasContextTshs(),
1197 (s) => s.contextTshs,
1198 (s, v) => s.setContextTshs(v)
1199 );
1200 this._missingExistenceOptimization = new SnapshotOptimization(
1201 (s) => s.hasMissingExistence(),
1202 (s) => s.missingExistence,
1203 (s, v) => s.setMissingExistence(v),
1204 false
1205 );
1206 this._managedItemInfoOptimization = new SnapshotOptimization(
1207 (s) => s.hasManagedItemInfo(),
1208 (s) => s.managedItemInfo,
1209 (s, v) => s.setManagedItemInfo(v),
1210 false
1211 );
1212 this._managedFilesOptimization = new SnapshotOptimization(
1213 (s) => s.hasManagedFiles(),
1214 (s) => s.managedFiles,
1215 (s, v) => s.setManagedFiles(v),
1216 false,
1217 true
1218 );
1219 this._managedContextsOptimization = new SnapshotOptimization(
1220 (s) => s.hasManagedContexts(),
1221 (s) => s.managedContexts,
1222 (s, v) => s.setManagedContexts(v),
1223 false,
1224 true
1225 );
1226 this._managedMissingOptimization = new SnapshotOptimization(
1227 (s) => s.hasManagedMissing(),
1228 (s) => s.managedMissing,
1229 (s, v) => s.setManagedMissing(v),
1230 false,
1231 true
1232 );
1233 /** @type {StackedCacheMap<string, FileTimestamp>} */
1234 this._fileTimestamps = new StackedCacheMap();
1235 /** @type {Map<string, string | null>} */
1236 this._fileHashes = new Map();
1237 /** @type {Map<string, TimestampAndHash | string>} */
1238 this._fileTshs = new Map();
1239 /** @type {StackedCacheMap<string, ContextTimestamp>} */
1240 this._contextTimestamps = new StackedCacheMap();
1241 /** @type {Map<string, ContextHash>} */
1242 this._contextHashes = new Map();
1243 /** @type {Map<string, ContextTimestampAndHash>} */
1244 this._contextTshs = new Map();
1245 /** @type {Map<string, string>} */
1246 this._managedItems = new Map();
1247 /** @type {AsyncQueue<string, string, FileSystemInfoEntry>} */
1248 this.fileTimestampQueue = new AsyncQueue({
1249 name: "file timestamp",
1250 parallelism: 30,
1251 processor: this._readFileTimestamp.bind(this)
1252 });
1253 /** @type {AsyncQueue<string, string, string>} */
1254 this.fileHashQueue = new AsyncQueue({
1255 name: "file hash",
1256 parallelism: 10,
1257 processor: this._readFileHash.bind(this)
1258 });
1259 /** @type {AsyncQueue<string, string, ContextFileSystemInfoEntry>} */
1260 this.contextTimestampQueue = new AsyncQueue({
1261 name: "context timestamp",
1262 parallelism: 2,
1263 processor: this._readContextTimestamp.bind(this)
1264 });
1265 /** @type {AsyncQueue<string, string, ContextHash>} */
1266 this.contextHashQueue = new AsyncQueue({
1267 name: "context hash",
1268 parallelism: 2,
1269 processor: this._readContextHash.bind(this)
1270 });
1271 /** @type {AsyncQueue<string, string, ContextTimestampAndHash>} */
1272 this.contextTshQueue = new AsyncQueue({
1273 name: "context hash and timestamp",
1274 parallelism: 2,
1275 processor: this._readContextTimestampAndHash.bind(this)
1276 });
1277 /** @type {AsyncQueue<string, string, string>} */
1278 this.managedItemQueue = new AsyncQueue({
1279 name: "managed item info",
1280 parallelism: 10,
1281 processor: this._getManagedItemInfo.bind(this)
1282 });
1283 /** @type {AsyncQueue<string, string, Set<string>>} */
1284 this.managedItemDirectoryQueue = new AsyncQueue({
1285 name: "managed item directory info",
1286 parallelism: 10,
1287 processor: this._getManagedItemDirectoryInfo.bind(this)
1288 });
1289 const _unmanagedPaths = [...unmanagedPaths];
1290 /** @type {string[]} */
1291 this.unmanagedPathsWithSlash = _unmanagedPaths
1292 .filter((p) => typeof p === "string")
1293 .map((p) => join(fs, p, "_").slice(0, -1));
1294 /** @type {RegExp[]} */
1295 this.unmanagedPathsRegExps = _unmanagedPaths.filter(
1296 (p) => typeof p !== "string"
1297 );
1298
1299 this.managedPaths = [...managedPaths];
1300 /** @type {string[]} */
1301 this.managedPathsWithSlash = this.managedPaths
1302 .filter((p) => typeof p === "string")
1303 .map((p) => join(fs, p, "_").slice(0, -1));
1304 /** @type {RegExp[]} */
1305 this.managedPathsRegExps = this.managedPaths.filter(
1306 (p) => typeof p !== "string"
1307 );
1308
1309 this.immutablePaths = [...immutablePaths];
1310 /** @type {string[]} */
1311 this.immutablePathsWithSlash = this.immutablePaths
1312 .filter((p) => typeof p === "string")
1313 .map((p) => join(fs, p, "_").slice(0, -1));
1314 /** @type {RegExp[]} */
1315 this.immutablePathsRegExps = this.immutablePaths.filter(
1316 (p) => typeof p !== "string"
1317 );
1318
1319 this._cachedDeprecatedFileTimestamps = undefined;
1320 this._cachedDeprecatedContextTimestamps = undefined;
1321
1322 this._warnAboutExperimentalEsmTracking = false;
1323
1324 this._statCreatedSnapshots = 0;
1325 this._statTestedSnapshotsCached = 0;
1326 this._statTestedSnapshotsNotCached = 0;
1327 this._statTestedChildrenCached = 0;
1328 this._statTestedChildrenNotCached = 0;
1329 this._statTestedEntries = 0;
1330 }
1331
1332 logStatistics() {
1333 const logger = /** @type {Logger} */ (this.logger);
1334 /**
1335 * Processes the provided header.
1336 * @param {string} header header
1337 * @param {string | undefined} message message
1338 */
1339 const logWhenMessage = (header, message) => {
1340 if (message) {
1341 logger.log(`${header}: ${message}`);
1342 }
1343 };
1344 logger.log(`${this._statCreatedSnapshots} new snapshots created`);
1345 logger.log(
1346 `${
1347 this._statTestedSnapshotsNotCached &&
1348 Math.round(
1349 (this._statTestedSnapshotsNotCached * 100) /
1350 (this._statTestedSnapshotsCached +
1351 this._statTestedSnapshotsNotCached)
1352 )
1353 }% root snapshot uncached (${this._statTestedSnapshotsNotCached} / ${
1354 this._statTestedSnapshotsCached + this._statTestedSnapshotsNotCached
1355 })`
1356 );
1357 logger.log(
1358 `${
1359 this._statTestedChildrenNotCached &&
1360 Math.round(
1361 (this._statTestedChildrenNotCached * 100) /
1362 (this._statTestedChildrenCached + this._statTestedChildrenNotCached)
1363 )
1364 }% children snapshot uncached (${this._statTestedChildrenNotCached} / ${
1365 this._statTestedChildrenCached + this._statTestedChildrenNotCached
1366 })`
1367 );
1368 logger.log(`${this._statTestedEntries} entries tested`);
1369 logger.log(
1370 `File info in cache: ${this._fileTimestamps.size} timestamps ${this._fileHashes.size} hashes ${this._fileTshs.size} timestamp hash combinations`
1371 );
1372 logWhenMessage(
1373 "File timestamp snapshot optimization",
1374 this._fileTimestampsOptimization.getStatisticMessage()
1375 );
1376 logWhenMessage(
1377 "File hash snapshot optimization",
1378 this._fileHashesOptimization.getStatisticMessage()
1379 );
1380 logWhenMessage(
1381 "File timestamp hash combination snapshot optimization",
1382 this._fileTshsOptimization.getStatisticMessage()
1383 );
1384 logger.log(
1385 `Directory info in cache: ${this._contextTimestamps.size} timestamps ${this._contextHashes.size} hashes ${this._contextTshs.size} timestamp hash combinations`
1386 );
1387 logWhenMessage(
1388 "Directory timestamp snapshot optimization",
1389 this._contextTimestampsOptimization.getStatisticMessage()
1390 );
1391 logWhenMessage(
1392 "Directory hash snapshot optimization",
1393 this._contextHashesOptimization.getStatisticMessage()
1394 );
1395 logWhenMessage(
1396 "Directory timestamp hash combination snapshot optimization",
1397 this._contextTshsOptimization.getStatisticMessage()
1398 );
1399 logWhenMessage(
1400 "Missing items snapshot optimization",
1401 this._missingExistenceOptimization.getStatisticMessage()
1402 );
1403 logger.log(`Managed items info in cache: ${this._managedItems.size} items`);
1404 logWhenMessage(
1405 "Managed items snapshot optimization",
1406 this._managedItemInfoOptimization.getStatisticMessage()
1407 );
1408 logWhenMessage(
1409 "Managed files snapshot optimization",
1410 this._managedFilesOptimization.getStatisticMessage()
1411 );
1412 logWhenMessage(
1413 "Managed contexts snapshot optimization",
1414 this._managedContextsOptimization.getStatisticMessage()
1415 );
1416 logWhenMessage(
1417 "Managed missing snapshot optimization",
1418 this._managedMissingOptimization.getStatisticMessage()
1419 );
1420 }
1421
1422 /**
1423 * Processes the provided path.
1424 * @private
1425 * @param {string} path path
1426 * @param {string} reason reason
1427 * @param {EXPECTED_ANY[]} args arguments
1428 */
1429 _log(path, reason, ...args) {
1430 const key = path + reason;
1431 const loggedPaths = /** @type {LoggedPaths} */ (this._loggedPaths);
1432 if (loggedPaths.has(key)) return;
1433 loggedPaths.add(key);
1434 /** @type {Logger} */
1435 (this.logger).debug(`${path} invalidated because ${reason}`, ...args);
1436 if (--this._remainingLogs === 0) {
1437 /** @type {Logger} */
1438 (this.logger).debug(
1439 "Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
1440 );
1441 }
1442 }
1443
1444 clear() {
1445 this._remainingLogs = this.logger ? 40 : 0;
1446 if (this._loggedPaths !== undefined) this._loggedPaths.clear();
1447
1448 this._snapshotCache = new WeakMap();
1449 this._fileTimestampsOptimization.clear();
1450 this._fileHashesOptimization.clear();
1451 this._fileTshsOptimization.clear();
1452 this._contextTimestampsOptimization.clear();
1453 this._contextHashesOptimization.clear();
1454 this._contextTshsOptimization.clear();
1455 this._missingExistenceOptimization.clear();
1456 this._managedItemInfoOptimization.clear();
1457 this._managedFilesOptimization.clear();
1458 this._managedContextsOptimization.clear();
1459 this._managedMissingOptimization.clear();
1460 this._fileTimestamps.clear();
1461 this._fileHashes.clear();
1462 this._fileTshs.clear();
1463 this._contextTimestamps.clear();
1464 this._contextHashes.clear();
1465 this._contextTshs.clear();
1466 this._managedItems.clear();
1467 this._managedItems.clear();
1468
1469 this._cachedDeprecatedFileTimestamps = undefined;
1470 this._cachedDeprecatedContextTimestamps = undefined;
1471
1472 this._statCreatedSnapshots = 0;
1473 this._statTestedSnapshotsCached = 0;
1474 this._statTestedSnapshotsNotCached = 0;
1475 this._statTestedChildrenCached = 0;
1476 this._statTestedChildrenNotCached = 0;
1477 this._statTestedEntries = 0;
1478 }
1479
1480 /**
1481 * Adds file timestamps.
1482 * @param {ReadonlyMap<string, FileTimestamp>} map timestamps
1483 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
1484 * @returns {void}
1485 */
1486 addFileTimestamps(map, immutable) {
1487 this._fileTimestamps.addAll(map, immutable);
1488 this._cachedDeprecatedFileTimestamps = undefined;
1489 }
1490
1491 /**
1492 * Adds context timestamps.
1493 * @param {ReadonlyMap<string, ContextTimestamp>} map timestamps
1494 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
1495 * @returns {void}
1496 */
1497 addContextTimestamps(map, immutable) {
1498 this._contextTimestamps.addAll(map, immutable);
1499 this._cachedDeprecatedContextTimestamps = undefined;
1500 }
1501
1502 /**
1503 * Gets file timestamp.
1504 * @param {string} path file path
1505 * @param {(err?: WebpackError | null, fileTimestamp?: FileSystemInfoEntry | "ignore" | null) => void} callback callback function
1506 * @returns {void}
1507 */
1508 getFileTimestamp(path, callback) {
1509 const cache = this._fileTimestamps.get(path);
1510 if (cache !== undefined && !isExistenceOnly(cache)) {
1511 return callback(
1512 null,
1513 /** @type {FileSystemInfoEntry | "ignore" | null} */ (cache)
1514 );
1515 }
1516 this.fileTimestampQueue.add(path, callback);
1517 }
1518
1519 /**
1520 * Gets context timestamp.
1521 * @param {string} path context path
1522 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
1523 * @returns {void}
1524 */
1525 getContextTimestamp(path, callback) {
1526 const cache = this._contextTimestamps.get(path);
1527 if (cache !== undefined && !isExistenceOnly(cache)) {
1528 if (cache === "ignore") return callback(null, "ignore");
1529 const fullEntry =
1530 /** @type {ContextFileSystemInfoEntry | null} */
1531 (cache);
1532 const resolved = getResolvedTimestamp(fullEntry);
1533 if (resolved !== undefined) return callback(null, resolved);
1534 return this._resolveContextTimestamp(
1535 /** @type {ContextFileSystemInfoEntry} */
1536 (fullEntry),
1537 callback
1538 );
1539 }
1540 this._readFreshContextTimestamp(path, callback);
1541 }
1542
1543 /**
1544 * Reads a context timestamp directly from disk, bypassing any cached
1545 * entry. Used by `getContextTimestamp` and the snapshot validity
1546 * checks when the cached entry is missing or is an `ExistenceOnlyTimeEntry`
1547 * (`{}`) supplied by watchpack — both cases require a fresh read to
1548 * obtain the `timestampHash`.
1549 * @private
1550 * @param {string} path context path
1551 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
1552 * @returns {void}
1553 */
1554 _readFreshContextTimestamp(path, callback) {
1555 this.contextTimestampQueue.add(path, (err, _entry) => {
1556 if (err) return callback(err);
1557 const entry = /** @type {ContextFileSystemInfoEntry | null} */ (_entry);
1558 if (entry === null) return callback(null, null);
1559 const resolved = getResolvedTimestamp(entry);
1560 if (resolved !== undefined) return callback(null, resolved);
1561 this._resolveContextTimestamp(entry, callback);
1562 });
1563 }
1564
1565 /**
1566 * Get unresolved context timestamp. Existence-only cache entries (`{}`)
1567 * are bypassed so the callback always receives a complete entry, "ignore"
1568 * or null.
1569 * @private
1570 * @param {string} path context path
1571 * @param {(err?: WebpackError | null, contextTimestamp?: ContextFileSystemInfoEntry | "ignore" | null) => void} callback callback function
1572 * @returns {void}
1573 */
1574 _getUnresolvedContextTimestamp(path, callback) {
1575 const cache = this._contextTimestamps.get(path);
1576 if (cache !== undefined && !isExistenceOnly(cache)) {
1577 return callback(
1578 null,
1579 /** @type {ContextFileSystemInfoEntry | "ignore" | null} */ (cache)
1580 );
1581 }
1582 this.contextTimestampQueue.add(path, callback);
1583 }
1584
1585 /**
1586 * Returns file hash.
1587 * @param {string} path file path
1588 * @param {(err?: WebpackError | null, hash?: string | null) => void} callback callback function
1589 * @returns {void}
1590 */
1591 getFileHash(path, callback) {
1592 const cache = this._fileHashes.get(path);
1593 if (cache !== undefined) return callback(null, cache);
1594 this.fileHashQueue.add(path, callback);
1595 }
1596
1597 /**
1598 * Returns context hash.
1599 * @param {string} path context path
1600 * @param {(err?: WebpackError | null, contextHash?: string) => void} callback callback function
1601 * @returns {void}
1602 */
1603 getContextHash(path, callback) {
1604 const cache = this._contextHashes.get(path);
1605 if (cache !== undefined) {
1606 const resolved = getResolvedHash(cache);
1607 if (resolved !== undefined) {
1608 return callback(null, /** @type {string} */ (resolved));
1609 }
1610 return this._resolveContextHash(cache, callback);
1611 }
1612 this.contextHashQueue.add(path, (err, _entry) => {
1613 if (err) return callback(err);
1614 const entry = /** @type {ContextHash} */ (_entry);
1615 const resolved = getResolvedHash(entry);
1616 if (resolved !== undefined) {
1617 return callback(null, /** @type {string} */ (resolved));
1618 }
1619 this._resolveContextHash(entry, callback);
1620 });
1621 }
1622
1623 /**
1624 * Get unresolved context hash.
1625 * @private
1626 * @param {string} path context path
1627 * @param {(err?: WebpackError | null, contextHash?: ContextHash | null) => void} callback callback function
1628 * @returns {void}
1629 */
1630 _getUnresolvedContextHash(path, callback) {
1631 const cache = this._contextHashes.get(path);
1632 if (cache !== undefined) return callback(null, cache);
1633 this.contextHashQueue.add(path, callback);
1634 }
1635
1636 /**
1637 * Returns context tsh.
1638 * @param {string} path context path
1639 * @param {(err?: WebpackError | null, resolvedContextTimestampAndHash?: ResolvedContextTimestampAndHash | null) => void} callback callback function
1640 * @returns {void}
1641 */
1642 getContextTsh(path, callback) {
1643 const cache = this._contextTshs.get(path);
1644 if (cache !== undefined) {
1645 const resolved = getResolvedTimestamp(cache);
1646 if (resolved !== undefined) return callback(null, resolved);
1647 return this._resolveContextTsh(cache, callback);
1648 }
1649 this.contextTshQueue.add(path, (err, _entry) => {
1650 if (err) return callback(err);
1651 const entry = /** @type {ContextTimestampAndHash} */ (_entry);
1652 const resolved = getResolvedTimestamp(entry);
1653 if (resolved !== undefined) return callback(null, resolved);
1654 this._resolveContextTsh(entry, callback);
1655 });
1656 }
1657
1658 /**
1659 * Get unresolved context tsh.
1660 * @private
1661 * @param {string} path context path
1662 * @param {(err?: WebpackError | null, contextTimestampAndHash?: ContextTimestampAndHash | null) => void} callback callback function
1663 * @returns {void}
1664 */
1665 _getUnresolvedContextTsh(path, callback) {
1666 const cache = this._contextTshs.get(path);
1667 if (cache !== undefined) return callback(null, cache);
1668 this.contextTshQueue.add(path, callback);
1669 }
1670
1671 _createBuildDependenciesResolvers() {
1672 const resolveContext = createResolver({
1673 resolveToContext: true,
1674 exportsFields: [],
1675 fileSystem: this.fs
1676 });
1677 const resolveCjs = createResolver({
1678 extensions: [".js", ".json", ".node"],
1679 conditionNames: ["require", "module-sync", "node"],
1680 exportsFields: ["exports"],
1681 fileSystem: this.fs
1682 });
1683 const resolveCjsAsChild = createResolver({
1684 extensions: [".js", ".json", ".node"],
1685 conditionNames: ["require", "module-sync", "node"],
1686 exportsFields: [],
1687 fileSystem: this.fs
1688 });
1689 const resolveEsm = createResolver({
1690 extensions: [".js", ".json", ".node"],
1691 fullySpecified: true,
1692 conditionNames: ["import", "module-sync", "node"],
1693 exportsFields: ["exports"],
1694 fileSystem: this.fs
1695 });
1696 return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild };
1697 }
1698
1699 /**
1700 * Resolves build dependencies.
1701 * @param {string} context context directory
1702 * @param {Iterable<string>} deps dependencies
1703 * @param {(err?: Error | null, resolveBuildDependenciesResult?: ResolveBuildDependenciesResult) => void} callback callback function
1704 * @returns {void}
1705 */
1706 resolveBuildDependencies(context, deps, callback) {
1707 const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } =
1708 this._createBuildDependenciesResolvers();
1709
1710 /** @type {Files} */
1711 const files = new Set();
1712 /** @type {Symlinks} */
1713 const fileSymlinks = new Set();
1714 /** @type {Directories} */
1715 const directories = new Set();
1716 /** @type {Symlinks} */
1717 const directorySymlinks = new Set();
1718 /** @type {Missing} */
1719 const missing = new Set();
1720 /** @type {ResolveDependencies["files"]} */
1721 const resolveFiles = new Set();
1722 /** @type {ResolveDependencies["directories"]} */
1723 const resolveDirectories = new Set();
1724 /** @type {ResolveDependencies["missing"]} */
1725 const resolveMissing = new Set();
1726 /** @type {ResolveResults} */
1727 const resolveResults = new Map();
1728 /** @type {Set<string>} */
1729 const invalidResolveResults = new Set();
1730 const resolverContext = {
1731 fileDependencies: resolveFiles,
1732 contextDependencies: resolveDirectories,
1733 missingDependencies: resolveMissing
1734 };
1735 /**
1736 * Expected to string.
1737 * @param {undefined | boolean | string} expected expected result
1738 * @returns {string} expected result
1739 */
1740 const expectedToString = (expected) =>
1741 expected ? ` (expected ${expected})` : "";
1742 /** @typedef {{ type: JobType, context: string | undefined, path: string, issuer: Job | undefined, expected: undefined | boolean | string }} Job */
1743
1744 /**
1745 * Returns result.
1746 * @param {Job} job job
1747 * @returns {string} result
1748 */
1749 const jobToString = (job) => {
1750 switch (job.type) {
1751 case RBDT_RESOLVE_FILE:
1752 return `resolve file ${job.path}${expectedToString(job.expected)}`;
1753 case RBDT_RESOLVE_DIRECTORY:
1754 return `resolve directory ${job.path}`;
1755 case RBDT_RESOLVE_CJS_FILE:
1756 return `resolve commonjs file ${job.path}${expectedToString(
1757 job.expected
1758 )}`;
1759 case RBDT_RESOLVE_ESM_FILE:
1760 return `resolve esm file ${job.path}${expectedToString(
1761 job.expected
1762 )}`;
1763 case RBDT_DIRECTORY:
1764 return `directory ${job.path}`;
1765 case RBDT_FILE:
1766 return `file ${job.path}`;
1767 case RBDT_DIRECTORY_DEPENDENCIES:
1768 return `directory dependencies ${job.path}`;
1769 case RBDT_FILE_DEPENDENCIES:
1770 return `file dependencies ${job.path}`;
1771 }
1772 return `unknown ${job.type} ${job.path}`;
1773 };
1774 /**
1775 * Returns string value.
1776 * @param {Job} job job
1777 * @returns {string} string value
1778 */
1779 const pathToString = (job) => {
1780 let result = ` at ${jobToString(job)}`;
1781 /** @type {Job | undefined} */
1782 (job) = job.issuer;
1783 while (job !== undefined) {
1784 result += `\n at ${jobToString(job)}`;
1785 job = /** @type {Job} */ (job.issuer);
1786 }
1787 return result;
1788 };
1789 const logger = /** @type {Logger} */ (this.logger);
1790 processAsyncTree(
1791 Array.from(
1792 deps,
1793 (dep) =>
1794 /** @type {Job} */ ({
1795 type: RBDT_RESOLVE_INITIAL,
1796 context,
1797 path: dep,
1798 expected: undefined,
1799 issuer: undefined
1800 })
1801 ),
1802 20,
1803 (job, push, callback) => {
1804 const { type, context, path, expected } = job;
1805 /**
1806 * Resolves directory.
1807 * @param {string} path path
1808 * @returns {void}
1809 */
1810 const resolveDirectory = (path) => {
1811 const key = `d\n${context}\n${path}`;
1812 if (resolveResults.has(key)) {
1813 return callback();
1814 }
1815 resolveResults.set(key, undefined);
1816 resolveContext(
1817 /** @type {string} */ (context),
1818 path,
1819 resolverContext,
1820 (err, _, result) => {
1821 if (err) {
1822 if (expected === false) {
1823 resolveResults.set(key, false);
1824 return callback();
1825 }
1826 invalidResolveResults.add(key);
1827 err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
1828 return callback(err);
1829 }
1830 const resultPath = /** @type {ResolveRequest} */ (result).path;
1831 resolveResults.set(key, resultPath);
1832 push({
1833 type: RBDT_DIRECTORY,
1834 context: undefined,
1835 path: /** @type {string} */ (resultPath),
1836 expected: undefined,
1837 issuer: job
1838 });
1839 callback();
1840 }
1841 );
1842 };
1843 /**
1844 * Processes the provided path.
1845 * @param {string} path path
1846 * @param {("f" | "c" | "e")=} symbol symbol
1847 * @param {(ResolveFunctionAsync)=} resolve resolve fn
1848 * @returns {void}
1849 */
1850 const resolveFile = (path, symbol, resolve) => {
1851 const key = `${symbol}\n${context}\n${path}`;
1852 if (resolveResults.has(key)) {
1853 return callback();
1854 }
1855 resolveResults.set(key, undefined);
1856 /** @type {ResolveFunctionAsync} */
1857 (resolve)(
1858 /** @type {string} */ (context),
1859 path,
1860 resolverContext,
1861 (err, _, result) => {
1862 if (typeof expected === "string") {
1863 if (!err && result && result.path === expected) {
1864 resolveResults.set(key, result.path);
1865 } else {
1866 invalidResolveResults.add(key);
1867 logger.warn(
1868 `Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
1869 err || (result && result.path)
1870 }' instead. Resolving dependencies are ignored for this path.\n${pathToString(
1871 job
1872 )}`
1873 );
1874 }
1875 } else {
1876 if (err) {
1877 if (expected === false) {
1878 resolveResults.set(key, false);
1879 return callback();
1880 }
1881 invalidResolveResults.add(key);
1882 err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
1883 job
1884 )}`;
1885 return callback(err);
1886 }
1887 const resultPath = /** @type {ResolveRequest} */ (result).path;
1888 resolveResults.set(key, resultPath);
1889 push({
1890 type: RBDT_FILE,
1891 context: undefined,
1892 path: /** @type {string} */ (resultPath),
1893 expected: undefined,
1894 issuer: job
1895 });
1896 }
1897 callback();
1898 }
1899 );
1900 };
1901 const resolvedType =
1902 type === RBDT_RESOLVE_INITIAL
1903 ? /[\\/]$/.test(path)
1904 ? RBDT_RESOLVE_DIRECTORY
1905 : RBDT_RESOLVE_FILE
1906 : type;
1907 switch (resolvedType) {
1908 case RBDT_RESOLVE_FILE: {
1909 resolveFile(
1910 path,
1911 "f",
1912 /\.mjs$/.test(path) ? resolveEsm : resolveCjs
1913 );
1914 break;
1915 }
1916 case RBDT_RESOLVE_DIRECTORY: {
1917 resolveDirectory(
1918 type === RBDT_RESOLVE_INITIAL ? path.slice(0, -1) : path
1919 );
1920 break;
1921 }
1922 case RBDT_RESOLVE_CJS_FILE: {
1923 resolveFile(path, "f", resolveCjs);
1924 break;
1925 }
1926 case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
1927 resolveFile(path, "c", resolveCjsAsChild);
1928 break;
1929 }
1930 case RBDT_RESOLVE_ESM_FILE: {
1931 resolveFile(path, "e", resolveEsm);
1932 break;
1933 }
1934 case RBDT_FILE: {
1935 if (files.has(path)) {
1936 callback();
1937 break;
1938 }
1939 files.add(path);
1940 /** @type {NonNullable<InputFileSystem["realpath"]>} */
1941 (this.fs.realpath)(path, (err, _realPath) => {
1942 if (err) return callback(err);
1943 const realPath = /** @type {string} */ (_realPath);
1944 if (realPath !== path) {
1945 fileSymlinks.add(path);
1946 resolveFiles.add(path);
1947 if (files.has(realPath)) return callback();
1948 files.add(realPath);
1949 }
1950 push({
1951 type: RBDT_FILE_DEPENDENCIES,
1952 context: undefined,
1953 path: realPath,
1954 expected: undefined,
1955 issuer: job
1956 });
1957 callback();
1958 });
1959 break;
1960 }
1961 case RBDT_DIRECTORY: {
1962 if (directories.has(path)) {
1963 callback();
1964 break;
1965 }
1966 directories.add(path);
1967 /** @type {NonNullable<InputFileSystem["realpath"]>} */
1968 (this.fs.realpath)(path, (err, _realPath) => {
1969 if (err) return callback(err);
1970 const realPath = /** @type {string} */ (_realPath);
1971 if (realPath !== path) {
1972 directorySymlinks.add(path);
1973 resolveFiles.add(path);
1974 if (directories.has(realPath)) return callback();
1975 directories.add(realPath);
1976 }
1977 push({
1978 type: RBDT_DIRECTORY_DEPENDENCIES,
1979 context: undefined,
1980 path: realPath,
1981 expected: undefined,
1982 issuer: job
1983 });
1984 callback();
1985 });
1986 break;
1987 }
1988 case RBDT_FILE_DEPENDENCIES: {
1989 // Check for known files without dependencies
1990 if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
1991 process.nextTick(callback);
1992 break;
1993 }
1994 // Check commonjs cache for the module
1995 /** @type {NodeModule | undefined} */
1996 const module = require.cache[path];
1997 if (
1998 module &&
1999 Array.isArray(module.children) &&
2000 // https://github.com/nodejs/node/issues/59868
2001 // Force use `es-module-lexer` for mjs
2002 !/\.mjs$/.test(path)
2003 ) {
2004 children: for (const child of module.children) {
2005 const childPath = child.filename;
2006 if (childPath) {
2007 push({
2008 type: RBDT_FILE,
2009 context: undefined,
2010 path: childPath,
2011 expected: undefined,
2012 issuer: job
2013 });
2014 const context = dirname(this.fs, path);
2015 for (const modulePath of module.paths) {
2016 if (childPath.startsWith(modulePath)) {
2017 const subPath = childPath.slice(modulePath.length + 1);
2018 const packageMatch = /^@[^\\/]+[\\/][^\\/]+/.exec(
2019 subPath
2020 );
2021 if (packageMatch) {
2022 push({
2023 type: RBDT_FILE,
2024 context: undefined,
2025 path: `${
2026 modulePath +
2027 childPath[modulePath.length] +
2028 packageMatch[0] +
2029 childPath[modulePath.length]
2030 }package.json`,
2031 expected: false,
2032 issuer: job
2033 });
2034 }
2035 let request = subPath.replace(/\\/g, "/");
2036 if (request.endsWith(".js")) {
2037 request = request.slice(0, -3);
2038 }
2039 push({
2040 type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
2041 context,
2042 path: request,
2043 expected: child.filename,
2044 issuer: job
2045 });
2046 continue children;
2047 }
2048 }
2049 let request = relative(this.fs, context, childPath);
2050 if (request.endsWith(".js")) request = request.slice(0, -3);
2051 request = request.replace(/\\/g, "/");
2052 if (!request.startsWith("../") && !isAbsolute(request)) {
2053 request = `./${request}`;
2054 }
2055 push({
2056 type: RBDT_RESOLVE_CJS_FILE,
2057 context,
2058 path: request,
2059 expected: child.filename,
2060 issuer: job
2061 });
2062 }
2063 }
2064 } else if (supportsEsm && /\.m?js$/.test(path)) {
2065 if (!this._warnAboutExperimentalEsmTracking) {
2066 logger.log(
2067 "Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
2068 "Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
2069 "As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
2070 );
2071 this._warnAboutExperimentalEsmTracking = true;
2072 }
2073
2074 const lexer = getEsModuleLexer();
2075
2076 lexer.init.then(() => {
2077 this.fs.readFile(path, (err, content) => {
2078 if (err) return callback(err);
2079 try {
2080 const context = dirname(this.fs, path);
2081 const source = /** @type {Buffer} */ (content).toString();
2082 const [imports] = lexer.parse(source);
2083 /** @type {Set<string>} */
2084 const added = new Set();
2085 for (const imp of imports) {
2086 try {
2087 /** @type {string} */
2088 let dependency;
2089 if (imp.d === -1) {
2090 // import ... from "..."
2091 dependency = parseString(
2092 source.slice(imp.s - 1, imp.e + 1)
2093 );
2094 } else if (imp.d > -1) {
2095 // import()
2096 const expr = source.slice(imp.s, imp.e).trim();
2097 dependency = parseString(expr);
2098 } else {
2099 // e.g. import.meta
2100 continue;
2101 }
2102
2103 // We should not track Node.js build dependencies
2104 if (dependency.startsWith("node:")) continue;
2105 if (builtinModules.has(dependency)) continue;
2106 // Avoid extra jobs for identical imports
2107 if (added.has(dependency)) continue;
2108
2109 push({
2110 type: RBDT_RESOLVE_ESM_FILE,
2111 context,
2112 path: dependency,
2113 expected: imp.d > -1 ? false : undefined,
2114 issuer: job
2115 });
2116 added.add(dependency);
2117 } catch (err1) {
2118 logger.warn(
2119 `Parsing of ${path} for build dependencies failed at 'import(${source.slice(
2120 imp.s,
2121 imp.e
2122 )})'.\n` +
2123 "Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
2124 );
2125 logger.debug(pathToString(job));
2126 logger.debug(/** @type {Error} */ (err1).stack);
2127 }
2128 }
2129 } catch (err2) {
2130 logger.warn(
2131 `Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
2132 );
2133 logger.debug(pathToString(job));
2134 logger.debug(/** @type {Error} */ (err2).stack);
2135 }
2136 process.nextTick(callback);
2137 });
2138 }, callback);
2139 break;
2140 } else {
2141 logger.log(
2142 `Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
2143 );
2144 logger.debug(pathToString(job));
2145 }
2146 process.nextTick(callback);
2147 break;
2148 }
2149 case RBDT_DIRECTORY_DEPENDENCIES: {
2150 const match =
2151 /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(path);
2152 const packagePath = match ? match[1] : path;
2153 const packageJson = join(this.fs, packagePath, "package.json");
2154 this.fs.readFile(packageJson, (err, content) => {
2155 if (err) {
2156 if (err.code === "ENOENT") {
2157 resolveMissing.add(packageJson);
2158 const parent = dirname(this.fs, packagePath);
2159 if (parent !== packagePath) {
2160 push({
2161 type: RBDT_DIRECTORY_DEPENDENCIES,
2162 context: undefined,
2163 path: parent,
2164 expected: undefined,
2165 issuer: job
2166 });
2167 }
2168 callback();
2169 return;
2170 }
2171 return callback(err);
2172 }
2173 resolveFiles.add(packageJson);
2174 /** @type {JsonObject} */
2175 let packageData;
2176 try {
2177 packageData = JSON.parse(
2178 /** @type {Buffer} */
2179 (content).toString("utf8")
2180 );
2181 } catch (parseErr) {
2182 return callback(/** @type {Error} */ (parseErr));
2183 }
2184 const depsObject = packageData.dependencies;
2185 const optionalDepsObject = packageData.optionalDependencies;
2186 /** @type {Set<string>} */
2187 const allDeps = new Set();
2188 /** @type {Set<string>} */
2189 const optionalDeps = new Set();
2190 if (typeof depsObject === "object" && depsObject) {
2191 for (const dep of Object.keys(depsObject)) {
2192 allDeps.add(dep);
2193 }
2194 }
2195 if (
2196 typeof optionalDepsObject === "object" &&
2197 optionalDepsObject
2198 ) {
2199 for (const dep of Object.keys(optionalDepsObject)) {
2200 allDeps.add(dep);
2201 optionalDeps.add(dep);
2202 }
2203 }
2204 for (const dep of allDeps) {
2205 push({
2206 type: RBDT_RESOLVE_DIRECTORY,
2207 context: packagePath,
2208 path: dep,
2209 expected: !optionalDeps.has(dep),
2210 issuer: job
2211 });
2212 }
2213 callback();
2214 });
2215 break;
2216 }
2217 }
2218 },
2219 (err) => {
2220 if (err) return callback(err);
2221 for (const l of fileSymlinks) files.delete(l);
2222 for (const l of directorySymlinks) directories.delete(l);
2223 for (const k of invalidResolveResults) resolveResults.delete(k);
2224 callback(null, {
2225 files,
2226 directories,
2227 missing,
2228 resolveResults,
2229 resolveDependencies: {
2230 files: resolveFiles,
2231 directories: resolveDirectories,
2232 missing: resolveMissing
2233 }
2234 });
2235 }
2236 );
2237 }
2238
2239 /**
2240 * Checks resolve results valid.
2241 * @param {ResolveResults} resolveResults results from resolving
2242 * @param {(err?: Error | null, result?: boolean) => void} callback callback with true when resolveResults resolve the same way
2243 * @returns {void}
2244 */
2245 checkResolveResultsValid(resolveResults, callback) {
2246 const { resolveCjs, resolveCjsAsChild, resolveEsm, resolveContext } =
2247 this._createBuildDependenciesResolvers();
2248 asyncLib.eachLimit(
2249 resolveResults,
2250 20,
2251 ([key, expectedResult], callback) => {
2252 const [type, context, path] = key.split("\n");
2253 switch (type) {
2254 case "d":
2255 resolveContext(context, path, {}, (err, _, result) => {
2256 if (expectedResult === false) {
2257 return callback(err ? undefined : INVALID);
2258 }
2259 if (err) return callback(err);
2260 const resultPath = /** @type {ResolveRequest} */ (result).path;
2261 if (resultPath !== expectedResult) return callback(INVALID);
2262 callback();
2263 });
2264 break;
2265 case "f":
2266 resolveCjs(context, path, {}, (err, _, result) => {
2267 if (expectedResult === false) {
2268 return callback(err ? undefined : INVALID);
2269 }
2270 if (err) return callback(err);
2271 const resultPath = /** @type {ResolveRequest} */ (result).path;
2272 if (resultPath !== expectedResult) return callback(INVALID);
2273 callback();
2274 });
2275 break;
2276 case "c":
2277 resolveCjsAsChild(context, path, {}, (err, _, result) => {
2278 if (expectedResult === false) {
2279 return callback(err ? undefined : INVALID);
2280 }
2281 if (err) return callback(err);
2282 const resultPath = /** @type {ResolveRequest} */ (result).path;
2283 if (resultPath !== expectedResult) return callback(INVALID);
2284 callback();
2285 });
2286 break;
2287 case "e":
2288 resolveEsm(context, path, {}, (err, _, result) => {
2289 if (expectedResult === false) {
2290 return callback(err ? undefined : INVALID);
2291 }
2292 if (err) return callback(err);
2293 const resultPath = /** @type {ResolveRequest} */ (result).path;
2294 if (resultPath !== expectedResult) return callback(INVALID);
2295 callback();
2296 });
2297 break;
2298 default:
2299 callback(new Error("Unexpected type in resolve result key"));
2300 break;
2301 }
2302 },
2303 /**
2304 * Processes the provided err.
2305 * @param {Error | typeof INVALID=} err error or invalid flag
2306 * @returns {void}
2307 */
2308 /** @type {import("neo-async").ErrorCallback<Error | typeof INVALID>} */ (
2309 (err) => {
2310 if (err === INVALID) {
2311 return callback(null, false);
2312 }
2313 if (err) {
2314 return callback(err);
2315 }
2316 return callback(null, true);
2317 }
2318 )
2319 );
2320 }
2321
2322 /**
2323 * Creates a snapshot.
2324 * @param {number | null | undefined} startTime when processing the files has started
2325 * @param {Iterable<string> | null | undefined} files all files
2326 * @param {Iterable<string> | null | undefined} directories all directories
2327 * @param {Iterable<string> | null | undefined} missing all missing files or directories
2328 * @param {SnapshotOptions | null | undefined} options options object (for future extensions)
2329 * @param {(err: WebpackError | null, snapshot: Snapshot | null) => void} callback callback function
2330 * @returns {void}
2331 */
2332 createSnapshot(startTime, files, directories, missing, options, callback) {
2333 /** @type {FileTimestamps} */
2334 const fileTimestamps = new Map();
2335 /** @type {FileHashes} */
2336 const fileHashes = new Map();
2337 /** @type {FileTshs} */
2338 const fileTshs = new Map();
2339 /** @type {ContextTimestamps} */
2340 const contextTimestamps = new Map();
2341 /** @type {ContextHashes} */
2342 const contextHashes = new Map();
2343 /** @type {ContextTshs} */
2344 const contextTshs = new Map();
2345 /** @type {MissingExistence} */
2346 const missingExistence = new Map();
2347 /** @type {ManagedItemInfo} */
2348 const managedItemInfo = new Map();
2349 /** @type {ManagedFiles} */
2350 const managedFiles = new Set();
2351 /** @type {ManagedContexts} */
2352 const managedContexts = new Set();
2353 /** @type {ManagedMissing} */
2354 const managedMissing = new Set();
2355 /** @type {Children} */
2356 const children = new Set();
2357
2358 const snapshot = new Snapshot();
2359 if (startTime) snapshot.setStartTime(startTime);
2360
2361 /** @type {Set<string>} */
2362 const managedItems = new Set();
2363
2364 /** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
2365 const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
2366
2367 let jobs = 1;
2368 const jobDone = () => {
2369 if (--jobs === 0) {
2370 if (fileTimestamps.size !== 0) {
2371 snapshot.setFileTimestamps(fileTimestamps);
2372 }
2373 if (fileHashes.size !== 0) {
2374 snapshot.setFileHashes(fileHashes);
2375 }
2376 if (fileTshs.size !== 0) {
2377 snapshot.setFileTshs(fileTshs);
2378 }
2379 if (contextTimestamps.size !== 0) {
2380 snapshot.setContextTimestamps(contextTimestamps);
2381 }
2382 if (contextHashes.size !== 0) {
2383 snapshot.setContextHashes(contextHashes);
2384 }
2385 if (contextTshs.size !== 0) {
2386 snapshot.setContextTshs(contextTshs);
2387 }
2388 if (missingExistence.size !== 0) {
2389 snapshot.setMissingExistence(missingExistence);
2390 }
2391 if (managedItemInfo.size !== 0) {
2392 snapshot.setManagedItemInfo(managedItemInfo);
2393 }
2394 this._managedFilesOptimization.optimize(snapshot, managedFiles);
2395 if (managedFiles.size !== 0) {
2396 snapshot.setManagedFiles(managedFiles);
2397 }
2398 this._managedContextsOptimization.optimize(snapshot, managedContexts);
2399 if (managedContexts.size !== 0) {
2400 snapshot.setManagedContexts(managedContexts);
2401 }
2402 this._managedMissingOptimization.optimize(snapshot, managedMissing);
2403 if (managedMissing.size !== 0) {
2404 snapshot.setManagedMissing(managedMissing);
2405 }
2406 if (children.size !== 0) {
2407 snapshot.setChildren(children);
2408 }
2409 this._snapshotCache.set(snapshot, true);
2410 this._statCreatedSnapshots++;
2411
2412 callback(null, snapshot);
2413 }
2414 };
2415 const jobError = () => {
2416 if (jobs > 0) {
2417 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
2418 jobs = -100000000;
2419 callback(null, null);
2420 }
2421 };
2422 /**
2423 * Checks true when managed.
2424 * @param {string} path path
2425 * @param {ManagedFiles} managedSet managed set
2426 * @returns {boolean} true when managed
2427 */
2428 const checkManaged = (path, managedSet) => {
2429 for (const unmanagedPath of this.unmanagedPathsRegExps) {
2430 if (unmanagedPath.test(path)) return false;
2431 }
2432 for (const unmanagedPath of this.unmanagedPathsWithSlash) {
2433 if (path.startsWith(unmanagedPath)) return false;
2434 }
2435 for (const immutablePath of this.immutablePathsRegExps) {
2436 if (immutablePath.test(path)) {
2437 managedSet.add(path);
2438 return true;
2439 }
2440 }
2441 for (const immutablePath of this.immutablePathsWithSlash) {
2442 if (path.startsWith(immutablePath)) {
2443 managedSet.add(path);
2444 return true;
2445 }
2446 }
2447 for (const managedPath of this.managedPathsRegExps) {
2448 const match = managedPath.exec(path);
2449 if (match) {
2450 const managedItem = getManagedItem(match[1], path);
2451 if (managedItem) {
2452 managedItems.add(managedItem);
2453 managedSet.add(path);
2454 return true;
2455 }
2456 }
2457 }
2458 for (const managedPath of this.managedPathsWithSlash) {
2459 if (path.startsWith(managedPath)) {
2460 const managedItem = getManagedItem(managedPath, path);
2461 if (managedItem) {
2462 managedItems.add(managedItem);
2463 managedSet.add(path);
2464 return true;
2465 }
2466 }
2467 }
2468 return false;
2469 };
2470 /**
2471 * Capture non managed.
2472 * @param {Iterable<string>} items items
2473 * @param {Set<string>} managedSet managed set
2474 * @returns {Set<string>} result
2475 */
2476 const captureNonManaged = (items, managedSet) => {
2477 /** @type {Set<string>} */
2478 const capturedItems = new Set();
2479 for (const path of items) {
2480 if (!checkManaged(path, managedSet)) capturedItems.add(path);
2481 }
2482 return capturedItems;
2483 };
2484 /**
2485 * Process captured files.
2486 * @param {ManagedFiles} capturedFiles captured files
2487 */
2488 const processCapturedFiles = (capturedFiles) => {
2489 if (capturedFiles.size === 0) {
2490 return;
2491 }
2492 switch (mode) {
2493 case 3:
2494 this._fileTshsOptimization.optimize(snapshot, capturedFiles);
2495 for (const path of capturedFiles) {
2496 const cache = this._fileTshs.get(path);
2497 if (cache !== undefined) {
2498 fileTshs.set(path, cache);
2499 } else {
2500 jobs++;
2501 this._getFileTimestampAndHash(path, (err, entry) => {
2502 if (err) {
2503 if (this.logger) {
2504 this.logger.debug(
2505 `Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
2506 );
2507 }
2508 jobError();
2509 } else {
2510 fileTshs.set(path, /** @type {TimestampAndHash} */ (entry));
2511 jobDone();
2512 }
2513 });
2514 }
2515 }
2516 break;
2517 case 2:
2518 this._fileHashesOptimization.optimize(snapshot, capturedFiles);
2519 for (const path of capturedFiles) {
2520 const cache = this._fileHashes.get(path);
2521 if (cache !== undefined) {
2522 fileHashes.set(path, cache);
2523 } else {
2524 jobs++;
2525 this.fileHashQueue.add(path, (err, entry) => {
2526 if (err) {
2527 if (this.logger) {
2528 this.logger.debug(
2529 `Error snapshotting file hash of ${path}: ${err.stack}`
2530 );
2531 }
2532 jobError();
2533 } else {
2534 fileHashes.set(path, /** @type {string} */ (entry));
2535 jobDone();
2536 }
2537 });
2538 }
2539 }
2540 break;
2541 case 1:
2542 this._fileTimestampsOptimization.optimize(snapshot, capturedFiles);
2543 for (const path of capturedFiles) {
2544 const cache = this._fileTimestamps.get(path);
2545 if (cache !== undefined && !isExistenceOnly(cache)) {
2546 if (cache !== "ignore") {
2547 fileTimestamps.set(
2548 path,
2549 /** @type {FileSystemInfoEntry | null} */ (cache)
2550 );
2551 }
2552 } else {
2553 jobs++;
2554 this.fileTimestampQueue.add(path, (err, entry) => {
2555 if (err) {
2556 if (this.logger) {
2557 this.logger.debug(
2558 `Error snapshotting file timestamp of ${path}: ${err.stack}`
2559 );
2560 }
2561 jobError();
2562 } else {
2563 fileTimestamps.set(
2564 path,
2565 /** @type {FileSystemInfoEntry} */
2566 (entry)
2567 );
2568 jobDone();
2569 }
2570 });
2571 }
2572 }
2573 break;
2574 }
2575 };
2576 if (files) {
2577 processCapturedFiles(captureNonManaged(files, managedFiles));
2578 }
2579 /**
2580 * Process captured directories.
2581 * @param {ManagedContexts} capturedDirectories captured directories
2582 */
2583 const processCapturedDirectories = (capturedDirectories) => {
2584 if (capturedDirectories.size === 0) {
2585 return;
2586 }
2587 switch (mode) {
2588 case 3:
2589 this._contextTshsOptimization.optimize(snapshot, capturedDirectories);
2590 for (const path of capturedDirectories) {
2591 const cache = this._contextTshs.get(path);
2592 /** @type {ResolvedContextTimestampAndHash | null | undefined} */
2593 let resolved;
2594 if (
2595 cache !== undefined &&
2596 (resolved = getResolvedTimestamp(cache)) !== undefined
2597 ) {
2598 contextTshs.set(path, resolved);
2599 } else {
2600 jobs++;
2601 /**
2602 * Processes the provided err.
2603 * @param {(WebpackError | null)=} err error
2604 * @param {(ResolvedContextTimestampAndHash | null)=} entry entry
2605 * @returns {void}
2606 */
2607 const callback = (err, entry) => {
2608 if (err) {
2609 if (this.logger) {
2610 this.logger.debug(
2611 `Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
2612 );
2613 }
2614 jobError();
2615 } else {
2616 contextTshs.set(
2617 path,
2618 /** @type {ResolvedContextTimestampAndHash | null} */
2619 (entry)
2620 );
2621 jobDone();
2622 }
2623 };
2624 if (cache !== undefined) {
2625 this._resolveContextTsh(cache, callback);
2626 } else {
2627 this.getContextTsh(path, callback);
2628 }
2629 }
2630 }
2631 break;
2632 case 2:
2633 this._contextHashesOptimization.optimize(
2634 snapshot,
2635 capturedDirectories
2636 );
2637 for (const path of capturedDirectories) {
2638 const cache = this._contextHashes.get(path);
2639 /** @type {undefined | null | string} */
2640 let resolved;
2641 if (
2642 cache !== undefined &&
2643 (resolved = getResolvedHash(cache)) !== undefined
2644 ) {
2645 contextHashes.set(path, resolved);
2646 } else {
2647 jobs++;
2648 /**
2649 * Processes the provided err.
2650 * @param {(WebpackError | null)=} err err
2651 * @param {string=} entry entry
2652 */
2653 const callback = (err, entry) => {
2654 if (err) {
2655 if (this.logger) {
2656 this.logger.debug(
2657 `Error snapshotting context hash of ${path}: ${err.stack}`
2658 );
2659 }
2660 jobError();
2661 } else {
2662 contextHashes.set(path, /** @type {string} */ (entry));
2663 jobDone();
2664 }
2665 };
2666 if (cache !== undefined) {
2667 this._resolveContextHash(cache, callback);
2668 } else {
2669 this.getContextHash(path, callback);
2670 }
2671 }
2672 }
2673 break;
2674 case 1:
2675 this._contextTimestampsOptimization.optimize(
2676 snapshot,
2677 capturedDirectories
2678 );
2679 for (const path of capturedDirectories) {
2680 const cache = this._contextTimestamps.get(path);
2681 if (cache === "ignore") continue;
2682 /** @type {ContextFileSystemInfoEntry | null | undefined} */
2683 const usableCache =
2684 cache === undefined || isExistenceOnly(cache)
2685 ? undefined
2686 : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
2687 // A non-null cache entry without `timestampHash` cannot be
2688 // used to populate the snapshot — the snapshot would then
2689 // miss directory-change detection, since validity relies on
2690 // `timestampHash`. Re-read the directory in that case.
2691 const cacheLacksHash =
2692 usableCache !== undefined &&
2693 usableCache !== null &&
2694 usableCache.timestampHash === undefined;
2695 /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
2696 let resolved;
2697 if (
2698 usableCache !== undefined &&
2699 !cacheLacksHash &&
2700 (resolved = getResolvedTimestamp(usableCache)) !== undefined
2701 ) {
2702 contextTimestamps.set(path, resolved);
2703 } else {
2704 jobs++;
2705 /**
2706 * Processes the provided err.
2707 * @param {(WebpackError | null)=} err error
2708 * @param {ResolvedContextTimestamp=} entry entry
2709 * @returns {void}
2710 */
2711 const callback = (err, entry) => {
2712 if (err) {
2713 if (this.logger) {
2714 this.logger.debug(
2715 `Error snapshotting context timestamp of ${path}: ${err.stack}`
2716 );
2717 }
2718 jobError();
2719 } else {
2720 contextTimestamps.set(
2721 path,
2722 /** @type {ResolvedContextFileSystemInfoEntry | null} */
2723 (entry)
2724 );
2725 jobDone();
2726 }
2727 };
2728 if (cacheLacksHash) {
2729 this._readFreshContextTimestamp(path, callback);
2730 } else if (usableCache !== undefined && usableCache !== null) {
2731 this._resolveContextTimestamp(usableCache, callback);
2732 } else {
2733 // Force a fresh on-disk read so the snapshot stores a
2734 // complete entry (with `timestampHash`).
2735 this._readFreshContextTimestamp(path, callback);
2736 }
2737 }
2738 }
2739 break;
2740 }
2741 };
2742 if (directories) {
2743 processCapturedDirectories(
2744 captureNonManaged(directories, managedContexts)
2745 );
2746 }
2747 /**
2748 * Process captured missing.
2749 * @param {ManagedMissing} capturedMissing captured missing
2750 */
2751 const processCapturedMissing = (capturedMissing) => {
2752 if (capturedMissing.size === 0) {
2753 return;
2754 }
2755 this._missingExistenceOptimization.optimize(snapshot, capturedMissing);
2756 for (const path of capturedMissing) {
2757 const cache = this._fileTimestamps.get(path);
2758 if (cache !== undefined && !isExistenceOnly(cache)) {
2759 if (cache !== "ignore") {
2760 missingExistence.set(path, Boolean(cache));
2761 }
2762 } else {
2763 jobs++;
2764 this.fileTimestampQueue.add(path, (err, entry) => {
2765 if (err) {
2766 if (this.logger) {
2767 this.logger.debug(
2768 `Error snapshotting missing timestamp of ${path}: ${err.stack}`
2769 );
2770 }
2771 jobError();
2772 } else {
2773 missingExistence.set(path, Boolean(entry));
2774 jobDone();
2775 }
2776 });
2777 }
2778 }
2779 };
2780 if (missing) {
2781 processCapturedMissing(captureNonManaged(missing, managedMissing));
2782 }
2783 this._managedItemInfoOptimization.optimize(snapshot, managedItems);
2784 for (const path of managedItems) {
2785 const cache = this._managedItems.get(path);
2786 if (cache !== undefined) {
2787 if (!cache.startsWith("*")) {
2788 managedFiles.add(join(this.fs, path, "package.json"));
2789 } else if (cache === "*nested") {
2790 managedMissing.add(join(this.fs, path, "package.json"));
2791 }
2792 managedItemInfo.set(path, cache);
2793 } else {
2794 jobs++;
2795 this.managedItemQueue.add(path, (err, entry) => {
2796 if (err) {
2797 if (this.logger) {
2798 this.logger.debug(
2799 `Error snapshotting managed item ${path}: ${err.stack}`
2800 );
2801 }
2802 jobError();
2803 } else if (entry) {
2804 if (!entry.startsWith("*")) {
2805 managedFiles.add(join(this.fs, path, "package.json"));
2806 } else if (cache === "*nested") {
2807 managedMissing.add(join(this.fs, path, "package.json"));
2808 }
2809 managedItemInfo.set(path, entry);
2810 jobDone();
2811 } else {
2812 // Fallback to normal snapshotting
2813 /**
2814 * Processes the provided set.
2815 * @param {Set<string>} set set
2816 * @param {(set: Set<string>) => void} fn fn
2817 */
2818 const process = (set, fn) => {
2819 if (set.size === 0) return;
2820 /** @type {Set<string>} */
2821 const captured = new Set();
2822 for (const file of set) {
2823 if (file.startsWith(path)) captured.add(file);
2824 }
2825 if (captured.size > 0) fn(captured);
2826 };
2827 process(managedFiles, processCapturedFiles);
2828 process(managedContexts, processCapturedDirectories);
2829 process(managedMissing, processCapturedMissing);
2830 jobDone();
2831 }
2832 });
2833 }
2834 }
2835 jobDone();
2836 }
2837
2838 /**
2839 * Merges the provided values into a single result.
2840 * @param {Snapshot} snapshot1 a snapshot
2841 * @param {Snapshot} snapshot2 a snapshot
2842 * @returns {Snapshot} merged snapshot
2843 */
2844 mergeSnapshots(snapshot1, snapshot2) {
2845 const snapshot = new Snapshot();
2846 if (snapshot1.hasStartTime() && snapshot2.hasStartTime()) {
2847 snapshot.setStartTime(
2848 Math.min(
2849 /** @type {NonNullable<Snapshot["startTime"]>} */
2850 (snapshot1.startTime),
2851 /** @type {NonNullable<Snapshot["startTime"]>} */
2852 (snapshot2.startTime)
2853 )
2854 );
2855 } else if (snapshot2.hasStartTime()) {
2856 snapshot.startTime = snapshot2.startTime;
2857 } else if (snapshot1.hasStartTime()) {
2858 snapshot.startTime = snapshot1.startTime;
2859 }
2860 if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
2861 snapshot.setFileTimestamps(
2862 mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
2863 );
2864 }
2865 if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
2866 snapshot.setFileHashes(
2867 mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
2868 );
2869 }
2870 if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
2871 snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
2872 }
2873 if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
2874 snapshot.setContextTimestamps(
2875 mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
2876 );
2877 }
2878 if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
2879 snapshot.setContextHashes(
2880 mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
2881 );
2882 }
2883 if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
2884 snapshot.setContextTshs(
2885 mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
2886 );
2887 }
2888 if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
2889 snapshot.setMissingExistence(
2890 mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
2891 );
2892 }
2893 if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
2894 snapshot.setManagedItemInfo(
2895 mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
2896 );
2897 }
2898 if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
2899 snapshot.setManagedFiles(
2900 mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
2901 );
2902 }
2903 if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
2904 snapshot.setManagedContexts(
2905 mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
2906 );
2907 }
2908 if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
2909 snapshot.setManagedMissing(
2910 mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
2911 );
2912 }
2913 if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
2914 snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
2915 }
2916 if (
2917 this._snapshotCache.get(snapshot1) === true &&
2918 this._snapshotCache.get(snapshot2) === true
2919 ) {
2920 this._snapshotCache.set(snapshot, true);
2921 }
2922 return snapshot;
2923 }
2924
2925 /**
2926 * Checks snapshot valid.
2927 * @param {Snapshot} snapshot the snapshot made
2928 * @param {CheckSnapshotValidCallback} callback callback function
2929 * @returns {void}
2930 */
2931 checkSnapshotValid(snapshot, callback) {
2932 const cachedResult = this._snapshotCache.get(snapshot);
2933 if (cachedResult !== undefined) {
2934 this._statTestedSnapshotsCached++;
2935 if (typeof cachedResult === "boolean") {
2936 callback(null, cachedResult);
2937 } else {
2938 cachedResult.push(callback);
2939 }
2940 return;
2941 }
2942 this._statTestedSnapshotsNotCached++;
2943 this._checkSnapshotValidNoCache(snapshot, callback);
2944 }
2945
2946 /**
2947 * Check snapshot valid no cache.
2948 * @private
2949 * @param {Snapshot} snapshot the snapshot made
2950 * @param {CheckSnapshotValidCallback} callback callback function
2951 * @returns {void}
2952 */
2953 _checkSnapshotValidNoCache(snapshot, callback) {
2954 /** @type {number | undefined} */
2955 let startTime;
2956 if (snapshot.hasStartTime()) {
2957 startTime = snapshot.startTime;
2958 }
2959 let jobs = 1;
2960 const jobDone = () => {
2961 if (--jobs === 0) {
2962 this._snapshotCache.set(snapshot, true);
2963 callback(null, true);
2964 }
2965 };
2966 const invalid = () => {
2967 if (jobs > 0) {
2968 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
2969 jobs = -100000000;
2970 this._snapshotCache.set(snapshot, false);
2971 callback(null, false);
2972 }
2973 };
2974 /**
2975 * Invalid with error.
2976 * @param {string} path path
2977 * @param {WebpackError} err err
2978 */
2979 const invalidWithError = (path, err) => {
2980 if (this._remainingLogs > 0) {
2981 this._log(path, "error occurred: %s", err);
2982 }
2983 invalid();
2984 };
2985 /**
2986 * Checks true, if ok.
2987 * @param {string} path file path
2988 * @param {string | null} current current hash
2989 * @param {string | null} snap snapshot hash
2990 * @returns {boolean} true, if ok
2991 */
2992 const checkHash = (path, current, snap) => {
2993 if (current !== snap) {
2994 // If hash differ it's invalid
2995 if (this._remainingLogs > 0) {
2996 this._log(path, "hashes differ (%s != %s)", current, snap);
2997 }
2998 return false;
2999 }
3000 return true;
3001 };
3002 /**
3003 * Checks true, if ok.
3004 * @param {string} path file path
3005 * @param {boolean} current current entry
3006 * @param {boolean} snap entry from snapshot
3007 * @returns {boolean} true, if ok
3008 */
3009 const checkExistence = (path, current, snap) => {
3010 if (!current !== !snap) {
3011 // If existence of item differs
3012 // it's invalid
3013 if (this._remainingLogs > 0) {
3014 this._log(
3015 path,
3016 current ? "it didn't exist before" : "it does no longer exist"
3017 );
3018 }
3019 return false;
3020 }
3021 return true;
3022 };
3023 /**
3024 * Checks true, if ok.
3025 * @param {string} path file path
3026 * @param {FileSystemInfoEntry | null} c current entry
3027 * @param {FileSystemInfoEntry | null} s entry from snapshot
3028 * @param {boolean} log log reason
3029 * @returns {boolean} true, if ok
3030 */
3031 const checkFile = (path, c, s, log = true) => {
3032 if (c === s) return true;
3033 if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
3034 if (c) {
3035 // For existing items only
3036 if (typeof startTime === "number" && c.safeTime > startTime) {
3037 // If a change happened after starting reading the item
3038 // this may no longer be valid
3039 if (log && this._remainingLogs > 0) {
3040 this._log(
3041 path,
3042 "it may have changed (%d) after the start time of the snapshot (%d)",
3043 c.safeTime,
3044 startTime
3045 );
3046 }
3047 return false;
3048 }
3049 const snap = /** @type {FileSystemInfoEntry} */ (s);
3050 if (snap.timestamp !== undefined && c.timestamp !== snap.timestamp) {
3051 // If we have a timestamp (it was a file or symlink) and it differs from current timestamp
3052 // it's invalid
3053 if (log && this._remainingLogs > 0) {
3054 this._log(
3055 path,
3056 "timestamps differ (%d != %d)",
3057 c.timestamp,
3058 snap.timestamp
3059 );
3060 }
3061 return false;
3062 }
3063 }
3064 return true;
3065 };
3066 /**
3067 * Checks true, if ok.
3068 * @param {string} path file path
3069 * @param {ResolvedContextFileSystemInfoEntry | null} c current entry
3070 * @param {ResolvedContextFileSystemInfoEntry | null} s entry from snapshot
3071 * @param {boolean} log log reason
3072 * @returns {boolean} true, if ok
3073 */
3074 const checkContext = (path, c, s, log = true) => {
3075 if (c === s) return true;
3076 if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
3077 if (c) {
3078 // For existing items only
3079 if (typeof startTime === "number" && c.safeTime > startTime) {
3080 // If a change happened after starting reading the item
3081 // this may no longer be valid
3082 if (log && this._remainingLogs > 0) {
3083 this._log(
3084 path,
3085 "it may have changed (%d) after the start time of the snapshot (%d)",
3086 c.safeTime,
3087 startTime
3088 );
3089 }
3090 return false;
3091 }
3092 const snap = /** @type {ResolvedContextFileSystemInfoEntry} */ (s);
3093 if (
3094 snap.timestampHash !== undefined &&
3095 c.timestampHash !== snap.timestampHash
3096 ) {
3097 // If we have a timestampHash (it was a directory) and it differs from current timestampHash
3098 // it's invalid
3099 if (log && this._remainingLogs > 0) {
3100 this._log(
3101 path,
3102 "timestamps hashes differ (%s != %s)",
3103 c.timestampHash,
3104 snap.timestampHash
3105 );
3106 }
3107 return false;
3108 }
3109 }
3110 return true;
3111 };
3112 if (snapshot.hasChildren()) {
3113 /**
3114 * Processes the provided err.
3115 * @param {(WebpackError | null)=} err err
3116 * @param {boolean=} result result
3117 * @returns {void}
3118 */
3119 const childCallback = (err, result) => {
3120 if (err || !result) return invalid();
3121 jobDone();
3122 };
3123 for (const child of /** @type {Children} */ (snapshot.children)) {
3124 const cache = this._snapshotCache.get(child);
3125 if (cache !== undefined) {
3126 this._statTestedChildrenCached++;
3127 /* istanbul ignore else */
3128 if (typeof cache === "boolean") {
3129 if (cache === false) {
3130 invalid();
3131 return;
3132 }
3133 } else {
3134 jobs++;
3135 cache.push(childCallback);
3136 }
3137 } else {
3138 this._statTestedChildrenNotCached++;
3139 jobs++;
3140 this._checkSnapshotValidNoCache(child, childCallback);
3141 }
3142 }
3143 }
3144 if (snapshot.hasFileTimestamps()) {
3145 const fileTimestamps =
3146 /** @type {FileTimestamps} */
3147 (snapshot.fileTimestamps);
3148 this._statTestedEntries += fileTimestamps.size;
3149 for (const [path, ts] of fileTimestamps) {
3150 const cache = this._fileTimestamps.get(path);
3151 if (cache !== undefined && !isExistenceOnly(cache)) {
3152 if (
3153 cache !== "ignore" &&
3154 !checkFile(
3155 path,
3156 /** @type {FileSystemInfoEntry | null} */ (cache),
3157 ts
3158 )
3159 ) {
3160 invalid();
3161 return;
3162 }
3163 } else {
3164 jobs++;
3165 this.fileTimestampQueue.add(path, (err, entry) => {
3166 if (err) return invalidWithError(path, err);
3167 if (
3168 !checkFile(
3169 path,
3170 /** @type {FileSystemInfoEntry | null} */ (entry),
3171 ts
3172 )
3173 ) {
3174 invalid();
3175 } else {
3176 jobDone();
3177 }
3178 });
3179 }
3180 }
3181 }
3182 /**
3183 * Process file hash snapshot.
3184 * @param {string} path file path
3185 * @param {string | null} hash hash
3186 */
3187 const processFileHashSnapshot = (path, hash) => {
3188 const cache = this._fileHashes.get(path);
3189 if (cache !== undefined) {
3190 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
3191 invalid();
3192 }
3193 } else {
3194 jobs++;
3195 this.fileHashQueue.add(path, (err, entry) => {
3196 if (err) return invalidWithError(path, err);
3197 if (!checkHash(path, /** @type {string} */ (entry), hash)) {
3198 invalid();
3199 } else {
3200 jobDone();
3201 }
3202 });
3203 }
3204 };
3205 if (snapshot.hasFileHashes()) {
3206 const fileHashes = /** @type {FileHashes} */ (snapshot.fileHashes);
3207 this._statTestedEntries += fileHashes.size;
3208 for (const [path, hash] of fileHashes) {
3209 processFileHashSnapshot(path, hash);
3210 }
3211 }
3212 if (snapshot.hasFileTshs()) {
3213 const fileTshs = /** @type {FileTshs} */ (snapshot.fileTshs);
3214 this._statTestedEntries += fileTshs.size;
3215 for (const [path, tsh] of fileTshs) {
3216 if (typeof tsh === "string") {
3217 processFileHashSnapshot(path, tsh);
3218 } else {
3219 const cache = this._fileTimestamps.get(path);
3220 if (cache !== undefined && !isExistenceOnly(cache)) {
3221 if (
3222 cache === "ignore" ||
3223 !checkFile(
3224 path,
3225 /** @type {FileSystemInfoEntry | null} */ (cache),
3226 tsh,
3227 false
3228 )
3229 ) {
3230 processFileHashSnapshot(path, tsh && tsh.hash);
3231 }
3232 } else {
3233 jobs++;
3234 this.fileTimestampQueue.add(path, (err, entry) => {
3235 if (err) return invalidWithError(path, err);
3236 if (
3237 !checkFile(
3238 path,
3239 /** @type {FileSystemInfoEntry | null} */
3240 (entry),
3241 tsh,
3242 false
3243 )
3244 ) {
3245 processFileHashSnapshot(path, tsh && tsh.hash);
3246 }
3247 jobDone();
3248 });
3249 }
3250 }
3251 }
3252 }
3253 if (snapshot.hasContextTimestamps()) {
3254 const contextTimestamps =
3255 /** @type {ContextTimestamps} */
3256 (snapshot.contextTimestamps);
3257 this._statTestedEntries += contextTimestamps.size;
3258 for (const [path, ts] of contextTimestamps) {
3259 const cache = this._contextTimestamps.get(path);
3260 if (cache === "ignore") continue;
3261 // Treat existence-only entries (`{}` from watchpack) as a cache
3262 // miss — they carry no time info, so we cannot compare them to
3263 // the snapshot.
3264 /** @type {ContextFileSystemInfoEntry | null | undefined} */
3265 const usableCache =
3266 cache === undefined || isExistenceOnly(cache)
3267 ? undefined
3268 : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
3269 // A non-null cache entry that lacks `timestampHash` while the
3270 // snapshot has one cannot be used either; we re-read the
3271 // directory through the disk-backed queue instead.
3272 const cacheLacksHash =
3273 usableCache !== undefined &&
3274 usableCache !== null &&
3275 usableCache.timestampHash === undefined &&
3276 ts !== null &&
3277 ts.timestampHash !== undefined;
3278 /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
3279 let resolved;
3280 if (
3281 usableCache !== undefined &&
3282 !cacheLacksHash &&
3283 (resolved = getResolvedTimestamp(usableCache)) !== undefined
3284 ) {
3285 if (!checkContext(path, resolved, ts)) {
3286 invalid();
3287 return;
3288 }
3289 } else {
3290 jobs++;
3291 /**
3292 * Processes the provided err.
3293 * @param {(WebpackError | null)=} err error
3294 * @param {ResolvedContextTimestamp=} entry entry
3295 * @returns {void}
3296 */
3297 const callback = (err, entry) => {
3298 if (err) return invalidWithError(path, err);
3299 if (
3300 !checkContext(
3301 path,
3302 /** @type {ResolvedContextFileSystemInfoEntry | null} */
3303 (entry),
3304 ts
3305 )
3306 ) {
3307 invalid();
3308 } else {
3309 jobDone();
3310 }
3311 };
3312 if (cacheLacksHash) {
3313 this._readFreshContextTimestamp(path, callback);
3314 } else if (usableCache !== undefined && usableCache !== null) {
3315 this._resolveContextTimestamp(usableCache, callback);
3316 } else {
3317 this.getContextTimestamp(path, callback);
3318 }
3319 }
3320 }
3321 }
3322 /**
3323 * Process context hash snapshot.
3324 * @param {string} path path
3325 * @param {string | null} hash hash
3326 */
3327 const processContextHashSnapshot = (path, hash) => {
3328 const cache = this._contextHashes.get(path);
3329 /** @type {undefined | null | string} */
3330 let resolved;
3331 if (
3332 cache !== undefined &&
3333 (resolved = getResolvedHash(cache)) !== undefined
3334 ) {
3335 if (!checkHash(path, resolved, hash)) {
3336 invalid();
3337 }
3338 } else {
3339 jobs++;
3340 /**
3341 * Processes the provided err.
3342 * @param {(WebpackError | null)=} err err
3343 * @param {string=} entry entry
3344 * @returns {void}
3345 */
3346 const callback = (err, entry) => {
3347 if (err) return invalidWithError(path, err);
3348 if (!checkHash(path, /** @type {string} */ (entry), hash)) {
3349 invalid();
3350 } else {
3351 jobDone();
3352 }
3353 };
3354 if (cache !== undefined) {
3355 this._resolveContextHash(cache, callback);
3356 } else {
3357 this.getContextHash(path, callback);
3358 }
3359 }
3360 };
3361 if (snapshot.hasContextHashes()) {
3362 const contextHashes =
3363 /** @type {ContextHashes} */
3364 (snapshot.contextHashes);
3365 this._statTestedEntries += contextHashes.size;
3366 for (const [path, hash] of contextHashes) {
3367 processContextHashSnapshot(path, hash);
3368 }
3369 }
3370 if (snapshot.hasContextTshs()) {
3371 const contextTshs = /** @type {ContextTshs} */ (snapshot.contextTshs);
3372 this._statTestedEntries += contextTshs.size;
3373 for (const [path, tsh] of contextTshs) {
3374 if (typeof tsh === "string") {
3375 processContextHashSnapshot(path, tsh);
3376 } else {
3377 const cache = this._contextTimestamps.get(path);
3378 if (cache === "ignore") continue;
3379 // See the matching block in `hasContextTimestamps` above.
3380 /** @type {ContextFileSystemInfoEntry | null | undefined} */
3381 const usableCache =
3382 cache === undefined || isExistenceOnly(cache)
3383 ? undefined
3384 : /** @type {ContextFileSystemInfoEntry | null} */ (cache);
3385 const cacheLacksHash =
3386 usableCache !== undefined &&
3387 usableCache !== null &&
3388 usableCache.timestampHash === undefined &&
3389 tsh !== null &&
3390 tsh.timestampHash !== undefined;
3391 /** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
3392 let resolved;
3393 if (
3394 usableCache !== undefined &&
3395 !cacheLacksHash &&
3396 (resolved = getResolvedTimestamp(usableCache)) !== undefined
3397 ) {
3398 if (!checkContext(path, resolved, tsh, false)) {
3399 processContextHashSnapshot(path, tsh && tsh.hash);
3400 }
3401 } else {
3402 jobs++;
3403 /**
3404 * Processes the provided err.
3405 * @param {(WebpackError | null)=} err error
3406 * @param {ResolvedContextTimestamp=} entry entry
3407 * @returns {void}
3408 */
3409 const callback = (err, entry) => {
3410 if (err) return invalidWithError(path, err);
3411 if (
3412 !checkContext(
3413 path,
3414 // TODO: test with `"ignore"`
3415 /** @type {ResolvedContextFileSystemInfoEntry | null} */
3416 (entry),
3417 tsh,
3418 false
3419 )
3420 ) {
3421 processContextHashSnapshot(path, tsh && tsh.hash);
3422 }
3423 jobDone();
3424 };
3425 if (cacheLacksHash) {
3426 this._readFreshContextTimestamp(path, callback);
3427 } else if (usableCache !== undefined && usableCache !== null) {
3428 this._resolveContextTimestamp(usableCache, callback);
3429 } else {
3430 this.getContextTimestamp(path, callback);
3431 }
3432 }
3433 }
3434 }
3435 }
3436 if (snapshot.hasMissingExistence()) {
3437 const missingExistence =
3438 /** @type {MissingExistence} */
3439 (snapshot.missingExistence);
3440 this._statTestedEntries += missingExistence.size;
3441 for (const [path, existence] of missingExistence) {
3442 const cache = this._fileTimestamps.get(path);
3443 if (cache !== undefined && !isExistenceOnly(cache)) {
3444 if (
3445 cache !== "ignore" &&
3446 !checkExistence(path, Boolean(cache), Boolean(existence))
3447 ) {
3448 invalid();
3449 return;
3450 }
3451 } else {
3452 jobs++;
3453 this.fileTimestampQueue.add(path, (err, entry) => {
3454 if (err) return invalidWithError(path, err);
3455 if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
3456 invalid();
3457 } else {
3458 jobDone();
3459 }
3460 });
3461 }
3462 }
3463 }
3464 if (snapshot.hasManagedItemInfo()) {
3465 const managedItemInfo =
3466 /** @type {ManagedItemInfo} */
3467 (snapshot.managedItemInfo);
3468 this._statTestedEntries += managedItemInfo.size;
3469 for (const [path, info] of managedItemInfo) {
3470 const cache = this._managedItems.get(path);
3471 if (cache !== undefined) {
3472 if (!checkHash(path, cache, info)) {
3473 invalid();
3474 return;
3475 }
3476 } else {
3477 jobs++;
3478 this.managedItemQueue.add(path, (err, entry) => {
3479 if (err) return invalidWithError(path, err);
3480 if (!checkHash(path, /** @type {string} */ (entry), info)) {
3481 invalid();
3482 } else {
3483 jobDone();
3484 }
3485 });
3486 }
3487 }
3488 }
3489 jobDone();
3490
3491 // if there was an async action
3492 // try to join multiple concurrent request for this snapshot
3493 if (jobs > 0) {
3494 const callbacks = [callback];
3495 callback = (err, result) => {
3496 for (const callback of callbacks) callback(err, result);
3497 };
3498 this._snapshotCache.set(snapshot, callbacks);
3499 }
3500 }
3501
3502 /**
3503 * @private
3504 * @type {Processor<string, FileSystemInfoEntry>}
3505 */
3506 _readFileTimestamp(path, callback) {
3507 this.fs.stat(path, (err, _stat) => {
3508 if (err) {
3509 if (err.code === "ENOENT") {
3510 this._fileTimestamps.set(path, null);
3511 this._cachedDeprecatedFileTimestamps = undefined;
3512 return callback(null, null);
3513 }
3514 return callback(/** @type {WebpackError} */ (err));
3515 }
3516 const stat = /** @type {IStats} */ (_stat);
3517 /** @type {FileSystemInfoEntry} */
3518 let ts;
3519 if (stat.isDirectory()) {
3520 ts = {
3521 safeTime: 0,
3522 timestamp: undefined
3523 };
3524 } else {
3525 const mtime = Number(stat.mtime);
3526
3527 if (mtime) applyMtime(mtime);
3528
3529 ts = {
3530 safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
3531 timestamp: mtime
3532 };
3533 }
3534
3535 this._fileTimestamps.set(path, ts);
3536 this._cachedDeprecatedFileTimestamps = undefined;
3537
3538 callback(null, ts);
3539 });
3540 }
3541
3542 /**
3543 * @private
3544 * @type {Processor<string, string>}
3545 */
3546 _readFileHash(path, callback) {
3547 this.fs.readFile(path, (err, content) => {
3548 if (err) {
3549 if (err.code === "EISDIR") {
3550 this._fileHashes.set(path, "directory");
3551 return callback(null, "directory");
3552 }
3553 if (err.code === "ENOENT") {
3554 this._fileHashes.set(path, null);
3555 return callback(null, null);
3556 }
3557 if (err.code === "ERR_FS_FILE_TOO_LARGE") {
3558 /** @type {Logger} */
3559 (this.logger).warn(`Ignoring ${path} for hashing as it's very large`);
3560 this._fileHashes.set(path, "too large");
3561 return callback(null, "too large");
3562 }
3563 return callback(/** @type {WebpackError} */ (err));
3564 }
3565
3566 const hash = createHash(this._hashFunction);
3567
3568 hash.update(/** @type {string | Buffer} */ (content));
3569
3570 const digest = hash.digest("hex");
3571
3572 this._fileHashes.set(path, digest);
3573
3574 callback(null, digest);
3575 });
3576 }
3577
3578 /**
3579 * Get file timestamp and hash.
3580 * @private
3581 * @param {string} path path
3582 * @param {(err: WebpackError | null, timestampAndHash?: TimestampAndHash | string) => void} callback callback
3583 */
3584 _getFileTimestampAndHash(path, callback) {
3585 /**
3586 * Continue with hash.
3587 * @param {string} hash hash
3588 * @returns {void}
3589 */
3590 const continueWithHash = (hash) => {
3591 const cache = this._fileTimestamps.get(path);
3592 if (cache !== undefined) {
3593 if (cache !== "ignore") {
3594 /** @type {TimestampAndHash} */
3595 const result = {
3596 .../** @type {FileSystemInfoEntry} */ (cache),
3597 hash
3598 };
3599 this._fileTshs.set(path, result);
3600 return callback(null, result);
3601 }
3602 this._fileTshs.set(path, hash);
3603 return callback(null, hash);
3604 }
3605 this.fileTimestampQueue.add(path, (err, entry) => {
3606 if (err) {
3607 return callback(err);
3608 }
3609 /** @type {TimestampAndHash} */
3610 const result = {
3611 .../** @type {FileSystemInfoEntry} */ (entry),
3612 hash
3613 };
3614 this._fileTshs.set(path, result);
3615 return callback(null, result);
3616 });
3617 };
3618
3619 const cache = this._fileHashes.get(path);
3620 if (cache !== undefined) {
3621 continueWithHash(/** @type {string} */ (cache));
3622 } else {
3623 this.fileHashQueue.add(path, (err, entry) => {
3624 if (err) {
3625 return callback(err);
3626 }
3627 continueWithHash(/** @type {string} */ (entry));
3628 });
3629 }
3630 }
3631
3632 /**
3633 * Processes the provided object.
3634 * @private
3635 * @template T
3636 * @template ItemType
3637 * @param {object} options options
3638 * @param {string} options.path path
3639 * @param {(value: string) => ItemType} options.fromImmutablePath called when context item is an immutable path
3640 * @param {(value: string) => ItemType} options.fromManagedItem called when context item is a managed path
3641 * @param {(value: string, result: string, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromSymlink called when context item is a symlink
3642 * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType | null) => void) => void} options.fromFile called when context item is a file
3643 * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromDirectory called when context item is a directory
3644 * @param {(arr: string[], arr1: ItemType[]) => T} options.reduce called from all context items
3645 * @param {(err?: Error | null, result?: T | null) => void} callback callback
3646 */
3647 _readContext(
3648 {
3649 path,
3650 fromImmutablePath,
3651 fromManagedItem,
3652 fromSymlink,
3653 fromFile,
3654 fromDirectory,
3655 reduce
3656 },
3657 callback
3658 ) {
3659 this.fs.readdir(path, (err, _files) => {
3660 if (err) {
3661 if (err.code === "ENOENT") {
3662 return callback(null, null);
3663 }
3664 return callback(err);
3665 }
3666 const files = /** @type {string[]} */ (_files)
3667 .map((file) => file.normalize("NFC"))
3668 .filter((file) => !/^\./.test(file))
3669 .sort();
3670 asyncLib.map(
3671 files,
3672 (file, callback) => {
3673 const child = join(this.fs, path, file);
3674 for (const immutablePath of this.immutablePathsRegExps) {
3675 if (immutablePath.test(path)) {
3676 // ignore any immutable path for timestamping
3677 return callback(null, fromImmutablePath(path));
3678 }
3679 }
3680 for (const immutablePath of this.immutablePathsWithSlash) {
3681 if (path.startsWith(immutablePath)) {
3682 // ignore any immutable path for timestamping
3683 return callback(null, fromImmutablePath(path));
3684 }
3685 }
3686 for (const managedPath of this.managedPathsRegExps) {
3687 const match = managedPath.exec(path);
3688 if (match) {
3689 const managedItem = getManagedItem(match[1], path);
3690 if (managedItem) {
3691 // construct timestampHash from managed info
3692 return this.managedItemQueue.add(managedItem, (err, info) => {
3693 if (err) return callback(err);
3694 return callback(
3695 null,
3696 fromManagedItem(/** @type {string} */ (info))
3697 );
3698 });
3699 }
3700 }
3701 }
3702 for (const managedPath of this.managedPathsWithSlash) {
3703 if (path.startsWith(managedPath)) {
3704 const managedItem = getManagedItem(managedPath, child);
3705 if (managedItem) {
3706 // construct timestampHash from managed info
3707 return this.managedItemQueue.add(managedItem, (err, info) => {
3708 if (err) return callback(err);
3709 return callback(
3710 null,
3711 fromManagedItem(/** @type {string} */ (info))
3712 );
3713 });
3714 }
3715 }
3716 }
3717
3718 lstatReadlinkAbsolute(this.fs, child, (err, _stat) => {
3719 if (err) return callback(err);
3720
3721 const stat = /** @type {IStats | string} */ (_stat);
3722
3723 if (typeof stat === "string") {
3724 return fromSymlink(child, stat, callback);
3725 }
3726
3727 if (stat.isFile()) {
3728 return fromFile(child, stat, callback);
3729 }
3730 if (stat.isDirectory()) {
3731 return fromDirectory(child, stat, callback);
3732 }
3733 callback(null, null);
3734 });
3735 },
3736 (err, results) => {
3737 if (err) return callback(err);
3738 const result = reduce(files, /** @type {ItemType[]} */ (results));
3739 callback(null, result);
3740 }
3741 );
3742 });
3743 }
3744
3745 /**
3746 * @private
3747 * @type {Processor<string, ContextFileSystemInfoEntry>}
3748 */
3749 _readContextTimestamp(path, callback) {
3750 this._readContext(
3751 {
3752 path,
3753 fromImmutablePath: () =>
3754 /** @type {ContextFileSystemInfoEntry | FileSystemInfoEntry | "ignore" | null} */
3755 (null),
3756 fromManagedItem: (info) => ({
3757 safeTime: 0,
3758 timestampHash: info
3759 }),
3760 fromSymlink: (file, target, callback) => {
3761 callback(
3762 null,
3763 /** @type {ContextFileSystemInfoEntry} */
3764 ({
3765 timestampHash: target,
3766 symlinks: new Set([target])
3767 })
3768 );
3769 },
3770 fromFile: (file, stat, callback) => {
3771 // Prefer the cached value over our new stat to report consistent results
3772 const cache = this._fileTimestamps.get(file);
3773 if (cache !== undefined && !isExistenceOnly(cache)) {
3774 return callback(
3775 null,
3776 cache === "ignore"
3777 ? null
3778 : /** @type {FileSystemInfoEntry | null} */ (cache)
3779 );
3780 }
3781
3782 const mtime = Number(stat.mtime);
3783
3784 if (mtime) applyMtime(mtime);
3785
3786 /** @type {FileSystemInfoEntry} */
3787 const ts = {
3788 safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
3789 timestamp: mtime
3790 };
3791
3792 this._fileTimestamps.set(file, ts);
3793 this._cachedDeprecatedFileTimestamps = undefined;
3794 callback(null, ts);
3795 },
3796 fromDirectory: (directory, stat, callback) => {
3797 this.contextTimestampQueue.increaseParallelism();
3798 this._getUnresolvedContextTimestamp(directory, (err, tsEntry) => {
3799 this.contextTimestampQueue.decreaseParallelism();
3800 callback(err, tsEntry);
3801 });
3802 },
3803 reduce: (files, tsEntries) => {
3804 /** @type {undefined | Symlinks} */
3805 let symlinks;
3806
3807 const hash = createHash(this._hashFunction);
3808
3809 for (const file of files) hash.update(file);
3810 let safeTime = 0;
3811 for (const _e of tsEntries) {
3812 if (!_e) {
3813 hash.update("n");
3814 continue;
3815 }
3816 const entry =
3817 /** @type {FileSystemInfoEntry | ContextFileSystemInfoEntry} */
3818 (_e);
3819 if (/** @type {FileSystemInfoEntry} */ (entry).timestamp) {
3820 hash.update("f");
3821 hash.update(
3822 `${/** @type {FileSystemInfoEntry} */ (entry).timestamp}`
3823 );
3824 } else if (
3825 /** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash
3826 ) {
3827 hash.update("d");
3828 hash.update(
3829 `${/** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash}`
3830 );
3831 }
3832 if (
3833 /** @type {ContextFileSystemInfoEntry} */
3834 (entry).symlinks !== undefined
3835 ) {
3836 if (symlinks === undefined) symlinks = new Set();
3837 addAll(
3838 /** @type {ContextFileSystemInfoEntry} */ (entry).symlinks,
3839 symlinks
3840 );
3841 }
3842 if (entry.safeTime) {
3843 safeTime = Math.max(safeTime, entry.safeTime);
3844 }
3845 }
3846
3847 const digest = hash.digest("hex");
3848 /** @type {ContextFileSystemInfoEntry} */
3849 const result = {
3850 safeTime,
3851 timestampHash: digest
3852 };
3853 if (symlinks) result.symlinks = symlinks;
3854 return result;
3855 }
3856 },
3857 (err, result) => {
3858 if (err) return callback(/** @type {WebpackError} */ (err));
3859 this._contextTimestamps.set(path, result);
3860 this._cachedDeprecatedContextTimestamps = undefined;
3861
3862 callback(null, result);
3863 }
3864 );
3865 }
3866
3867 /**
3868 * Resolve context timestamp.
3869 * @private
3870 * @param {ContextFileSystemInfoEntry} entry entry
3871 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback
3872 * @returns {void}
3873 */
3874 _resolveContextTimestamp(entry, callback) {
3875 /** @type {string[]} */
3876 const hashes = [];
3877 let safeTime = 0;
3878 processAsyncTree(
3879 /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
3880 10,
3881 (target, push, callback) => {
3882 this._getUnresolvedContextTimestamp(target, (err, entry) => {
3883 if (err) return callback(err);
3884 if (entry && entry !== "ignore") {
3885 hashes.push(/** @type {string} */ (entry.timestampHash));
3886 if (entry.safeTime) {
3887 safeTime = Math.max(safeTime, entry.safeTime);
3888 }
3889 if (entry.symlinks !== undefined) {
3890 for (const target of entry.symlinks) push(target);
3891 }
3892 }
3893 callback();
3894 });
3895 },
3896 (err) => {
3897 if (err) return callback(/** @type {WebpackError} */ (err));
3898 const hash = createHash(this._hashFunction);
3899 hash.update(/** @type {string} */ (entry.timestampHash));
3900 if (entry.safeTime) {
3901 safeTime = Math.max(safeTime, entry.safeTime);
3902 }
3903 hashes.sort();
3904 for (const h of hashes) {
3905 hash.update(h);
3906 }
3907 callback(
3908 null,
3909 (entry.resolved = {
3910 safeTime,
3911 timestampHash: hash.digest("hex")
3912 })
3913 );
3914 }
3915 );
3916 }
3917
3918 /**
3919 * @private
3920 * @type {Processor<string, ContextHash>}
3921 */
3922 _readContextHash(path, callback) {
3923 this._readContext(
3924 {
3925 path,
3926 fromImmutablePath: () => /** @type {ContextHash | ""} */ (""),
3927 fromManagedItem: (info) => info || "",
3928 fromSymlink: (file, target, callback) => {
3929 callback(
3930 null,
3931 /** @type {ContextHash} */
3932 ({
3933 hash: target,
3934 symlinks: new Set([target])
3935 })
3936 );
3937 },
3938 fromFile: (file, stat, callback) =>
3939 this.getFileHash(file, (err, hash) => {
3940 callback(err, hash || "");
3941 }),
3942 fromDirectory: (directory, stat, callback) => {
3943 this.contextHashQueue.increaseParallelism();
3944 this._getUnresolvedContextHash(directory, (err, hash) => {
3945 this.contextHashQueue.decreaseParallelism();
3946 callback(err, hash || "");
3947 });
3948 },
3949 /**
3950 * Returns reduced hash.
3951 * @param {string[]} files files
3952 * @param {(string | ContextHash)[]} fileHashes hashes
3953 * @returns {ContextHash} reduced hash
3954 */
3955 reduce: (files, fileHashes) => {
3956 /** @type {undefined | Symlinks} */
3957 let symlinks;
3958 const hash = createHash(this._hashFunction);
3959
3960 for (const file of files) hash.update(file);
3961 for (const entry of fileHashes) {
3962 if (typeof entry === "string") {
3963 hash.update(entry);
3964 } else {
3965 hash.update(entry.hash);
3966 if (entry.symlinks) {
3967 if (symlinks === undefined) symlinks = new Set();
3968 addAll(entry.symlinks, symlinks);
3969 }
3970 }
3971 }
3972
3973 /** @type {ContextHash} */
3974 const result = {
3975 hash: hash.digest("hex")
3976 };
3977 if (symlinks) result.symlinks = symlinks;
3978 return result;
3979 }
3980 },
3981 (err, _result) => {
3982 if (err) return callback(/** @type {WebpackError} */ (err));
3983 const result = /** @type {ContextHash} */ (_result);
3984 this._contextHashes.set(path, result);
3985 return callback(null, result);
3986 }
3987 );
3988 }
3989
3990 /**
3991 * Resolve context hash.
3992 * @private
3993 * @param {ContextHash} entry context hash
3994 * @param {(err: WebpackError | null, contextHash?: string) => void} callback callback
3995 * @returns {void}
3996 */
3997 _resolveContextHash(entry, callback) {
3998 /** @type {string[]} */
3999 const hashes = [];
4000 processAsyncTree(
4001 /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
4002 10,
4003 (target, push, callback) => {
4004 this._getUnresolvedContextHash(target, (err, hash) => {
4005 if (err) return callback(err);
4006 if (hash) {
4007 hashes.push(hash.hash);
4008 if (hash.symlinks !== undefined) {
4009 for (const target of hash.symlinks) push(target);
4010 }
4011 }
4012 callback();
4013 });
4014 },
4015 (err) => {
4016 if (err) return callback(/** @type {WebpackError} */ (err));
4017 const hash = createHash(this._hashFunction);
4018 hash.update(entry.hash);
4019 hashes.sort();
4020 for (const h of hashes) {
4021 hash.update(h);
4022 }
4023 callback(null, (entry.resolved = hash.digest("hex")));
4024 }
4025 );
4026 }
4027
4028 /**
4029 * @private
4030 * @type {Processor<string, ContextTimestampAndHash>}
4031 */
4032 _readContextTimestampAndHash(path, callback) {
4033 /**
4034 * Processes the provided timestamp.
4035 * @param {ContextTimestamp} timestamp timestamp
4036 * @param {ContextHash} hash hash
4037 */
4038 const finalize = (timestamp, hash) => {
4039 const result =
4040 /** @type {ContextTimestampAndHash} */
4041 (timestamp === "ignore" ? hash : { ...timestamp, ...hash });
4042 this._contextTshs.set(path, result);
4043 callback(null, result);
4044 };
4045 const cachedHash = this._contextHashes.get(path);
4046 const cachedTimestamp = this._contextTimestamps.get(path);
4047 if (cachedHash !== undefined) {
4048 if (cachedTimestamp !== undefined) {
4049 finalize(cachedTimestamp, cachedHash);
4050 } else {
4051 this.contextTimestampQueue.add(path, (err, entry) => {
4052 if (err) return callback(err);
4053 finalize(
4054 /** @type {ContextFileSystemInfoEntry} */
4055 (entry),
4056 cachedHash
4057 );
4058 });
4059 }
4060 } else if (cachedTimestamp !== undefined) {
4061 this.contextHashQueue.add(path, (err, entry) => {
4062 if (err) return callback(err);
4063 finalize(cachedTimestamp, /** @type {ContextHash} */ (entry));
4064 });
4065 } else {
4066 this._readContext(
4067 {
4068 path,
4069 fromImmutablePath: () =>
4070 /** @type {ContextTimestampAndHash | Omit<ContextTimestampAndHash, "safeTime"> | string | null} */ (
4071 null
4072 ),
4073 fromManagedItem: (info) => ({
4074 safeTime: 0,
4075 timestampHash: info,
4076 hash: info || ""
4077 }),
4078 fromSymlink: (file, target, callback) => {
4079 callback(null, {
4080 timestampHash: target,
4081 hash: target,
4082 symlinks: new Set([target])
4083 });
4084 },
4085 fromFile: (file, stat, callback) => {
4086 this._getFileTimestampAndHash(file, callback);
4087 },
4088 fromDirectory: (directory, stat, callback) => {
4089 this.contextTshQueue.increaseParallelism();
4090 this.contextTshQueue.add(directory, (err, result) => {
4091 this.contextTshQueue.decreaseParallelism();
4092 callback(err, result);
4093 });
4094 },
4095 /**
4096 * Returns tsh.
4097 * @param {string[]} files files
4098 * @param {(Partial<TimestampAndHash> & Partial<ContextTimestampAndHash> | string | null)[]} results results
4099 * @returns {ContextTimestampAndHash} tsh
4100 */
4101 reduce: (files, results) => {
4102 /** @type {undefined | Symlinks} */
4103 let symlinks;
4104
4105 const tsHash = createHash(this._hashFunction);
4106 const hash = createHash(this._hashFunction);
4107
4108 for (const file of files) {
4109 tsHash.update(file);
4110 hash.update(file);
4111 }
4112 let safeTime = 0;
4113 for (const entry of results) {
4114 if (!entry) {
4115 tsHash.update("n");
4116 continue;
4117 }
4118 if (typeof entry === "string") {
4119 tsHash.update("n");
4120 hash.update(entry);
4121 continue;
4122 }
4123 if (entry.timestamp) {
4124 tsHash.update("f");
4125 tsHash.update(`${entry.timestamp}`);
4126 } else if (entry.timestampHash) {
4127 tsHash.update("d");
4128 tsHash.update(`${entry.timestampHash}`);
4129 }
4130 if (entry.symlinks !== undefined) {
4131 if (symlinks === undefined) symlinks = new Set();
4132 addAll(entry.symlinks, symlinks);
4133 }
4134 if (entry.safeTime) {
4135 safeTime = Math.max(safeTime, entry.safeTime);
4136 }
4137 hash.update(/** @type {string} */ (entry.hash));
4138 }
4139
4140 /** @type {ContextTimestampAndHash} */
4141 const result = {
4142 safeTime,
4143 timestampHash: tsHash.digest("hex"),
4144 hash: hash.digest("hex")
4145 };
4146 if (symlinks) result.symlinks = symlinks;
4147 return result;
4148 }
4149 },
4150 (err, _result) => {
4151 if (err) return callback(/** @type {WebpackError} */ (err));
4152 const result = /** @type {ContextTimestampAndHash} */ (_result);
4153 this._contextTshs.set(path, result);
4154 return callback(null, result);
4155 }
4156 );
4157 }
4158 }
4159
4160 /**
4161 * Resolve context tsh.
4162 * @private
4163 * @param {ContextTimestampAndHash} entry entry
4164 * @param {ProcessorCallback<ResolvedContextTimestampAndHash>} callback callback
4165 * @returns {void}
4166 */
4167 _resolveContextTsh(entry, callback) {
4168 /** @type {string[]} */
4169 const hashes = [];
4170 /** @type {string[]} */
4171 const tsHashes = [];
4172 let safeTime = 0;
4173 processAsyncTree(
4174 /** @type {NonNullable<ContextHash["symlinks"]>} */ (entry.symlinks),
4175 10,
4176 (target, push, callback) => {
4177 this._getUnresolvedContextTsh(target, (err, entry) => {
4178 if (err) return callback(err);
4179 if (entry) {
4180 hashes.push(entry.hash);
4181 if (entry.timestampHash) tsHashes.push(entry.timestampHash);
4182 if (entry.safeTime) {
4183 safeTime = Math.max(safeTime, entry.safeTime);
4184 }
4185 if (entry.symlinks !== undefined) {
4186 for (const target of entry.symlinks) push(target);
4187 }
4188 }
4189 callback();
4190 });
4191 },
4192 (err) => {
4193 if (err) return callback(/** @type {WebpackError} */ (err));
4194 const hash = createHash(this._hashFunction);
4195 const tsHash = createHash(this._hashFunction);
4196 hash.update(entry.hash);
4197 if (entry.timestampHash) tsHash.update(entry.timestampHash);
4198 if (entry.safeTime) {
4199 safeTime = Math.max(safeTime, entry.safeTime);
4200 }
4201 hashes.sort();
4202 for (const h of hashes) {
4203 hash.update(h);
4204 }
4205 tsHashes.sort();
4206 for (const h of tsHashes) {
4207 tsHash.update(h);
4208 }
4209 callback(
4210 null,
4211 (entry.resolved = {
4212 safeTime,
4213 timestampHash: tsHash.digest("hex"),
4214 hash: hash.digest("hex")
4215 })
4216 );
4217 }
4218 );
4219 }
4220
4221 /**
4222 * @private
4223 * @type {Processor<string, Set<string>>}
4224 */
4225 _getManagedItemDirectoryInfo(path, callback) {
4226 this.fs.readdir(path, (err, elements) => {
4227 if (err) {
4228 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
4229 return callback(null, EMPTY_SET);
4230 }
4231 return callback(/** @type {WebpackError} */ (err));
4232 }
4233 const set = new Set(
4234 /** @type {string[]} */
4235 (elements).map((element) => join(this.fs, path, element))
4236 );
4237 callback(null, set);
4238 });
4239 }
4240
4241 /**
4242 * @private
4243 * @type {Processor<string, string>}
4244 */
4245 _getManagedItemInfo(path, callback) {
4246 const dir = dirname(this.fs, path);
4247 this.managedItemDirectoryQueue.add(dir, (err, elements) => {
4248 if (err) {
4249 return callback(err);
4250 }
4251 if (!(/** @type {Set<string>} */ (elements).has(path))) {
4252 // file or directory doesn't exist
4253 this._managedItems.set(path, "*missing");
4254 return callback(null, "*missing");
4255 }
4256 // something exists
4257 // it may be a file or directory
4258 if (
4259 path.endsWith("node_modules") &&
4260 (path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
4261 ) {
4262 // we are only interested in existence of this special directory
4263 this._managedItems.set(path, "*node_modules");
4264 return callback(null, "*node_modules");
4265 }
4266
4267 // we assume it's a directory, as files shouldn't occur in managed paths
4268 const packageJsonPath = join(this.fs, path, "package.json");
4269 this.fs.readFile(packageJsonPath, (err, content) => {
4270 if (err) {
4271 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
4272 // no package.json or path is not a directory
4273 this.fs.readdir(path, (err, elements) => {
4274 if (
4275 !err &&
4276 /** @type {string[]} */ (elements).length === 1 &&
4277 /** @type {string[]} */ (elements)[0] === "node_modules"
4278 ) {
4279 // This is only a grouping folder e.g. used by yarn
4280 // we are only interested in existence of this special directory
4281 this._managedItems.set(path, "*nested");
4282 return callback(null, "*nested");
4283 }
4284 /** @type {Logger} */
4285 (this.logger).warn(
4286 `Managed item ${path} isn't a directory or doesn't contain a package.json (see snapshot.managedPaths option)`
4287 );
4288 return callback();
4289 });
4290 return;
4291 }
4292 return callback(/** @type {WebpackError} */ (err));
4293 }
4294 /** @type {JsonObject} */
4295 let data;
4296 try {
4297 data = JSON.parse(/** @type {Buffer} */ (content).toString("utf8"));
4298 } catch (parseErr) {
4299 return callback(/** @type {WebpackError} */ (parseErr));
4300 }
4301 if (!data.name) {
4302 /** @type {Logger} */
4303 (this.logger).warn(
4304 `${packageJsonPath} doesn't contain a "name" property (see snapshot.managedPaths option)`
4305 );
4306 return callback();
4307 }
4308 const info = `${data.name || ""}@${data.version || ""}`;
4309 this._managedItems.set(path, info);
4310 callback(null, info);
4311 });
4312 });
4313 }
4314
4315 getDeprecatedFileTimestamps() {
4316 if (this._cachedDeprecatedFileTimestamps !== undefined) {
4317 return this._cachedDeprecatedFileTimestamps;
4318 }
4319 /** @type {Map<string, number | null>} */
4320 const map = new Map();
4321 for (const [path, info] of this._fileTimestamps) {
4322 if (info) {
4323 const safeTime =
4324 typeof info === "object"
4325 ? /** @type {Partial<FileSystemInfoEntry>} */ (info).safeTime
4326 : undefined;
4327 map.set(path, safeTime === undefined ? null : safeTime);
4328 }
4329 }
4330 return (this._cachedDeprecatedFileTimestamps = map);
4331 }
4332
4333 getDeprecatedContextTimestamps() {
4334 if (this._cachedDeprecatedContextTimestamps !== undefined) {
4335 return this._cachedDeprecatedContextTimestamps;
4336 }
4337 /** @type {Map<string, number | null>} */
4338 const map = new Map();
4339 for (const [path, info] of this._contextTimestamps) {
4340 if (info) {
4341 const safeTime =
4342 typeof info === "object"
4343 ? /** @type {Partial<ContextFileSystemInfoEntry>} */ (info).safeTime
4344 : undefined;
4345 map.set(path, safeTime === undefined ? null : safeTime);
4346 }
4347 }
4348 return (this._cachedDeprecatedContextTimestamps = map);
4349 }
4350}
4351
4352module.exports = FileSystemInfo;
4353module.exports.Snapshot = Snapshot;
Note: See TracBrowser for help on using the repository browser.