@depup/webpack 5.109.0-depup.0 → 5.109.2-depup.0

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.
Files changed (62) hide show
  1. package/README.md +4 -4
  2. package/bin/webpack.js +12 -0
  3. package/changes.json +4 -4
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/CleanPlugin.js +11 -9
  6. package/lib/CompatibilityPlugin.js +31 -0
  7. package/lib/Compilation.js +21 -5
  8. package/lib/Compiler.js +16 -4
  9. package/lib/DefinePlugin.js +47 -20
  10. package/lib/ExportsInfo.js +22 -5
  11. package/lib/ExternalModule.js +10 -7
  12. package/lib/FileSystemInfo.js +126 -26
  13. package/lib/FlagDependencyUsagePlugin.js +33 -12
  14. package/lib/InitFragment.js +20 -32
  15. package/lib/ModuleFilenameHelpers.js +13 -2
  16. package/lib/NormalModule.js +80 -71
  17. package/lib/NormalModuleFactory.js +8 -2
  18. package/lib/RuntimePlugin.js +40 -113
  19. package/lib/Template.js +23 -3
  20. package/lib/cache/PackFileCacheStrategy.js +259 -5
  21. package/lib/config/defaults.js +21 -2
  22. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  23. package/lib/container/ModuleFederationPlugin.js +15 -9
  24. package/lib/css/CssGenerator.js +8 -2
  25. package/lib/css/CssLoadingRuntimeModule.js +25 -12
  26. package/lib/css/CssModulesPlugin.js +29 -18
  27. package/lib/css/CssParser.js +135 -50
  28. package/lib/css/syntax.js +862 -765
  29. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  30. package/lib/dependencies/CommonJsFullRequireDependency.js +33 -4
  31. package/lib/dependencies/CommonJsImportsParserPlugin.js +2 -0
  32. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  33. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  34. package/lib/dependencies/HarmonyImportDependency.js +1 -1
  35. package/lib/dependencies/HarmonyImportGuard.js +2 -1
  36. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  37. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +15 -9
  38. package/lib/hmr/lazyCompilationBackend.js +19 -4
  39. package/lib/html/HtmlGenerator.js +23 -3
  40. package/lib/html/HtmlModulesPlugin.js +30 -12
  41. package/lib/html/syntax.js +2485 -2399
  42. package/lib/ids/IdHelpers.js +24 -10
  43. package/lib/javascript/JavascriptModulesPlugin.js +83 -49
  44. package/lib/javascript/JavascriptParser.js +49 -13
  45. package/lib/javascript/syntax.js +700 -43
  46. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +10 -2
  47. package/lib/node/ReadFileCompileWasmPlugin.js +10 -2
  48. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  49. package/lib/optimize/ConcatenatedModule.js +19 -15
  50. package/lib/optimize/ModuleConcatenationPlugin.js +73 -24
  51. package/lib/optimize/RealContentHashPlugin.js +10 -7
  52. package/lib/optimize/SplitChunksPlugin.js +146 -44
  53. package/lib/prefetch/ResourceHintPlugin.js +96 -72
  54. package/lib/serialization/BinaryMiddleware.js +270 -938
  55. package/lib/serialization/FileMiddleware.js +299 -60
  56. package/lib/stats/DefaultStatsFactoryPlugin.js +204 -198
  57. package/lib/stats/DefaultStatsPrinterPlugin.js +24 -166
  58. package/lib/stats/StatsFactory.js +31 -9
  59. package/lib/util/identifier.js +21 -1
  60. package/package.json +10 -10
  61. package/schemas/WebpackOptions.json +110 -55
  62. package/types.d.ts +832 -247
@@ -7,6 +7,7 @@
7
7
 
8
8
  const FileSystemInfo = require("../FileSystemInfo");
9
9
  const ProgressPlugin = require("../ProgressPlugin");
10
+ const { getReferencedFilenames } = require("../serialization/FileMiddleware");
10
11
  const SerializerMiddleware = require("../serialization/SerializerMiddleware");
11
12
  const LazySet = require("../util/LazySet");
12
13
  const formatSize = require("../util/formatSize");
@@ -34,6 +35,17 @@ const {
34
35
  /** @typedef {Set<string>} Items */
35
36
  /** @typedef {Set<string>} BuildDependencies */
36
37
  /** @typedef {Map<string, PackItemInfo>} ItemInfo */
38
+ /** @typedef {{ firstSeen: number, size: number }} UnreferencedFile */
39
+ /** @typedef {Map<string, UnreferencedFile>} UnreferencedFiles */
40
+
41
+ // Unreferenced files are kept for this long to not race concurrent builds sharing the cache directory.
42
+ const CLEANUP_GRACE_PERIOD = 30 * 60 * 1000;
43
+ // Records when each unreferenced file was first seen. Aging by recorded time keeps
44
+ // orphans expiring across caches restored with refreshed modification times.
45
+ const UNREFERENCED_FILE = "unreferenced.json";
46
+ // A file written this recently may belong to a concurrent build that reused the name,
47
+ // in which case the recorded time describes the previous file and must not be trusted.
48
+ const CLEANUP_RECENT_WRITE_PERIOD = 60 * 1000;
37
49
 
38
50
  class PackContainer {
39
51
  /**
@@ -569,6 +581,41 @@ class Pack {
569
581
  }
570
582
  }
571
583
 
584
+ /**
585
+ * Drops every content whose items all expired. Unlike a partial collection this
586
+ * never unpacks, so it is not limited to a single content per store and lets a
587
+ * long unused cache shrink in one go instead of one pack per build.
588
+ */
589
+ _gcExpiredContent() {
590
+ const now = Date.now();
591
+ let packCount = 0;
592
+ let itemCount = 0;
593
+ for (let loc = 0; loc < this.content.length; loc++) {
594
+ const content = this.content[loc];
595
+ if (!content) continue;
596
+ let expired = true;
597
+ for (const identifier of content.items) {
598
+ const info = this.itemInfo.get(identifier);
599
+ if (info !== undefined && now - info.lastAccess <= this.maxAge) {
600
+ expired = false;
601
+ break;
602
+ }
603
+ }
604
+ if (!expired) continue;
605
+ for (const identifier of content.items) this.itemInfo.delete(identifier);
606
+ this.content[loc] = undefined;
607
+ packCount++;
608
+ itemCount += content.items.size;
609
+ }
610
+ if (packCount > 0) {
611
+ this.logger.log(
612
+ "Garbage Collected %d completely expired packs with %d items",
613
+ packCount,
614
+ itemCount
615
+ );
616
+ }
617
+ }
618
+
572
619
  /**
573
620
  * Find the content with the oldest item and run GC on that.
574
621
  * Only runs for one content to avoid large invalidation.
@@ -581,11 +628,10 @@ class Pack {
581
628
  oldest = info;
582
629
  }
583
630
  }
584
- if (
585
- Date.now() - /** @type {PackItemInfo} */ (oldest).lastAccess >
586
- this.maxAge
587
- ) {
588
- const loc = /** @type {PackItemInfo} */ (oldest).location;
631
+ // collecting expired content may have left no items at all
632
+ if (oldest === undefined) return;
633
+ if (Date.now() - oldest.lastAccess > this.maxAge) {
634
+ const loc = oldest.location;
589
635
  if (loc < 0) return;
590
636
  const content = /** @type {PackContent} */ (this.content[loc]);
591
637
  const items = new Set(content.items);
@@ -621,6 +667,7 @@ class Pack {
621
667
  this._persistFreshContent();
622
668
  this._optimizeSmallContent();
623
669
  this._optimizeUnusedContent();
670
+ this._gcExpiredContent();
624
671
  this._gcOldestContent();
625
672
  for (const identifier of this.itemInfo.keys()) {
626
673
  write(identifier);
@@ -1166,6 +1213,8 @@ class PackFileCacheStrategy {
1166
1213
  });
1167
1214
  /** @type {Compiler} */
1168
1215
  this.compiler = compiler;
1216
+ /** @type {IntermediateFileSystem} */
1217
+ this.fs = fs;
1169
1218
  /** @type {string} */
1170
1219
  this.context = context;
1171
1220
  /** @type {string} */
@@ -1182,6 +1231,10 @@ class PackFileCacheStrategy {
1182
1231
  this.readonly = readonly;
1183
1232
  /** @type {boolean | undefined} */
1184
1233
  this.allowCollectingMemory = allowCollectingMemory;
1234
+ // referenced names per on-disk file, versioned by mtime since concurrent
1235
+ // processes may rewrite packs in place under the same name
1236
+ /** @type {Map<string, { mtimeMs: number, referenced: string[] }>} */
1237
+ this._referencedFilesCache = new Map();
1185
1238
  /** @type {false | "gzip" | "brotli" | "zstd" | undefined} */
1186
1239
  this.compression = compression;
1187
1240
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
@@ -1588,10 +1641,17 @@ class PackFileCacheStrategy {
1588
1641
  /** @type {Snapshot} */
1589
1642
  (this.resolveBuildDependenciesSnapshot)
1590
1643
  );
1644
+ const cleanup = this.fs.unlink !== undefined;
1645
+ /** @type {Set<string> | undefined} */
1646
+ const writtenFiles = cleanup ? new Set() : undefined;
1647
+ /** @type {Set<string> | undefined} */
1648
+ const retainedFiles = cleanup ? new Set() : undefined;
1591
1649
  return this.fileSerializer
1592
1650
  .serialize(content, {
1593
1651
  filename: `${this.cacheLocation}/index${this._extension}`,
1594
1652
  extension: `${this._extension}`,
1653
+ writtenFiles,
1654
+ retainedFiles,
1595
1655
  logger: this.logger,
1596
1656
  profile: this.profile
1597
1657
  })
@@ -1608,9 +1668,17 @@ class PackFileCacheStrategy {
1608
1668
  stats.count,
1609
1669
  Math.round(stats.size / 1024 / 1024)
1610
1670
  );
1671
+ if (writtenFiles !== undefined) {
1672
+ return this._cleanupUnusedFiles(
1673
+ writtenFiles,
1674
+ /** @type {Set<string>} */ (retainedFiles)
1675
+ );
1676
+ }
1611
1677
  })
1612
1678
  .catch((err) => {
1613
1679
  this.logger.timeEnd("store pack");
1680
+ // files may be in an unknown state after a failed store
1681
+ this._referencedFilesCache.clear();
1614
1682
  this.logger.warn(`Caching failed for pack: ${err}`);
1615
1683
  this.logger.debug(err.stack);
1616
1684
  });
@@ -1622,6 +1690,192 @@ class PackFileCacheStrategy {
1622
1690
  }));
1623
1691
  }
1624
1692
 
1693
+ /**
1694
+ * Reads when the currently unreferenced files were first seen. A missing or
1695
+ * unreadable file just restarts the grace period for every orphan.
1696
+ * @returns {Promise<UnreferencedFiles>} first seen time and size per file name
1697
+ */
1698
+ _readUnreferencedFiles() {
1699
+ return new Promise((resolve) => {
1700
+ this.fs.readFile(
1701
+ `${this.cacheLocation}/${UNREFERENCED_FILE}`,
1702
+ (err, content) => {
1703
+ /** @type {UnreferencedFiles} */
1704
+ const result = new Map();
1705
+ if (err) return resolve(result);
1706
+ try {
1707
+ const data = JSON.parse(
1708
+ /** @type {Buffer} */ (content).toString("utf8")
1709
+ );
1710
+ for (const [file, entry] of Object.entries(data)) {
1711
+ const { firstSeen, size } = /** @type {UnreferencedFile} */ (
1712
+ entry
1713
+ );
1714
+ if (typeof firstSeen === "number" && typeof size === "number") {
1715
+ result.set(file, { firstSeen, size });
1716
+ }
1717
+ }
1718
+ } catch (_err) {
1719
+ result.clear();
1720
+ }
1721
+ resolve(result);
1722
+ }
1723
+ );
1724
+ });
1725
+ }
1726
+
1727
+ /**
1728
+ * Persists when the still unreferenced files were first seen. Failing to write
1729
+ * only costs the orphans another grace period, so errors are ignored.
1730
+ * @param {UnreferencedFiles} unreferenced first seen time and size per file name
1731
+ * @param {boolean} hadEntries whether a previous state exists that must be replaced
1732
+ * @returns {Promise<void>} promise
1733
+ */
1734
+ _writeUnreferencedFiles(unreferenced, hadEntries) {
1735
+ if (unreferenced.size === 0 && !hadEntries) return Promise.resolve();
1736
+ /** @type {Record<string, UnreferencedFile>} */
1737
+ const data = {};
1738
+ for (const [file, entry] of unreferenced) data[file] = entry;
1739
+ return new Promise((resolve) => {
1740
+ this.fs.writeFile(
1741
+ `${this.cacheLocation}/${UNREFERENCED_FILE}`,
1742
+ JSON.stringify(data),
1743
+ () => resolve()
1744
+ );
1745
+ });
1746
+ }
1747
+
1748
+ /**
1749
+ * Deletes files from the cache directory that are no longer referenced by the
1750
+ * stored pack. Retained files are walked on disk since nested lazy segments
1751
+ * reference files not visible during serialization. Errors only log a warning.
1752
+ * @param {Set<string>} writtenNames names (without extension) written by this store
1753
+ * @param {Set<string>} retainedNames names (without extension) referenced but not rewritten
1754
+ * @returns {Promise<void>} promise
1755
+ */
1756
+ async _cleanupUnusedFiles(writtenNames, retainedNames) {
1757
+ this.logger.time("cleanup unused cache files");
1758
+ const fs = this.fs;
1759
+ const extension = this._extension;
1760
+ const cacheLocation = this.cacheLocation;
1761
+ const referencedFilesCache = this._referencedFilesCache;
1762
+ try {
1763
+ // rewritten files may reference different names now
1764
+ for (const name of writtenNames) referencedFilesCache.delete(name);
1765
+ /** @type {Set<string>} */
1766
+ const liveFiles = new Set([`index${extension}`, UNREFERENCED_FILE]);
1767
+ for (const name of writtenNames) liveFiles.add(`${name}${extension}`);
1768
+ /** @type {string[]} */
1769
+ const queue = [];
1770
+ /**
1771
+ * Marks a file live and queues it for walking its references.
1772
+ * @param {string} name file name without extension
1773
+ */
1774
+ const enqueue = (name) => {
1775
+ const file = `${name}${extension}`;
1776
+ if (liveFiles.has(file)) return;
1777
+ liveFiles.add(file);
1778
+ queue.push(name);
1779
+ };
1780
+ for (const name of retainedNames) enqueue(name);
1781
+ while (queue.length > 0) {
1782
+ const name = /** @type {string} */ (queue.pop());
1783
+ const file = `${cacheLocation}/${name}${extension}`;
1784
+ // an unchanged mtime proves the memo entry still matches the disk
1785
+ const mtimeMs = await new Promise((resolve, reject) => {
1786
+ fs.stat(file, (err, stats) => {
1787
+ if (err) return reject(err);
1788
+ resolve(
1789
+ /** @type {number} */ (
1790
+ /** @type {import("../util/fs").IStats} */ (stats).mtimeMs
1791
+ )
1792
+ );
1793
+ });
1794
+ });
1795
+ const entry = referencedFilesCache.get(name);
1796
+ let referenced;
1797
+ if (entry !== undefined && entry.mtimeMs === mtimeMs) {
1798
+ referenced = entry.referenced;
1799
+ } else {
1800
+ referenced = await getReferencedFilenames(fs, file);
1801
+ referencedFilesCache.set(name, { mtimeMs, referenced });
1802
+ }
1803
+ for (const referencedName of referenced) enqueue(referencedName);
1804
+ }
1805
+ const files = await new Promise((resolve, reject) => {
1806
+ fs.readdir(cacheLocation, (err, files) => {
1807
+ if (err) return reject(err);
1808
+ resolve(/** @type {string[]} */ (files));
1809
+ });
1810
+ });
1811
+ const seenFiles = await this._readUnreferencedFiles();
1812
+ const now = Date.now();
1813
+ // every store rewrites the index backup, so a recorded time would age a file
1814
+ // that is in fact new; renaming carries the previous index mtime onto it
1815
+ const indexBackup = `index${extension}.old`;
1816
+ /** @type {UnreferencedFiles} */
1817
+ const stillUnreferenced = new Map();
1818
+ let deletedCount = 0;
1819
+ for (const file of files) {
1820
+ if (typeof file !== "string" || liveFiles.has(file)) continue;
1821
+ const path = `${cacheLocation}/${file}`;
1822
+ const stats = await new Promise((resolve) => {
1823
+ fs.stat(path, (err, stats) => {
1824
+ resolve(
1825
+ err
1826
+ ? undefined
1827
+ : /** @type {import("../util/fs").IStats} */ (stats)
1828
+ );
1829
+ });
1830
+ });
1831
+ if (stats === undefined || !stats.isFile()) continue;
1832
+ const size = /** @type {number} */ (stats.size);
1833
+ const seen = seenFiles.get(file);
1834
+ // a differing size means the name was rewritten, so it is a new orphan
1835
+ const firstSeen =
1836
+ seen !== undefined && seen.size === size ? seen.firstSeen : now;
1837
+ const expireTime = now - CLEANUP_GRACE_PERIOD;
1838
+ const mtimeMs = /** @type {number} */ (stats.mtimeMs);
1839
+ // either signal is enough: modification times are lost when a cache is
1840
+ // restored, and recorded times are lost when the cache directory is new
1841
+ const expired =
1842
+ mtimeMs <= expireTime ||
1843
+ (file !== indexBackup &&
1844
+ firstSeen <= expireTime &&
1845
+ mtimeMs <= now - CLEANUP_RECENT_WRITE_PERIOD);
1846
+ if (expired) {
1847
+ const deleted = await new Promise((resolve) => {
1848
+ /** @type {NonNullable<IntermediateFileSystem["unlink"]>} */
1849
+ (fs.unlink)(path, (err) => resolve(!err));
1850
+ });
1851
+ if (deleted) {
1852
+ deletedCount++;
1853
+ continue;
1854
+ }
1855
+ }
1856
+ if (file !== indexBackup) {
1857
+ stillUnreferenced.set(file, { firstSeen, size });
1858
+ }
1859
+ }
1860
+ await this._writeUnreferencedFiles(stillUnreferenced, seenFiles.size > 0);
1861
+ // drop entries of files that are no longer alive
1862
+ for (const name of referencedFilesCache.keys()) {
1863
+ if (!liveFiles.has(`${name}${extension}`)) {
1864
+ referencedFilesCache.delete(name);
1865
+ }
1866
+ }
1867
+ if (deletedCount > 0) {
1868
+ this.logger.log("Deleted %d unused cache files", deletedCount);
1869
+ }
1870
+ } catch (err) {
1871
+ this.logger.warn(
1872
+ `Cleanup of unused cache files failed: ${/** @type {Error} */ (err)}`
1873
+ );
1874
+ this.logger.debug(/** @type {Error} */ (err).stack);
1875
+ }
1876
+ this.logger.timeEnd("cleanup unused cache files");
1877
+ }
1878
+
1625
1879
  clear() {
1626
1880
  this.fileSystemInfo.clear();
1627
1881
  this.buildDependencies.clear();
@@ -511,7 +511,13 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
511
511
  /** @type {NonNullable<WebpackOptionsNormalized["loader"]>} */ (
512
512
  options.loader
513
513
  ),
514
- { targetProperties, environment: options.output.environment }
514
+ {
515
+ targetProperties,
516
+ environment: options.output.environment,
517
+ outputModule:
518
+ /** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
519
+ (options.output.module)
520
+ }
515
521
  );
516
522
 
517
523
  F(options, "externalsType", () => {
@@ -2251,9 +2257,13 @@ const applyExternalsPresetsDefaults = (
2251
2257
  * @param {object} options options
2252
2258
  * @param {TargetProperties | false} options.targetProperties target properties
2253
2259
  * @param {Environment} options.environment environment
2260
+ * @param {boolean} options.outputModule is output type is module
2254
2261
  * @returns {void}
2255
2262
  */
2256
- const applyLoaderDefaults = (loader, { targetProperties, environment }) => {
2263
+ const applyLoaderDefaults = (
2264
+ loader,
2265
+ { targetProperties, environment, outputModule }
2266
+ ) => {
2257
2267
  F(loader, "target", () => {
2258
2268
  if (targetProperties) {
2259
2269
  if (targetProperties.electron) {
@@ -2267,6 +2277,15 @@ const applyLoaderDefaults = (loader, { targetProperties, environment }) => {
2267
2277
  if (targetProperties.bun) return "bun";
2268
2278
  if (targetProperties.node) return "node";
2269
2279
  if (targetProperties.web) return "web";
2280
+ // no single platform to report: the bundle runs on both (target
2281
+ // `"universal"` / `["web", "node"]`), so loaders get `"universal"`
2282
+ if (
2283
+ outputModule &&
2284
+ targetProperties.node === null &&
2285
+ targetProperties.web === null
2286
+ ) {
2287
+ return "universal";
2288
+ }
2270
2289
  }
2271
2290
  });
2272
2291
  D(loader, "environment", environment);
@@ -38,12 +38,13 @@ class HoistContainerReferences {
38
38
  const depsToTrace = new Set();
39
39
  /** @type {Set<Dependency>} */
40
40
  const entryExternalsToHoist = new Set();
41
- hooks.addContainerEntryDependency.tap(PLUGIN_NAME, (dep) => {
41
+ // Both hooks feed the same trace set, so they share one callback.
42
+ /** @type {(dep: Dependency) => void} */
43
+ const traceDep = (dep) => {
42
44
  depsToTrace.add(dep);
43
- });
44
- hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, (dep) => {
45
- depsToTrace.add(dep);
46
- });
45
+ };
46
+ hooks.addContainerEntryDependency.tap(PLUGIN_NAME, traceDep);
47
+ hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, traceDep);
47
48
 
48
49
  compilation.hooks.addEntry.tap(PLUGIN_NAME, (entryDep) => {
49
50
  if (entryDep.type === "entry") {
@@ -76,46 +77,26 @@ class HoistContainerReferences {
76
77
  * @param {Set<Dependency>} entryExternalsToHoist Set of container entry dependencies to hoist.
77
78
  */
78
79
  hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) {
79
- const { chunkGraph, moduleGraph } = compilation;
80
+ const { moduleGraph } = compilation;
80
81
 
81
- // loop over entry points
82
+ // Entry externals: hoist the external modules (e.g. RemoteModule) they reference.
82
83
  for (const dep of entryExternalsToHoist) {
83
84
  const entryModule = moduleGraph.getModule(dep);
84
85
  if (!entryModule) continue;
85
- // get all the external module types and hoist them to the runtime chunk, this will get RemoteModule externals
86
86
  const allReferencedModules = getAllReferencedModules(
87
87
  compilation,
88
88
  entryModule,
89
89
  "external",
90
90
  false
91
91
  );
92
-
93
- const containerRuntimes = chunkGraph.getModuleRuntimes(entryModule);
94
- /** @type {Set<string>} */
95
- const runtimes = new Set();
96
-
97
- for (const runtimeSpec of containerRuntimes) {
98
- forEachRuntime(runtimeSpec, (runtimeKey) => {
99
- if (runtimeKey) {
100
- runtimes.add(runtimeKey);
101
- }
102
- });
103
- }
104
-
105
- for (const runtime of runtimes) {
106
- const runtimeChunk = compilation.namedChunks.get(runtime);
107
- if (!runtimeChunk) continue;
108
-
109
- for (const module of allReferencedModules) {
110
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
111
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
112
- }
113
- }
114
- }
115
- this.cleanUpChunks(compilation, allReferencedModules);
92
+ this.hoistReferencedModules(
93
+ compilation,
94
+ entryModule,
95
+ allReferencedModules
96
+ );
116
97
  }
117
98
 
118
- // handle container entry specifically
99
+ // Container entries: hoist the initial graph plus its external references.
119
100
  for (const dep of depsToTrace) {
120
101
  const containerEntryModule = moduleGraph.getModule(dep);
121
102
  if (!containerEntryModule) continue;
@@ -125,43 +106,54 @@ class HoistContainerReferences {
125
106
  "initial",
126
107
  false
127
108
  );
128
-
129
109
  const allRemoteReferences = getAllReferencedModules(
130
110
  compilation,
131
111
  containerEntryModule,
132
112
  "external",
133
113
  false
134
114
  );
135
-
136
115
  for (const remote of allRemoteReferences) {
137
116
  allReferencedModules.add(remote);
138
117
  }
118
+ this.hoistReferencedModules(
119
+ compilation,
120
+ containerEntryModule,
121
+ allReferencedModules
122
+ );
123
+ }
124
+ }
139
125
 
140
- const containerRuntimes =
141
- chunkGraph.getModuleRuntimes(containerEntryModule);
142
- /** @type {Set<string>} */
143
- const runtimes = new Set();
144
-
145
- for (const runtimeSpec of containerRuntimes) {
146
- forEachRuntime(runtimeSpec, (runtimeKey) => {
147
- if (runtimeKey) {
148
- runtimes.add(runtimeKey);
149
- }
150
- });
151
- }
126
+ /**
127
+ * Connect `referencedModules` into each runtime chunk of `entryModule`, then
128
+ * prune the chunks they were hoisted out of. The two passes above build
129
+ * `referencedModules` differently but hoist them identically.
130
+ * @param {Compilation} compilation The webpack compilation instance.
131
+ * @param {Module} entryModule The module whose runtimes receive the modules.
132
+ * @param {Set<Module>} referencedModules The modules to hoist.
133
+ */
134
+ hoistReferencedModules(compilation, entryModule, referencedModules) {
135
+ const { chunkGraph } = compilation;
136
+ /** @type {Set<string>} */
137
+ const runtimes = new Set();
138
+ for (const runtimeSpec of chunkGraph.getModuleRuntimes(entryModule)) {
139
+ forEachRuntime(runtimeSpec, (runtimeKey) => {
140
+ if (runtimeKey) {
141
+ runtimes.add(runtimeKey);
142
+ }
143
+ });
144
+ }
152
145
 
153
- for (const runtime of runtimes) {
154
- const runtimeChunk = compilation.namedChunks.get(runtime);
155
- if (!runtimeChunk) continue;
146
+ for (const runtime of runtimes) {
147
+ const runtimeChunk = compilation.namedChunks.get(runtime);
148
+ if (!runtimeChunk) continue;
156
149
 
157
- for (const module of allReferencedModules) {
158
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
159
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
160
- }
150
+ for (const module of referencedModules) {
151
+ if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
152
+ chunkGraph.connectChunkAndModule(runtimeChunk, module);
161
153
  }
162
154
  }
163
- this.cleanUpChunks(compilation, allReferencedModules);
164
155
  }
156
+ this.cleanUpChunks(compilation, referencedModules);
165
157
  }
166
158
 
167
159
  /**
@@ -18,11 +18,21 @@ const HoistContainerReferences = require("./HoistContainerReferencesPlugin");
18
18
  /** @typedef {import("../Compiler")} Compiler */
19
19
  /** @typedef {import("../Dependency")} Dependency */
20
20
 
21
+ const createCompilationHooks = () => ({
22
+ /**
23
+ * @type {SyncHook<Dependency>}
24
+ * @since 5.96.0
25
+ */
26
+ addContainerEntryDependency: new SyncHook(["dependency"]),
27
+ /**
28
+ * @type {SyncHook<Dependency>}
29
+ * @since 5.96.0
30
+ */
31
+ addFederationRuntimeDependency: new SyncHook(["dependency"])
32
+ });
33
+
21
34
  /**
22
- * Defines the compilation hooks type used by this module.
23
- * @typedef {object} CompilationHooks
24
- * @property {SyncHook<Dependency>} addContainerEntryDependency
25
- * @property {SyncHook<Dependency>} addFederationRuntimeDependency
35
+ * @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks
26
36
  */
27
37
 
28
38
  const PLUGIN_NAME = "ModuleFederationPlugin";
@@ -111,11 +121,7 @@ class ModuleFederationPlugin {
111
121
  }
112
122
 
113
123
  ModuleFederationPlugin.getCompilationHooks = createHooksRegistry(
114
- () =>
115
- /** @type {CompilationHooks} */ ({
116
- addContainerEntryDependency: new SyncHook(["dependency"]),
117
- addFederationRuntimeDependency: new SyncHook(["dependency"])
118
- })
124
+ createCompilationHooks
119
125
  );
120
126
 
121
127
  module.exports = ModuleFederationPlugin;
@@ -33,6 +33,7 @@ const CssImportDependency = require("../dependencies/CssImportDependency");
33
33
  const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
34
34
 
35
35
  const { encodeMappings } = require("../util/createMappings");
36
+ const { contextifySourceUrl } = require("../util/identifier");
36
37
  const memoize = require("../util/memoize");
37
38
  const {
38
39
  PUBLIC_PATH_FULL_HASH,
@@ -549,8 +550,13 @@ class CssGenerator extends Generator {
549
550
  }
550
551
 
551
552
  const generatedJs = /** @type {string} */ (source.source());
552
- const sourceName = module.readableIdentifier(
553
- compilation.requestShortener
553
+ // Context-relative identifier, like `NormalModule.createSource` — so
554
+ // `SourceMapDevToolPlugin` can match the module and apply the
555
+ // devtool filename template instead of leaking this raw name
556
+ const sourceName = contextifySourceUrl(
557
+ /** @type {string} */ (compilation.options.context),
558
+ module.identifier(),
559
+ compilation.compiler.root
554
560
  );
555
561
 
556
562
  if (
@@ -20,12 +20,31 @@ const { chunkHasCss } = require("./CssModulesPlugin");
20
20
  /** @typedef {import("../ChunkGraph")} ChunkGraph */
21
21
  /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
22
22
 
23
+ const createCompilationHooks = () => ({
24
+ /**
25
+ * @type {SyncWaterfallHook<[string, Chunk]>}
26
+ * @since 5.66.0
27
+ */
28
+ createStylesheet: new SyncWaterfallHook(["source", "chunk"]),
29
+ /**
30
+ * @type {SyncWaterfallHook<[string, Chunk]>}
31
+ * @since 5.91.0
32
+ */
33
+ linkPreload: new SyncWaterfallHook(["source", "chunk"]),
34
+ /**
35
+ * @type {SyncWaterfallHook<[string, Chunk]>}
36
+ * @since 5.91.0
37
+ */
38
+ linkPrefetch: new SyncWaterfallHook(["source", "chunk"]),
39
+ /**
40
+ * @type {SyncWaterfallHook<[string, Chunk]>}
41
+ * @since 5.107.0
42
+ */
43
+ linkInsert: new SyncWaterfallHook(["source", "chunk"])
44
+ });
45
+
23
46
  /**
24
- * @typedef {object} CssLoadingRuntimeModulePluginHooks
25
- * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
26
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
27
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
28
- * @property {SyncWaterfallHook<[string, Chunk]>} linkInsert
47
+ * @typedef {ReturnType<typeof createCompilationHooks>} CssLoadingRuntimeModulePluginHooks
29
48
  */
30
49
 
31
50
  class CssLoadingRuntimeModule extends RuntimeModule {
@@ -577,13 +596,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
577
596
  }
578
597
 
579
598
  CssLoadingRuntimeModule.getCompilationHooks = createHooksRegistry(
580
- () =>
581
- /** @type {CssLoadingRuntimeModulePluginHooks} */ ({
582
- createStylesheet: new SyncWaterfallHook(["source", "chunk"]),
583
- linkPreload: new SyncWaterfallHook(["source", "chunk"]),
584
- linkPrefetch: new SyncWaterfallHook(["source", "chunk"]),
585
- linkInsert: new SyncWaterfallHook(["source", "chunk"])
586
- })
599
+ createCompilationHooks
587
600
  );
588
601
 
589
602
  module.exports = CssLoadingRuntimeModule;