source: imaps-frontend/node_modules/webpack/lib/FileSystemInfo.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

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