PackFileCacheStrategy.js 43 KB

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