PackFileCacheStrategy.js 44 KB

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