source: frontend/node_modules/webpack/lib/cache/PackFileCacheStrategy.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 46.5 KB
RevLine 
[9af201e]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const FileSystemInfo = require("../FileSystemInfo");
9const ProgressPlugin = require("../ProgressPlugin");
10const SerializerMiddleware = require("../serialization/SerializerMiddleware");
11const LazySet = require("../util/LazySet");
12const formatSize = require("../util/formatSize");
13const makeSerializable = require("../util/makeSerializable");
14const memoize = require("../util/memoize");
15const {
16 NOT_SERIALIZABLE,
17 createFileSerializer
18} = require("../util/serialization");
19
20/** @typedef {import("../../declarations/WebpackOptions").SnapshotOptions} SnapshotOptions */
21/** @typedef {import("../Compilation").FileSystemDependencies} FileSystemDependencies */
22/** @typedef {import("../Cache").Data} Data */
23/** @typedef {import("../Cache").Etag} Etag */
24/** @typedef {import("../Compiler")} Compiler */
25/** @typedef {import("../FileSystemInfo").ResolveBuildDependenciesResult} ResolveBuildDependenciesResult */
26/** @typedef {import("../FileSystemInfo").ResolveResults} ResolveResults */
27/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */
28/** @typedef {import("../logging/Logger").Logger} Logger */
29/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
30/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
31/** @typedef {import("../util/Hash").HashFunction} HashFunction */
32/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
33
34/** @typedef {Set<string>} Items */
35/** @typedef {Set<string>} BuildDependencies */
36/** @typedef {Map<string, PackItemInfo>} ItemInfo */
37
38class PackContainer {
39 /**
40 * Creates an instance of PackContainer.
41 * @param {Pack} data stored data
42 * @param {string} version version identifier
43 * @param {Snapshot} buildSnapshot snapshot of all build dependencies
44 * @param {BuildDependencies} buildDependencies list of all unresolved build dependencies captured
45 * @param {ResolveResults} resolveResults result of the resolved build dependencies
46 * @param {Snapshot} resolveBuildDependenciesSnapshot snapshot of the dependencies of the build dependencies resolving
47 */
48 constructor(
49 data,
50 version,
51 buildSnapshot,
52 buildDependencies,
53 resolveResults,
54 resolveBuildDependenciesSnapshot
55 ) {
56 /** @type {Pack | (() => Pack)} */
57 this.data = data;
58 /** @type {string} */
59 this.version = version;
60 /** @type {Snapshot} */
61 this.buildSnapshot = buildSnapshot;
62 /** @type {BuildDependencies} */
63 this.buildDependencies = buildDependencies;
64 /** @type {ResolveResults} */
65 this.resolveResults = resolveResults;
66 /** @type {Snapshot} */
67 this.resolveBuildDependenciesSnapshot = resolveBuildDependenciesSnapshot;
68 }
69
70 /**
71 * Serializes this instance into the provided serializer context.
72 * @param {ObjectSerializerContext} context context
73 */
74 serialize({ write, writeLazy }) {
75 write(this.version);
76 write(this.buildSnapshot);
77 write(this.buildDependencies);
78 write(this.resolveResults);
79 write(this.resolveBuildDependenciesSnapshot);
80 /** @type {NonNullable<ObjectSerializerContext["writeLazy"]>} */
81 (writeLazy)(this.data);
82 }
83
84 /**
85 * Restores this instance from the provided deserializer context.
86 * @param {ObjectDeserializerContext} context context
87 */
88 deserialize({ read }) {
89 this.version = read();
90 this.buildSnapshot = read();
91 this.buildDependencies = read();
92 this.resolveResults = read();
93 this.resolveBuildDependenciesSnapshot = read();
94 this.data = read();
95 }
96}
97
98makeSerializable(
99 PackContainer,
100 "webpack/lib/cache/PackFileCacheStrategy",
101 "PackContainer"
102);
103
104const MIN_CONTENT_SIZE = 1024 * 1024; // 1 MB
105const CONTENT_COUNT_TO_MERGE = 10;
106const MIN_ITEMS_IN_FRESH_PACK = 100;
107const MAX_ITEMS_IN_FRESH_PACK = 50000;
108const MAX_TIME_IN_FRESH_PACK = 60 * 1000; // 1 min
109
110class PackItemInfo {
111 /**
112 * Creates an instance of PackItemInfo.
113 * @param {string} identifier identifier of item
114 * @param {string | null | undefined} etag etag of item
115 * @param {Data} value fresh value of item
116 */
117 constructor(identifier, etag, value) {
118 /** @type {string} */
119 this.identifier = identifier;
120 /** @type {string | null | undefined} */
121 this.etag = etag;
122 /** @type {number} */
123 this.location = -1;
124 /** @type {number} */
125 this.lastAccess = Date.now();
126 /** @type {Data} */
127 this.freshValue = value;
128 }
129}
130
131class Pack {
132 /**
133 * Creates an instance of Pack.
134 * @param {Logger} logger a logger
135 * @param {number} maxAge max age of cache items
136 */
137 constructor(logger, maxAge) {
138 /** @type {ItemInfo} */
139 this.itemInfo = new Map();
140 /** @type {(string | undefined)[]} */
141 this.requests = [];
142 /** @type {undefined | NodeJS.Timeout} */
143 this.requestsTimeout = undefined;
144 /** @type {ItemInfo} */
145 this.freshContent = new Map();
146 /** @type {(undefined | PackContent)[]} */
147 this.content = [];
148 /** @type {boolean} */
149 this.invalid = false;
150 /** @type {Logger} */
151 this.logger = logger;
152 /** @type {number} */
153 this.maxAge = maxAge;
154 }
155
156 /**
157 * Adds the provided identifier to the pack.
158 * @param {string} identifier identifier
159 */
160 _addRequest(identifier) {
161 this.requests.push(identifier);
162 if (this.requestsTimeout === undefined) {
163 this.requestsTimeout = setTimeout(() => {
164 this.requests.push(undefined);
165 this.requestsTimeout = undefined;
166 }, MAX_TIME_IN_FRESH_PACK);
167 if (this.requestsTimeout.unref) this.requestsTimeout.unref();
168 }
169 }
170
171 stopCapturingRequests() {
172 if (this.requestsTimeout !== undefined) {
173 clearTimeout(this.requestsTimeout);
174 this.requestsTimeout = undefined;
175 }
176 }
177
178 /**
179 * Returns cached content.
180 * @param {string} identifier unique name for the resource
181 * @param {string | null} etag etag of the resource
182 * @returns {Data} cached content
183 */
184 get(identifier, etag) {
185 const info = this.itemInfo.get(identifier);
186 this._addRequest(identifier);
187 if (info === undefined) {
188 return;
189 }
190 if (info.etag !== etag) return null;
191 info.lastAccess = Date.now();
192 const loc = info.location;
193 if (loc === -1) {
194 return info.freshValue;
195 }
196 if (!this.content[loc]) {
197 return;
198 }
199 return /** @type {PackContent} */ (this.content[loc]).get(identifier);
200 }
201
202 /**
203 * Updates value using the provided identifier.
204 * @param {string} identifier unique name for the resource
205 * @param {string | null} etag etag of the resource
206 * @param {Data} data cached content
207 * @returns {void}
208 */
209 set(identifier, etag, data) {
210 if (!this.invalid) {
211 this.invalid = true;
212 this.logger.log(`Pack got invalid because of write to: ${identifier}`);
213 }
214 const info = this.itemInfo.get(identifier);
215 if (info === undefined) {
216 const newInfo = new PackItemInfo(identifier, etag, data);
217 this.itemInfo.set(identifier, newInfo);
218 this._addRequest(identifier);
219 this.freshContent.set(identifier, newInfo);
220 } else {
221 const loc = info.location;
222 if (loc >= 0) {
223 this._addRequest(identifier);
224 this.freshContent.set(identifier, info);
225 const content = /** @type {PackContent} */ (this.content[loc]);
226 content.delete(identifier);
227 if (content.items.size === 0) {
228 this.content[loc] = undefined;
229 this.logger.debug("Pack %d got empty and is removed", loc);
230 }
231 }
232 info.freshValue = data;
233 info.lastAccess = Date.now();
234 info.etag = etag;
235 info.location = -1;
236 }
237 }
238
239 getContentStats() {
240 let count = 0;
241 let size = 0;
242 for (const content of this.content) {
243 if (content !== undefined) {
244 count++;
245 const s = content.getSize();
246 if (s > 0) {
247 size += s;
248 }
249 }
250 }
251 return { count, size };
252 }
253
254 /**
255 * Returns new location of data entries.
256 * @returns {number} new location of data entries
257 */
258 _findLocation() {
259 /** @type {number} */
260 let i;
261 for (i = 0; i < this.content.length && this.content[i] !== undefined; i++);
262 return i;
263 }
264
265 /**
266 * Gc and update location.
267 * @private
268 * @param {Items} items items
269 * @param {Items} usedItems used items
270 * @param {number} newLoc new location
271 */
272 _gcAndUpdateLocation(items, usedItems, newLoc) {
273 let count = 0;
274 /** @type {undefined | string} */
275 let lastGC;
276 const now = Date.now();
277 for (const identifier of items) {
278 const info = /** @type {PackItemInfo} */ (this.itemInfo.get(identifier));
279 if (now - info.lastAccess > this.maxAge) {
280 this.itemInfo.delete(identifier);
281 items.delete(identifier);
282 usedItems.delete(identifier);
283 count++;
284 lastGC = identifier;
285 } else {
286 info.location = newLoc;
287 }
288 }
289 if (count > 0) {
290 this.logger.log(
291 "Garbage Collected %d old items at pack %d (%d items remaining) e. g. %s",
292 count,
293 newLoc,
294 items.size,
295 lastGC
296 );
297 }
298 }
299
300 _persistFreshContent() {
301 /** @typedef {{ items: Items, map: Content, loc: number }} PackItem */
302 const itemsCount = this.freshContent.size;
303 if (itemsCount > 0) {
304 const packCount = Math.ceil(itemsCount / MAX_ITEMS_IN_FRESH_PACK);
305 const itemsPerPack = Math.ceil(itemsCount / packCount);
306 /** @type {PackItem[]} */
307 const packs = [];
308 let i = 0;
309 let ignoreNextTimeTick = false;
310 const createNextPack = () => {
311 const loc = this._findLocation();
312 this.content[loc] = /** @type {EXPECTED_ANY} */ (null); // reserve
313 /** @type {PackItem} */
314 const pack = {
315 items: new Set(),
316 map: new Map(),
317 loc
318 };
319 packs.push(pack);
320 return pack;
321 };
322 let pack = createNextPack();
323 if (this.requestsTimeout !== undefined) {
324 clearTimeout(this.requestsTimeout);
325 }
326 for (const identifier of this.requests) {
327 if (identifier === undefined) {
328 if (ignoreNextTimeTick) {
329 ignoreNextTimeTick = false;
330 } else if (pack.items.size >= MIN_ITEMS_IN_FRESH_PACK) {
331 i = 0;
332 pack = createNextPack();
333 }
334 continue;
335 }
336 const info = this.freshContent.get(identifier);
337 if (info === undefined) continue;
338 pack.items.add(identifier);
339 pack.map.set(identifier, info.freshValue);
340 info.location = pack.loc;
341 info.freshValue = undefined;
342 this.freshContent.delete(identifier);
343 if (++i > itemsPerPack) {
344 i = 0;
345 pack = createNextPack();
346 ignoreNextTimeTick = true;
347 }
348 }
349 this.requests.length = 0;
350 for (const pack of packs) {
351 this.content[pack.loc] = new PackContent(
352 pack.items,
353 new Set(pack.items),
354 new PackContentItems(pack.map)
355 );
356 }
357 this.logger.log(
358 `${itemsCount} fresh items in cache put into pack ${
359 packs.length > 1
360 ? packs
361 .map((pack) => `${pack.loc} (${pack.items.size} items)`)
362 .join(", ")
363 : packs[0].loc
364 }`
365 );
366 }
367 }
368
369 /**
370 * Merges small content files to a single content file
371 */
372 _optimizeSmallContent() {
373 // 1. Find all small content files
374 // Treat unused content files separately to avoid
375 // a merge-split cycle
376 /** @type {number[]} */
377 const smallUsedContents = [];
378 /** @type {number} */
379 let smallUsedContentSize = 0;
380 /** @type {number[]} */
381 const smallUnusedContents = [];
382 /** @type {number} */
383 let smallUnusedContentSize = 0;
384 for (let i = 0; i < this.content.length; i++) {
385 const content = this.content[i];
386 if (content === undefined) continue;
387 if (content.outdated) continue;
388 const size = content.getSize();
389 if (size < 0 || size > MIN_CONTENT_SIZE) continue;
390 if (content.used.size > 0) {
391 smallUsedContents.push(i);
392 smallUsedContentSize += size;
393 } else {
394 smallUnusedContents.push(i);
395 smallUnusedContentSize += size;
396 }
397 }
398
399 // 2. Check if minimum number is reached
400 /** @type {number[]} */
401 let mergedIndices;
402 if (
403 smallUsedContents.length >= CONTENT_COUNT_TO_MERGE ||
404 smallUsedContentSize > MIN_CONTENT_SIZE
405 ) {
406 mergedIndices = smallUsedContents;
407 } else if (
408 smallUnusedContents.length >= CONTENT_COUNT_TO_MERGE ||
409 smallUnusedContentSize > MIN_CONTENT_SIZE
410 ) {
411 mergedIndices = smallUnusedContents;
412 } else {
413 return;
414 }
415
416 /** @type {PackContent[]} */
417 const mergedContent = [];
418
419 // 3. Remove old content entries
420 for (const i of mergedIndices) {
421 mergedContent.push(/** @type {PackContent} */ (this.content[i]));
422 this.content[i] = undefined;
423 }
424
425 // 4. Determine merged items
426 /** @type {Items} */
427 const mergedItems = new Set();
428 /** @type {Items} */
429 const mergedUsedItems = new Set();
430 /** @type {((map: Content) => Promise<void>)[]} */
431 const addToMergedMap = [];
432 for (const content of mergedContent) {
433 for (const identifier of content.items) {
434 mergedItems.add(identifier);
435 }
436 for (const identifier of content.used) {
437 mergedUsedItems.add(identifier);
438 }
439 addToMergedMap.push(async (map) => {
440 // unpack existing content
441 // after that values are accessible in .content
442 await content.unpack(
443 "it should be merged with other small pack contents"
444 );
445 for (const [identifier, value] of /** @type {Content} */ (
446 content.content
447 )) {
448 map.set(identifier, value);
449 }
450 });
451 }
452
453 // 5. GC and update location of merged items
454 const newLoc = this._findLocation();
455 this._gcAndUpdateLocation(mergedItems, mergedUsedItems, newLoc);
456
457 // 6. If not empty, store content somewhere
458 if (mergedItems.size > 0) {
459 this.content[newLoc] = new PackContent(
460 mergedItems,
461 mergedUsedItems,
462 memoize(async () => {
463 /** @type {Content} */
464 const map = new Map();
465 await Promise.all(addToMergedMap.map((fn) => fn(map)));
466 return new PackContentItems(map);
467 })
468 );
469 this.logger.log(
470 "Merged %d small files with %d cache items into pack %d",
471 mergedContent.length,
472 mergedItems.size,
473 newLoc
474 );
475 }
476 }
477
478 /**
479 * Split large content files with used and unused items
480 * into two parts to separate used from unused items
481 */
482 _optimizeUnusedContent() {
483 // 1. Find a large content file with used and unused items
484 for (let i = 0; i < this.content.length; i++) {
485 const content = this.content[i];
486 if (content === undefined) continue;
487 const size = content.getSize();
488 if (size < MIN_CONTENT_SIZE) continue;
489 const used = content.used.size;
490 const total = content.items.size;
491 if (used > 0 && used < total) {
492 // 2. Remove this content
493 this.content[i] = undefined;
494
495 // 3. Determine items for the used content file
496 const usedItems = new Set(content.used);
497 const newLoc = this._findLocation();
498 this._gcAndUpdateLocation(usedItems, usedItems, newLoc);
499
500 // 4. Create content file for used items
501 if (usedItems.size > 0) {
502 this.content[newLoc] = new PackContent(
503 usedItems,
504 new Set(usedItems),
505 async () => {
506 await content.unpack(
507 "it should be splitted into used and unused items"
508 );
509 /** @type {Content} */
510 const map = new Map();
511 for (const identifier of usedItems) {
512 map.set(
513 identifier,
514 /** @type {Content} */
515 (content.content).get(identifier)
516 );
517 }
518 return new PackContentItems(map);
519 }
520 );
521 }
522
523 // 5. Determine items for the unused content file
524 const unusedItems = new Set(content.items);
525 /** @type {Items} */
526 const usedOfUnusedItems = new Set();
527 for (const identifier of usedItems) {
528 unusedItems.delete(identifier);
529 }
530 const newUnusedLoc = this._findLocation();
531 this._gcAndUpdateLocation(unusedItems, usedOfUnusedItems, newUnusedLoc);
532
533 // 6. Create content file for unused items
534 if (unusedItems.size > 0) {
535 this.content[newUnusedLoc] = new PackContent(
536 unusedItems,
537 usedOfUnusedItems,
538 async () => {
539 await content.unpack(
540 "it should be splitted into used and unused items"
541 );
542 /** @type {Content} */
543 const map = new Map();
544 for (const identifier of unusedItems) {
545 map.set(
546 identifier,
547 /** @type {Content} */
548 (content.content).get(identifier)
549 );
550 }
551 return new PackContentItems(map);
552 }
553 );
554 }
555
556 this.logger.log(
557 "Split pack %d into pack %d with %d used items and pack %d with %d unused items",
558 i,
559 newLoc,
560 usedItems.size,
561 newUnusedLoc,
562 unusedItems.size
563 );
564
565 // optimizing only one of them is good enough and
566 // reduces the amount of serialization needed
567 return;
568 }
569 }
570 }
571
572 /**
573 * Find the content with the oldest item and run GC on that.
574 * Only runs for one content to avoid large invalidation.
575 */
576 _gcOldestContent() {
577 /** @type {PackItemInfo | undefined} */
578 let oldest;
579 for (const info of this.itemInfo.values()) {
580 if (oldest === undefined || info.lastAccess < oldest.lastAccess) {
581 oldest = info;
582 }
583 }
584 if (
585 Date.now() - /** @type {PackItemInfo} */ (oldest).lastAccess >
586 this.maxAge
587 ) {
588 const loc = /** @type {PackItemInfo} */ (oldest).location;
589 if (loc < 0) return;
590 const content = /** @type {PackContent} */ (this.content[loc]);
591 const items = new Set(content.items);
592 const usedItems = new Set(content.used);
593 this._gcAndUpdateLocation(items, usedItems, loc);
594
595 this.content[loc] =
596 items.size > 0
597 ? new PackContent(items, usedItems, async () => {
598 await content.unpack(
599 "it contains old items that should be garbage collected"
600 );
601 /** @type {Content} */
602 const map = new Map();
603 for (const identifier of items) {
604 map.set(
605 identifier,
606 /** @type {Content} */
607 (content.content).get(identifier)
608 );
609 }
610 return new PackContentItems(map);
611 })
612 : undefined;
613 }
614 }
615
616 /**
617 * Serializes this instance into the provided serializer context.
618 * @param {ObjectSerializerContext} context context
619 */
620 serialize({ write, writeSeparate }) {
621 this._persistFreshContent();
622 this._optimizeSmallContent();
623 this._optimizeUnusedContent();
624 this._gcOldestContent();
625 for (const identifier of this.itemInfo.keys()) {
626 write(identifier);
627 }
628 write(null); // null as marker of the end of keys
629 for (const info of this.itemInfo.values()) {
630 write(info.etag);
631 }
632 for (const info of this.itemInfo.values()) {
633 write(info.lastAccess);
634 }
635 for (let i = 0; i < this.content.length; i++) {
636 const content = this.content[i];
637 if (content !== undefined) {
638 write(content.items);
639 content.writeLazy((lazy) =>
640 /** @type {NonNullable<ObjectSerializerContext["writeSeparate"]>} */
641 (writeSeparate)(lazy, { name: `${i}` })
642 );
643 } else {
644 write(undefined); // undefined marks an empty content slot
645 }
646 }
647 write(null); // null as marker of the end of items
648 }
649
650 /**
651 * Restores this instance from the provided deserializer context.
652 * @param {ObjectDeserializerContext & { logger: Logger }} context context
653 */
654 deserialize({ read, logger }) {
655 this.logger = logger;
656 {
657 const items = [];
658 let item = read();
659 while (item !== null) {
660 items.push(item);
661 item = read();
662 }
663 this.itemInfo.clear();
664 const infoItems = items.map((identifier) => {
665 const info = new PackItemInfo(identifier, undefined, undefined);
666 this.itemInfo.set(identifier, info);
667 return info;
668 });
669 for (const info of infoItems) {
670 info.etag = read();
671 }
672 for (const info of infoItems) {
673 info.lastAccess = read();
674 }
675 }
676 this.content.length = 0;
677 let items = read();
678 while (items !== null) {
679 if (items === undefined) {
680 this.content.push(items);
681 } else {
682 const idx = this.content.length;
683 const lazy = read();
684 this.content.push(
685 new PackContent(
686 items,
687 new Set(),
688 lazy,
689 logger,
690 `${this.content.length}`
691 )
692 );
693 for (const identifier of items) {
694 /** @type {PackItemInfo} */
695 (this.itemInfo.get(identifier)).location = idx;
696 }
697 }
698 items = read();
699 }
700 }
701}
702
703makeSerializable(Pack, "webpack/lib/cache/PackFileCacheStrategy", "Pack");
704
705/** @typedef {Map<string, Data>} Content */
706
707class PackContentItems {
708 /**
709 * Creates an instance of PackContentItems.
710 * @param {Content} map items
711 */
712 constructor(map) {
713 this.map = map;
714 }
715
716 /**
717 * Serializes this instance into the provided serializer context.
718 * @param {ObjectSerializerContext & { logger: Logger, profile: boolean | undefined }} context context
719 */
720 serialize({ write, snapshot, rollback, logger, profile }) {
721 if (profile) {
722 write(false);
723 for (const [key, value] of this.map) {
724 const s = snapshot();
725 try {
726 write(key);
727 const start = process.hrtime();
728 write(value);
729 const durationHr = process.hrtime(start);
730 const duration = durationHr[0] * 1000 + durationHr[1] / 1e6;
731 if (duration > 1) {
732 if (duration > 500) {
733 logger.error(`Serialization of '${key}': ${duration} ms`);
734 } else if (duration > 50) {
735 logger.warn(`Serialization of '${key}': ${duration} ms`);
736 } else if (duration > 10) {
737 logger.info(`Serialization of '${key}': ${duration} ms`);
738 } else if (duration > 5) {
739 logger.log(`Serialization of '${key}': ${duration} ms`);
740 } else {
741 logger.debug(`Serialization of '${key}': ${duration} ms`);
742 }
743 }
744 } catch (err) {
745 rollback(s);
746 if (err === NOT_SERIALIZABLE) continue;
747 const msg = "Skipped not serializable cache item";
748 const notSerializableErr = /** @type {Error} */ (err);
749 if (notSerializableErr.message.includes("ModuleBuildError")) {
750 logger.log(
751 `${msg} (in build error): ${notSerializableErr.message}`
752 );
753 logger.debug(
754 `${msg} '${key}' (in build error): ${notSerializableErr.stack}`
755 );
756 } else {
757 logger.warn(`${msg}: ${notSerializableErr.message}`);
758 logger.debug(`${msg} '${key}': ${notSerializableErr.stack}`);
759 }
760 }
761 }
762 write(null);
763 return;
764 }
765 // Try to serialize all at once
766 const s = snapshot();
767 try {
768 write(true);
769 write(this.map);
770 } catch (_err) {
771 rollback(s);
772
773 // Try to serialize each item on it's own
774 write(false);
775 for (const [key, value] of this.map) {
776 const s = snapshot();
777 try {
778 write(key);
779 write(value);
780 } catch (err) {
781 rollback(s);
782 if (err === NOT_SERIALIZABLE) continue;
783 const notSerializableErr = /** @type {Error} */ (err);
784 logger.warn(
785 `Skipped not serializable cache item '${key}': ${notSerializableErr.message}`
786 );
787 logger.debug(notSerializableErr.stack);
788 }
789 }
790 write(null);
791 }
792 }
793
794 /**
795 * Restores this instance from the provided deserializer context.
796 * @param {ObjectDeserializerContext & { logger: Logger, profile: boolean | undefined }} context context
797 */
798 deserialize({ read, logger, profile }) {
799 if (read()) {
800 this.map = read();
801 } else if (profile) {
802 /** @type {Content} */
803 const map = new Map();
804 let key = read();
805 while (key !== null) {
806 const start = process.hrtime();
807 const value = read();
808 const durationHr = process.hrtime(start);
809 const duration = durationHr[0] * 1000 + durationHr[1] / 1e6;
810 if (duration > 1) {
811 if (duration > 100) {
812 logger.error(`Deserialization of '${key}': ${duration} ms`);
813 } else if (duration > 20) {
814 logger.warn(`Deserialization of '${key}': ${duration} ms`);
815 } else if (duration > 5) {
816 logger.info(`Deserialization of '${key}': ${duration} ms`);
817 } else if (duration > 2) {
818 logger.log(`Deserialization of '${key}': ${duration} ms`);
819 } else {
820 logger.debug(`Deserialization of '${key}': ${duration} ms`);
821 }
822 }
823 map.set(key, value);
824 key = read();
825 }
826 this.map = map;
827 } else {
828 /** @type {Content} */
829 const map = new Map();
830 let key = read();
831 while (key !== null) {
832 map.set(key, read());
833 key = read();
834 }
835 this.map = map;
836 }
837 }
838}
839
840makeSerializable(
841 PackContentItems,
842 "webpack/lib/cache/PackFileCacheStrategy",
843 "PackContentItems"
844);
845
846/** @typedef {(() => Promise<PackContentItems> | PackContentItems) & Partial<{ options: { size?: number } }>} LazyFunction */
847
848class PackContent {
849 /*
850 This class can be in these states:
851 | this.lazy | this.content | this.outdated | state
852 A1 | undefined | Map | false | fresh content
853 A2 | undefined | Map | true | (will not happen)
854 B1 | lazy () => {} | undefined | false | not deserialized
855 B2 | lazy () => {} | undefined | true | not deserialized, but some items has been removed
856 C1 | lazy* () => {} | Map | false | deserialized
857 C2 | lazy* () => {} | Map | true | deserialized, and some items has been removed
858
859 this.used is a subset of this.items.
860 this.items is a subset of this.content.keys() resp. this.lazy().map.keys()
861 When this.outdated === false, this.items === this.content.keys() resp. this.lazy().map.keys()
862 When this.outdated === true, this.items should be used to recreated this.lazy/this.content.
863 When this.lazy and this.content is set, they contain the same data.
864 this.get must only be called with a valid item from this.items.
865 In state C this.lazy is unMemoized
866 */
867
868 /**
869 * Creates an instance of PackContent.
870 * @param {Items} items keys
871 * @param {Items} usedItems used keys
872 * @param {PackContentItems | (() => Promise<PackContentItems>)} dataOrFn sync or async content
873 * @param {Logger=} logger logger for logging
874 * @param {string=} lazyName name of dataOrFn for logging
875 */
876 constructor(items, usedItems, dataOrFn, logger, lazyName) {
877 /** @type {Items} */
878 this.items = items;
879 /** @type {LazyFunction | undefined} */
880 this.lazy = typeof dataOrFn === "function" ? dataOrFn : undefined;
881 /** @type {Content | undefined} */
882 this.content = typeof dataOrFn === "function" ? undefined : dataOrFn.map;
883 /** @type {boolean} */
884 this.outdated = false;
885 /** @type {Items} */
886 this.used = usedItems;
887 /** @type {Logger | undefined} */
888 this.logger = logger;
889 /** @type {string | undefined} */
890 this.lazyName = lazyName;
891 }
892
893 /**
894 * Returns result.
895 * @param {string} identifier identifier
896 * @returns {string | Promise<string>} result
897 */
898 get(identifier) {
899 this.used.add(identifier);
900 if (this.content) {
901 return this.content.get(identifier);
902 }
903
904 const logger = /** @type {Logger} */ (this.logger);
905 // We are in state B
906 const { lazyName } = this;
907 /** @type {string | undefined} */
908 let timeMessage;
909 if (lazyName) {
910 // only log once
911 this.lazyName = undefined;
912 timeMessage = `restore cache content ${lazyName} (${formatSize(
913 this.getSize()
914 )})`;
915 logger.log(
916 `starting to restore cache content ${lazyName} (${formatSize(
917 this.getSize()
918 )}) because of request to: ${identifier}`
919 );
920 logger.time(timeMessage);
921 }
922 const value = /** @type {LazyFunction} */ (this.lazy)();
923 if ("then" in value) {
924 return value.then((data) => {
925 const map = data.map;
926 if (timeMessage) {
927 logger.timeEnd(timeMessage);
928 }
929 // Move to state C
930 this.content = map;
931 this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
932 return map.get(identifier);
933 });
934 }
935
936 const map = value.map;
937 if (timeMessage) {
938 logger.timeEnd(timeMessage);
939 }
940 // Move to state C
941 this.content = map;
942 this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
943 return map.get(identifier);
944 }
945
946 /**
947 * Returns maybe a promise if lazy.
948 * @param {string} reason explanation why unpack is necessary
949 * @returns {void | Promise<void>} maybe a promise if lazy
950 */
951 unpack(reason) {
952 if (this.content) return;
953
954 const logger = /** @type {Logger} */ (this.logger);
955 // Move from state B to C
956 if (this.lazy) {
957 const { lazyName } = this;
958 /** @type {string | undefined} */
959 let timeMessage;
960 if (lazyName) {
961 // only log once
962 this.lazyName = undefined;
963 timeMessage = `unpack cache content ${lazyName} (${formatSize(
964 this.getSize()
965 )})`;
966 logger.log(
967 `starting to unpack cache content ${lazyName} (${formatSize(
968 this.getSize()
969 )}) because ${reason}`
970 );
971 logger.time(timeMessage);
972 }
973 const value =
974 /** @type {PackContentItems | Promise<PackContentItems>} */
975 (this.lazy());
976 if ("then" in value) {
977 return value.then((data) => {
978 if (timeMessage) {
979 logger.timeEnd(timeMessage);
980 }
981 this.content = data.map;
982 });
983 }
984 if (timeMessage) {
985 logger.timeEnd(timeMessage);
986 }
987 this.content = value.map;
988 }
989 }
990
991 /**
992 * Returns the estimated size for the requested source type.
993 * @returns {number} size of the content or -1 if not known
994 */
995 getSize() {
996 if (!this.lazy) return -1;
997 const options =
998 /** @type {{ options: { size?: number } }} */
999 (this.lazy).options;
1000 if (!options) return -1;
1001 const size = options.size;
1002 if (typeof size !== "number") return -1;
1003 return size;
1004 }
1005
1006 /**
1007 * Processes the provided identifier.
1008 * @param {string} identifier identifier
1009 */
1010 delete(identifier) {
1011 this.items.delete(identifier);
1012 this.used.delete(identifier);
1013 this.outdated = true;
1014 }
1015
1016 /**
1017 * Processes the provided write.
1018 * @param {(lazy: LazyFunction) => (() => PackContentItems | Promise<PackContentItems>)} write write function
1019 * @returns {void}
1020 */
1021 writeLazy(write) {
1022 if (!this.outdated && this.lazy) {
1023 // State B1 or C1
1024 // this.lazy is still the valid deserialized version
1025 write(this.lazy);
1026 return;
1027 }
1028 if (!this.outdated && this.content) {
1029 // State A1
1030 const map = new Map(this.content);
1031 // Move to state C1
1032 this.lazy = SerializerMiddleware.unMemoizeLazy(
1033 write(() => new PackContentItems(map))
1034 );
1035 return;
1036 }
1037 if (this.content) {
1038 // State A2 or C2
1039 /** @type {Content} */
1040 const map = new Map();
1041 for (const item of this.items) {
1042 map.set(item, this.content.get(item));
1043 }
1044 // Move to state C1
1045 this.outdated = false;
1046 this.content = map;
1047 this.lazy = SerializerMiddleware.unMemoizeLazy(
1048 write(() => new PackContentItems(map))
1049 );
1050 return;
1051 }
1052 const logger = /** @type {Logger} */ (this.logger);
1053 // State B2
1054 const { lazyName } = this;
1055 /** @type {string | undefined} */
1056 let timeMessage;
1057 if (lazyName) {
1058 // only log once
1059 this.lazyName = undefined;
1060 timeMessage = `unpack cache content ${lazyName} (${formatSize(
1061 this.getSize()
1062 )})`;
1063 logger.log(
1064 `starting to unpack cache content ${lazyName} (${formatSize(
1065 this.getSize()
1066 )}) because it's outdated and need to be serialized`
1067 );
1068 logger.time(timeMessage);
1069 }
1070 const value = /** @type {LazyFunction} */ (this.lazy)();
1071 this.outdated = false;
1072 if ("then" in value) {
1073 // Move to state B1
1074 this.lazy = write(() =>
1075 value.then((data) => {
1076 if (timeMessage) {
1077 logger.timeEnd(timeMessage);
1078 }
1079 const oldMap = data.map;
1080 /** @type {Content} */
1081 const map = new Map();
1082 for (const item of this.items) {
1083 map.set(item, oldMap.get(item));
1084 }
1085 // Move to state C1 (or maybe C2)
1086 this.content = map;
1087 this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
1088
1089 return new PackContentItems(map);
1090 })
1091 );
1092 } else {
1093 // Move to state C1
1094 if (timeMessage) {
1095 logger.timeEnd(timeMessage);
1096 }
1097 const oldMap = value.map;
1098 /** @type {Content} */
1099 const map = new Map();
1100 for (const item of this.items) {
1101 map.set(item, oldMap.get(item));
1102 }
1103 this.content = map;
1104 this.lazy = write(() => new PackContentItems(map));
1105 }
1106 }
1107}
1108
1109/**
1110 * Allow collecting memory.
1111 * @param {Buffer} buf buffer
1112 * @returns {Buffer} buffer that can be collected
1113 */
1114const allowCollectingMemory = (buf) => {
1115 const wasted = buf.buffer.byteLength - buf.byteLength;
1116 if (wasted > 8192 && (wasted > 1048576 || wasted > buf.byteLength)) {
1117 return Buffer.from(buf);
1118 }
1119 return buf;
1120};
1121
1122class PackFileCacheStrategy {
1123 /**
1124 * Creates an instance of PackFileCacheStrategy.
1125 * @param {object} options options
1126 * @param {Compiler} options.compiler the compiler
1127 * @param {IntermediateFileSystem} options.fs the filesystem
1128 * @param {string} options.context the context directory
1129 * @param {string} options.cacheLocation the location of the cache data
1130 * @param {string} options.version version identifier
1131 * @param {Logger} options.logger a logger
1132 * @param {SnapshotOptions} options.snapshot options regarding snapshotting
1133 * @param {number} options.maxAge max age of cache items
1134 * @param {boolean=} options.profile track and log detailed timing information for individual cache items
1135 * @param {boolean=} options.allowCollectingMemory allow to collect unused memory created during deserialization
1136 * @param {false | "gzip" | "brotli"=} options.compression compression used
1137 * @param {boolean=} options.readonly disable storing cache into filesystem
1138 */
1139 constructor({
1140 compiler,
1141 fs,
1142 context,
1143 cacheLocation,
1144 version,
1145 logger,
1146 snapshot,
1147 maxAge,
1148 profile,
1149 allowCollectingMemory,
1150 compression,
1151 readonly
1152 }) {
1153 /** @type {import("../serialization/Serializer")<PackContainer, null, EXPECTED_OBJECT>} */
1154 this.fileSerializer = createFileSerializer(
1155 fs,
1156 /** @type {HashFunction} */
1157 (compiler.options.output.hashFunction)
1158 );
1159 /** @type {FileSystemInfo} */
1160 this.fileSystemInfo = new FileSystemInfo(fs, {
1161 managedPaths: snapshot.managedPaths,
1162 immutablePaths: snapshot.immutablePaths,
1163 logger: logger.getChildLogger("webpack.FileSystemInfo"),
1164 hashFunction: compiler.options.output.hashFunction
1165 });
1166 /** @type {Compiler} */
1167 this.compiler = compiler;
1168 /** @type {string} */
1169 this.context = context;
1170 /** @type {string} */
1171 this.cacheLocation = cacheLocation;
1172 /** @type {string} */
1173 this.version = version;
1174 /** @type {Logger} */
1175 this.logger = logger;
1176 /** @type {number} */
1177 this.maxAge = maxAge;
1178 /** @type {boolean | undefined} */
1179 this.profile = profile;
1180 /** @type {boolean | undefined} */
1181 this.readonly = readonly;
1182 /** @type {boolean | undefined} */
1183 this.allowCollectingMemory = allowCollectingMemory;
1184 /** @type {false | "gzip" | "brotli" | undefined} */
1185 this.compression = compression;
1186 /** @type {string} */
1187 this._extension =
1188 compression === "brotli"
1189 ? ".pack.br"
1190 : compression === "gzip"
1191 ? ".pack.gz"
1192 : ".pack";
1193 /** @type {SnapshotOptions} */
1194 this.snapshot = snapshot;
1195 /** @type {BuildDependencies} */
1196 this.buildDependencies = new Set();
1197 /** @type {FileSystemDependencies} */
1198 this.newBuildDependencies = new LazySet();
1199 /** @type {Snapshot | undefined} */
1200 this.resolveBuildDependenciesSnapshot = undefined;
1201 /** @type {ResolveResults | undefined} */
1202 this.resolveResults = undefined;
1203 /** @type {Snapshot | undefined} */
1204 this.buildSnapshot = undefined;
1205 /** @type {Promise<Pack> | undefined} */
1206 this.packPromise = this._openPack();
1207 /** @type {Promise<void>} */
1208 this.storePromise = Promise.resolve();
1209 }
1210
1211 /**
1212 * Returns pack.
1213 * @returns {Promise<Pack>} pack
1214 */
1215 _getPack() {
1216 if (this.packPromise === undefined) {
1217 this.packPromise = this.storePromise.then(() => this._openPack());
1218 }
1219 return this.packPromise;
1220 }
1221
1222 /**
1223 * Returns the pack.
1224 * @returns {Promise<Pack>} the pack
1225 */
1226 _openPack() {
1227 const { logger, profile, cacheLocation, version } = this;
1228 /** @type {Snapshot} */
1229 let buildSnapshot;
1230 /** @type {BuildDependencies} */
1231 let buildDependencies;
1232 /** @type {BuildDependencies} */
1233 let newBuildDependencies;
1234 /** @type {Snapshot} */
1235 let resolveBuildDependenciesSnapshot;
1236 /** @type {ResolveResults | undefined} */
1237 let resolveResults;
1238 logger.time("restore cache container");
1239 return this.fileSerializer
1240 .deserialize(null, {
1241 filename: `${cacheLocation}/index${this._extension}`,
1242 extension: `${this._extension}`,
1243 logger,
1244 profile,
1245 retainedBuffer: this.allowCollectingMemory
1246 ? allowCollectingMemory
1247 : undefined
1248 })
1249 .catch((err) => {
1250 if (err.code !== "ENOENT") {
1251 logger.warn(
1252 `Restoring pack failed from ${cacheLocation}${this._extension}: ${err}`
1253 );
1254 logger.debug(err.stack);
1255 } else {
1256 logger.debug(
1257 `No pack exists at ${cacheLocation}${this._extension}: ${err}`
1258 );
1259 }
1260 return undefined;
1261 })
1262 .then((packContainer) => {
1263 logger.timeEnd("restore cache container");
1264 if (!packContainer) return;
1265 if (!(packContainer instanceof PackContainer)) {
1266 logger.warn(
1267 `Restored pack from ${cacheLocation}${this._extension}, but contained content is unexpected.`,
1268 packContainer
1269 );
1270 return;
1271 }
1272 if (packContainer.version !== version) {
1273 logger.log(
1274 `Restored pack from ${cacheLocation}${this._extension}, but version doesn't match.`
1275 );
1276 return;
1277 }
1278 logger.time("check build dependencies");
1279 return Promise.all([
1280 new Promise((resolve, _reject) => {
1281 this.fileSystemInfo.checkSnapshotValid(
1282 packContainer.buildSnapshot,
1283 (err, valid) => {
1284 if (err) {
1285 logger.log(
1286 `Restored pack from ${cacheLocation}${this._extension}, but checking snapshot of build dependencies errored: ${err}.`
1287 );
1288 logger.debug(err.stack);
1289 return resolve(false);
1290 }
1291 if (!valid) {
1292 logger.log(
1293 `Restored pack from ${cacheLocation}${this._extension}, but build dependencies have changed.`
1294 );
1295 return resolve(false);
1296 }
1297 buildSnapshot = packContainer.buildSnapshot;
1298 return resolve(true);
1299 }
1300 );
1301 }),
1302 new Promise((resolve, _reject) => {
1303 this.fileSystemInfo.checkSnapshotValid(
1304 packContainer.resolveBuildDependenciesSnapshot,
1305 (err, valid) => {
1306 if (err) {
1307 logger.log(
1308 `Restored pack from ${cacheLocation}${this._extension}, but checking snapshot of resolving of build dependencies errored: ${err}.`
1309 );
1310 logger.debug(err.stack);
1311 return resolve(false);
1312 }
1313 if (valid) {
1314 resolveBuildDependenciesSnapshot =
1315 packContainer.resolveBuildDependenciesSnapshot;
1316 buildDependencies = packContainer.buildDependencies;
1317 resolveResults = packContainer.resolveResults;
1318 return resolve(true);
1319 }
1320 logger.log(
1321 "resolving of build dependencies is invalid, will re-resolve build dependencies"
1322 );
1323 this.fileSystemInfo.checkResolveResultsValid(
1324 packContainer.resolveResults,
1325 (err, valid) => {
1326 if (err) {
1327 logger.log(
1328 `Restored pack from ${cacheLocation}${this._extension}, but resolving of build dependencies errored: ${err}.`
1329 );
1330 logger.debug(err.stack);
1331 return resolve(false);
1332 }
1333 if (valid) {
1334 newBuildDependencies = packContainer.buildDependencies;
1335 resolveResults = packContainer.resolveResults;
1336 return resolve(true);
1337 }
1338 logger.log(
1339 `Restored pack from ${cacheLocation}${this._extension}, but build dependencies resolve to different locations.`
1340 );
1341 return resolve(false);
1342 }
1343 );
1344 }
1345 );
1346 })
1347 ])
1348 .catch((err) => {
1349 logger.timeEnd("check build dependencies");
1350 throw err;
1351 })
1352 .then(([buildSnapshotValid, resolveValid]) => {
1353 logger.timeEnd("check build dependencies");
1354 if (buildSnapshotValid && resolveValid) {
1355 logger.time("restore cache content metadata");
1356 const d =
1357 /** @type {() => Pack} */
1358 (packContainer.data)();
1359 logger.timeEnd("restore cache content metadata");
1360 return d;
1361 }
1362 return undefined;
1363 });
1364 })
1365 .then((pack) => {
1366 if (pack) {
1367 pack.maxAge = this.maxAge;
1368 this.buildSnapshot = buildSnapshot;
1369 if (buildDependencies) this.buildDependencies = buildDependencies;
1370 if (newBuildDependencies) {
1371 this.newBuildDependencies.addAll(newBuildDependencies);
1372 }
1373 this.resolveResults = resolveResults;
1374 this.resolveBuildDependenciesSnapshot =
1375 resolveBuildDependenciesSnapshot;
1376 return pack;
1377 }
1378 return new Pack(logger, this.maxAge);
1379 })
1380 .catch((err) => {
1381 this.logger.warn(
1382 `Restoring pack from ${cacheLocation}${this._extension} failed: ${err}`
1383 );
1384 this.logger.debug(err.stack);
1385 return new Pack(logger, this.maxAge);
1386 });
1387 }
1388
1389 /**
1390 * Returns promise.
1391 * @param {string} identifier unique name for the resource
1392 * @param {Etag | null} etag etag of the resource
1393 * @param {Data} data cached content
1394 * @returns {Promise<void>} promise
1395 */
1396 store(identifier, etag, data) {
1397 if (this.readonly) return Promise.resolve();
1398
1399 return this._getPack().then((pack) => {
1400 pack.set(identifier, etag === null ? null : etag.toString(), data);
1401 });
1402 }
1403
1404 /**
1405 * Returns promise to the cached content.
1406 * @param {string} identifier unique name for the resource
1407 * @param {Etag | null} etag etag of the resource
1408 * @returns {Promise<Data>} promise to the cached content
1409 */
1410 restore(identifier, etag) {
1411 return this._getPack()
1412 .then((pack) =>
1413 pack.get(identifier, etag === null ? null : etag.toString())
1414 )
1415 .catch((err) => {
1416 if (err && err.code !== "ENOENT") {
1417 this.logger.warn(
1418 `Restoring failed for ${identifier} from pack: ${err}`
1419 );
1420 this.logger.debug(err.stack);
1421 }
1422 });
1423 }
1424
1425 /**
1426 * Stores build dependencies.
1427 * @param {FileSystemDependencies | Iterable<string>} dependencies dependencies to store
1428 */
1429 storeBuildDependencies(dependencies) {
1430 if (this.readonly) return;
1431 this.newBuildDependencies.addAll(dependencies);
1432 }
1433
1434 afterAllStored() {
1435 const packPromise = this.packPromise;
1436 if (packPromise === undefined) return Promise.resolve();
1437 const reportProgress = ProgressPlugin.getReporter(this.compiler);
1438 return (this.storePromise = packPromise
1439 .then((pack) => {
1440 pack.stopCapturingRequests();
1441 if (!pack.invalid) return;
1442 this.packPromise = undefined;
1443 this.logger.log("Storing pack...");
1444 /** @type {undefined | Promise<void>} */
1445 let promise;
1446 /** @type {Set<string>} */
1447 const newBuildDependencies = new Set();
1448 for (const dep of this.newBuildDependencies) {
1449 if (!this.buildDependencies.has(dep)) {
1450 newBuildDependencies.add(dep);
1451 }
1452 }
1453 if (newBuildDependencies.size > 0 || !this.buildSnapshot) {
1454 if (reportProgress) reportProgress(0.5, "resolve build dependencies");
1455 this.logger.debug(
1456 `Capturing build dependencies... (${[...newBuildDependencies].join(", ")})`
1457 );
1458 promise = new Promise(
1459 /**
1460 * Handles the callback logic for this hook.
1461 * @param {(value?: undefined) => void} resolve resolve
1462 * @param {(reason?: Error) => void} reject reject
1463 */
1464 (resolve, reject) => {
1465 this.logger.time("resolve build dependencies");
1466 this.fileSystemInfo.resolveBuildDependencies(
1467 this.context,
1468 newBuildDependencies,
1469 (err, result) => {
1470 this.logger.timeEnd("resolve build dependencies");
1471 if (err) return reject(err);
1472
1473 this.logger.time("snapshot build dependencies");
1474 const {
1475 files,
1476 directories,
1477 missing,
1478 resolveResults,
1479 resolveDependencies
1480 } = /** @type {ResolveBuildDependenciesResult} */ (result);
1481 if (this.resolveResults) {
1482 for (const [key, value] of resolveResults) {
1483 this.resolveResults.set(key, value);
1484 }
1485 } else {
1486 this.resolveResults = resolveResults;
1487 }
1488 if (reportProgress) {
1489 reportProgress(
1490 0.6,
1491 "snapshot build dependencies",
1492 "resolving"
1493 );
1494 }
1495 this.fileSystemInfo.createSnapshot(
1496 undefined,
1497 resolveDependencies.files,
1498 resolveDependencies.directories,
1499 resolveDependencies.missing,
1500 this.snapshot.resolveBuildDependencies,
1501 (err, snapshot) => {
1502 if (err) {
1503 this.logger.timeEnd("snapshot build dependencies");
1504 return reject(err);
1505 }
1506 if (!snapshot) {
1507 this.logger.timeEnd("snapshot build dependencies");
1508 return reject(
1509 new Error("Unable to snapshot resolve dependencies")
1510 );
1511 }
1512 if (this.resolveBuildDependenciesSnapshot) {
1513 this.resolveBuildDependenciesSnapshot =
1514 this.fileSystemInfo.mergeSnapshots(
1515 this.resolveBuildDependenciesSnapshot,
1516 snapshot
1517 );
1518 } else {
1519 this.resolveBuildDependenciesSnapshot = snapshot;
1520 }
1521 if (reportProgress) {
1522 reportProgress(
1523 0.7,
1524 "snapshot build dependencies",
1525 "modules"
1526 );
1527 }
1528 this.fileSystemInfo.createSnapshot(
1529 undefined,
1530 files,
1531 directories,
1532 missing,
1533 this.snapshot.buildDependencies,
1534 (err, snapshot) => {
1535 this.logger.timeEnd("snapshot build dependencies");
1536 if (err) return reject(err);
1537 if (!snapshot) {
1538 return reject(
1539 new Error("Unable to snapshot build dependencies")
1540 );
1541 }
1542 this.logger.debug("Captured build dependencies");
1543
1544 if (this.buildSnapshot) {
1545 this.buildSnapshot =
1546 this.fileSystemInfo.mergeSnapshots(
1547 this.buildSnapshot,
1548 snapshot
1549 );
1550 } else {
1551 this.buildSnapshot = snapshot;
1552 }
1553
1554 resolve();
1555 }
1556 );
1557 }
1558 );
1559 }
1560 );
1561 }
1562 );
1563 } else {
1564 promise = Promise.resolve();
1565 }
1566 return promise.then(() => {
1567 if (reportProgress) reportProgress(0.8, "serialize pack");
1568 this.logger.time("store pack");
1569 const updatedBuildDependencies = new Set(this.buildDependencies);
1570 for (const dep of newBuildDependencies) {
1571 updatedBuildDependencies.add(dep);
1572 }
1573 const content = new PackContainer(
1574 pack,
1575 this.version,
1576 /** @type {Snapshot} */
1577 (this.buildSnapshot),
1578 updatedBuildDependencies,
1579 /** @type {ResolveResults} */
1580 (this.resolveResults),
1581 /** @type {Snapshot} */
1582 (this.resolveBuildDependenciesSnapshot)
1583 );
1584 return this.fileSerializer
1585 .serialize(content, {
1586 filename: `${this.cacheLocation}/index${this._extension}`,
1587 extension: `${this._extension}`,
1588 logger: this.logger,
1589 profile: this.profile
1590 })
1591 .then(() => {
1592 for (const dep of newBuildDependencies) {
1593 this.buildDependencies.add(dep);
1594 }
1595 this.newBuildDependencies.clear();
1596 this.logger.timeEnd("store pack");
1597 const stats = pack.getContentStats();
1598 this.logger.log(
1599 "Stored pack (%d items, %d files, %d MiB)",
1600 pack.itemInfo.size,
1601 stats.count,
1602 Math.round(stats.size / 1024 / 1024)
1603 );
1604 })
1605 .catch((err) => {
1606 this.logger.timeEnd("store pack");
1607 this.logger.warn(`Caching failed for pack: ${err}`);
1608 this.logger.debug(err.stack);
1609 });
1610 });
1611 })
1612 .catch((err) => {
1613 this.logger.warn(`Caching failed for pack: ${err}`);
1614 this.logger.debug(err.stack);
1615 }));
1616 }
1617
1618 clear() {
1619 this.fileSystemInfo.clear();
1620 this.buildDependencies.clear();
1621 this.newBuildDependencies.clear();
1622 this.resolveBuildDependenciesSnapshot = undefined;
1623 this.resolveResults = undefined;
1624 this.buildSnapshot = undefined;
1625 this.packPromise = undefined;
1626 }
1627}
1628
1629module.exports = PackFileCacheStrategy;
Note: See TracBrowser for help on using the repository browser.