webpack 5.107.1 → 5.107.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Compiler.js CHANGED
@@ -491,6 +491,25 @@ class Compiler {
491
491
  }
492
492
  }
493
493
 
494
+ /**
495
+ * Release fields on a finished compilation that nothing reads after emit,
496
+ * so the heap can shrink while user code still holds the Stats reference.
497
+ * Recurses into child compilations. Stats output is preserved — only
498
+ * codeGen byproducts are dropped.
499
+ * @param {Compilation} compilation finished compilation to slim down
500
+ * @returns {void}
501
+ */
502
+ _releaseUnusedCompilationData(compilation) {
503
+ for (const child of compilation.children) {
504
+ this._releaseUnusedCompilationData(child);
505
+ }
506
+ // Rendered source per (module × runtime) — used only during seal/emit,
507
+ // never read by Stats, and not serialized to the persistent cache.
508
+ if (compilation.codeGenerationResults !== undefined) {
509
+ compilation.codeGenerationResults.map.clear();
510
+ }
511
+ }
512
+
494
513
  /**
495
514
  * Returns a compiler watcher.
496
515
  * @param {WatchOptions} watchOptions the watcher's options
@@ -1439,9 +1458,14 @@ ${other}`);
1439
1458
  }
1440
1459
  this.hooks.shutdown.callAsync((err) => {
1441
1460
  if (err) return callback(err);
1442
- // Get rid of reference to last compilation to avoid leaking memory
1443
- // We can't run this._cleanupLastCompilation() as the Stats to this compilation
1444
- // might be still in use. We try to get rid of the reference to the cache instead.
1461
+ // Defer a microtask so a close() made inside the run callback can't
1462
+ // release codeGenerationResults before afterDone fires on the same stack.
1463
+ const lastCompilation = this._lastCompilation;
1464
+ if (lastCompilation !== undefined) {
1465
+ Promise.resolve().then(() => {
1466
+ this._releaseUnusedCompilationData(lastCompilation);
1467
+ });
1468
+ }
1445
1469
  this._lastCompilation = undefined;
1446
1470
  this._lastNormalModuleFactory = undefined;
1447
1471
  this.cache.shutdown(callback);
@@ -379,6 +379,8 @@ class ContextModuleFactory extends ModuleFactory {
379
379
  if (!processedFiles || processedFiles.length === 0) {
380
380
  return callback(null, []);
381
381
  }
382
+ /** @type {ContextAlternativeRequest[]} */
383
+ const fileObjs = [];
382
384
  asyncLib.map(
383
385
  processedFiles.filter((p) => p.indexOf(".") !== 0),
384
386
  (segment, callback) => {
@@ -404,41 +406,16 @@ class ContextModuleFactory extends ModuleFactory {
404
406
  stat.isFile() &&
405
407
  (!include || include.test(subResource))
406
408
  ) {
407
- /** @type {{ context: string, request: string }} */
408
- const obj = {
409
+ // Collect for a single batched alternativeRequests call
410
+ // per directory below. Calling the hook once per file
411
+ // would pay per-call overhead (closure, resolverFactory
412
+ // lookup, array allocations) for every file in the
413
+ // context — which is the bulk of work on rebuilds.
414
+ fileObjs.push({
409
415
  context: ctx,
410
416
  request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
411
- };
412
-
413
- this.hooks.alternativeRequests.callAsync(
414
- [obj],
415
- options,
416
- (err, alternatives) => {
417
- if (err) return callback(err);
418
- callback(
419
- null,
420
- /** @type {ContextAlternativeRequest[]} */
421
- (alternatives)
422
- .filter((obj) =>
423
- regExp.test(/** @type {string} */ (obj.request))
424
- )
425
- .map((obj) => {
426
- const dep = new ContextElementDependency(
427
- `${obj.request}${resourceQuery}${resourceFragment}`,
428
- obj.request,
429
- typePrefix,
430
- /** @type {string} */
431
- (category),
432
- referencedExports,
433
- obj.context,
434
- attributes
435
- );
436
- dep.optional = true;
437
- return dep;
438
- })
439
- );
440
- }
441
- );
417
+ });
418
+ callback();
442
419
  } else {
443
420
  callback();
444
421
  }
@@ -450,16 +427,46 @@ class ContextModuleFactory extends ModuleFactory {
450
427
  (err, result) => {
451
428
  if (err) return callback(err);
452
429
 
453
- if (!result) return callback(null, []);
454
-
455
430
  /** @type {ContextElementDependency[]} */
456
431
  const flattenedResult = [];
457
432
 
458
- for (const item of result) {
459
- if (item) flattenedResult.push(...item);
433
+ if (result) {
434
+ for (const item of result) {
435
+ if (item) flattenedResult.push(...item);
436
+ }
460
437
  }
461
438
 
462
- callback(null, flattenedResult);
439
+ if (fileObjs.length === 0) {
440
+ return callback(null, flattenedResult);
441
+ }
442
+
443
+ this.hooks.alternativeRequests.callAsync(
444
+ fileObjs,
445
+ options,
446
+ (err, alternatives) => {
447
+ if (err) return callback(err);
448
+ for (const alt of /** @type {ContextAlternativeRequest[]} */ (
449
+ alternatives
450
+ )) {
451
+ if (!regExp.test(/** @type {string} */ (alt.request))) {
452
+ continue;
453
+ }
454
+ const dep = new ContextElementDependency(
455
+ `${alt.request}${resourceQuery}${resourceFragment}`,
456
+ alt.request,
457
+ typePrefix,
458
+ /** @type {string} */
459
+ (category),
460
+ referencedExports,
461
+ alt.context,
462
+ attributes
463
+ );
464
+ dep.optional = true;
465
+ flattenedResult.push(dep);
466
+ }
467
+ callback(null, flattenedResult);
468
+ }
469
+ );
463
470
  }
464
471
  );
465
472
  });
@@ -128,6 +128,20 @@ module.exports = class MultiCompiler {
128
128
  doneCompilers--;
129
129
  }
130
130
  });
131
+ // Release fields on this child's Compilation once it's done. The
132
+ // stage: Infinity tap runs after every afterDone tap at a lower
133
+ // stage, so plugins observing compilation state in afterDone still
134
+ // see it intact. Stats remains usable; only fields Stats never reads
135
+ // (and that the persistent cache never serializes) are dropped.
136
+
137
+ compiler.hooks.afterDone.tap(
138
+ { name: CLASS_NAME, stage: Infinity },
139
+ (stats) => {
140
+ if (stats !== undefined) {
141
+ compiler._releaseUnusedCompilationData(stats.compilation);
142
+ }
143
+ }
144
+ );
131
145
  }
132
146
  this._validateCompilersOptions();
133
147
  }