PackFileCacheStrategy.js 42 KB

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