PackFileCacheStrategy.js 45 KB

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