PackFileCacheStrategy.js 47 KB

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