PackFileCacheStrategy.js 41 KB

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