HoistContainerReferencesPlugin.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Zackary Jackson @ScriptedAlchemy
  4. */
  5. "use strict";
  6. const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
  7. const ExternalModule = require("../ExternalModule");
  8. const { STAGE_ADVANCED } = require("../OptimizationStages");
  9. const { forEachRuntime } = require("../util/runtime");
  10. const getModuleFederationCompilationHooks = require("./moduleFederationHooks");
  11. /** @import Compilation from "../Compilation" */
  12. /** @import Compiler from "../Compiler" */
  13. /** @import Dependency from "../Dependency" */
  14. /** @import Module from "../Module" */
  15. const PLUGIN_NAME = "HoistContainerReferences";
  16. /**
  17. * This class is used to hoist container references in the code.
  18. */
  19. class HoistContainerReferences {
  20. /**
  21. * Apply the plugin to the compiler.
  22. * @param {Compiler} compiler The webpack compiler instance.
  23. */
  24. apply(compiler) {
  25. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  26. const hooks = getModuleFederationCompilationHooks(compilation);
  27. /** @type {Set<Dependency>} */
  28. const depsToTrace = new Set();
  29. /** @type {Set<Dependency>} */
  30. const entryExternalsToHoist = new Set();
  31. // Both hooks feed the same trace set, so they share one callback.
  32. /** @type {(dep: Dependency) => void} */
  33. const traceDep = (dep) => {
  34. depsToTrace.add(dep);
  35. };
  36. hooks.addContainerEntryDependency.tap(PLUGIN_NAME, traceDep);
  37. hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, traceDep);
  38. compilation.hooks.addEntry.tap(PLUGIN_NAME, (entryDep) => {
  39. if (entryDep.type === "entry") {
  40. entryExternalsToHoist.add(entryDep);
  41. }
  42. });
  43. // Hook into the optimizeChunks phase
  44. compilation.hooks.optimizeChunks.tap(
  45. {
  46. name: PLUGIN_NAME,
  47. // advanced stage is where SplitChunksPlugin runs.
  48. stage: STAGE_ADVANCED + 1
  49. },
  50. (_chunks) => {
  51. this.hoistModulesInChunks(
  52. compilation,
  53. depsToTrace,
  54. entryExternalsToHoist
  55. );
  56. }
  57. );
  58. });
  59. }
  60. /**
  61. * Hoist modules in chunks.
  62. * @param {Compilation} compilation The webpack compilation instance.
  63. * @param {Set<Dependency>} depsToTrace Set of container entry dependencies.
  64. * @param {Set<Dependency>} entryExternalsToHoist Set of container entry dependencies to hoist.
  65. */
  66. hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) {
  67. const { moduleGraph } = compilation;
  68. // Entry externals: hoist the external modules (e.g. RemoteModule) they reference.
  69. for (const dep of entryExternalsToHoist) {
  70. const entryModule = moduleGraph.getModule(dep);
  71. if (!entryModule) continue;
  72. const allReferencedModules = getAllReferencedModules(
  73. compilation,
  74. entryModule,
  75. "external",
  76. false
  77. );
  78. this.hoistReferencedModules(
  79. compilation,
  80. entryModule,
  81. allReferencedModules
  82. );
  83. }
  84. // Container entries: hoist the initial graph plus its external references.
  85. for (const dep of depsToTrace) {
  86. const containerEntryModule = moduleGraph.getModule(dep);
  87. if (!containerEntryModule) continue;
  88. const allReferencedModules = getAllReferencedModules(
  89. compilation,
  90. containerEntryModule,
  91. "initial",
  92. false
  93. );
  94. const allRemoteReferences = getAllReferencedModules(
  95. compilation,
  96. containerEntryModule,
  97. "external",
  98. false
  99. );
  100. for (const remote of allRemoteReferences) {
  101. allReferencedModules.add(remote);
  102. }
  103. this.hoistReferencedModules(
  104. compilation,
  105. containerEntryModule,
  106. allReferencedModules
  107. );
  108. }
  109. }
  110. /**
  111. * Connect `referencedModules` into each runtime chunk of `entryModule`, then
  112. * prune the chunks they were hoisted out of. The two passes above build
  113. * `referencedModules` differently but hoist them identically.
  114. * @param {Compilation} compilation The webpack compilation instance.
  115. * @param {Module} entryModule The module whose runtimes receive the modules.
  116. * @param {Set<Module>} referencedModules The modules to hoist.
  117. */
  118. hoistReferencedModules(compilation, entryModule, referencedModules) {
  119. const { chunkGraph } = compilation;
  120. /** @type {Set<string>} */
  121. const runtimes = new Set();
  122. for (const runtimeSpec of chunkGraph.getModuleRuntimes(entryModule)) {
  123. forEachRuntime(runtimeSpec, (runtimeKey) => {
  124. if (runtimeKey) {
  125. runtimes.add(runtimeKey);
  126. }
  127. });
  128. }
  129. for (const runtime of runtimes) {
  130. const runtimeChunk = compilation.namedChunks.get(runtime);
  131. if (!runtimeChunk) continue;
  132. for (const module of referencedModules) {
  133. if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
  134. chunkGraph.connectChunkAndModule(runtimeChunk, module);
  135. }
  136. }
  137. }
  138. this.cleanUpChunks(compilation, referencedModules);
  139. }
  140. /**
  141. * Clean up chunks by disconnecting unused modules.
  142. * @param {Compilation} compilation The webpack compilation instance.
  143. * @param {Set<Module>} modules Set of modules to clean up.
  144. */
  145. cleanUpChunks(compilation, modules) {
  146. const { chunkGraph } = compilation;
  147. for (const module of modules) {
  148. for (const chunk of chunkGraph.getModuleChunks(module)) {
  149. if (!chunk.hasRuntime()) {
  150. chunkGraph.disconnectChunkAndModule(chunk, module);
  151. if (
  152. chunkGraph.getNumberOfChunkModules(chunk) === 0 &&
  153. chunkGraph.getNumberOfEntryModules(chunk) === 0
  154. ) {
  155. chunkGraph.disconnectChunk(chunk);
  156. compilation.chunks.delete(chunk);
  157. if (chunk.name) {
  158. compilation.namedChunks.delete(chunk.name);
  159. }
  160. }
  161. }
  162. }
  163. }
  164. modules.clear();
  165. }
  166. }
  167. /**
  168. * Helper method to collect all referenced modules recursively.
  169. * @param {Compilation} compilation The webpack compilation instance.
  170. * @param {Module} module The module to start collecting from.
  171. * @param {string} type The type of modules to collect ("initial", "external", or "all").
  172. * @param {boolean} includeInitial Should include the referenced module passed
  173. * @returns {Set<Module>} Set of collected modules.
  174. */
  175. function getAllReferencedModules(compilation, module, type, includeInitial) {
  176. const collectedModules = new Set(includeInitial ? [module] : []);
  177. /** @type {WeakSet<Module>} */
  178. const visitedModules = new WeakSet([module]);
  179. /** @type {Module[]} */
  180. const stack = [module];
  181. while (stack.length > 0) {
  182. const currentModule = stack.pop();
  183. if (!currentModule) continue;
  184. const outgoingConnections =
  185. compilation.moduleGraph.getOutgoingConnections(currentModule);
  186. if (outgoingConnections) {
  187. for (const connection of outgoingConnections) {
  188. const connectedModule = connection.module;
  189. // Skip if module has already been visited
  190. if (!connectedModule || visitedModules.has(connectedModule)) {
  191. continue;
  192. }
  193. // Handle 'initial' type (skipping async blocks)
  194. if (type === "initial") {
  195. const parentBlock = compilation.moduleGraph.getParentBlock(
  196. /** @type {Dependency} */
  197. (connection.dependency)
  198. );
  199. if (parentBlock instanceof AsyncDependenciesBlock) {
  200. continue;
  201. }
  202. }
  203. // Handle 'external' type (collecting only external modules)
  204. if (type === "external") {
  205. if (connection.module instanceof ExternalModule) {
  206. collectedModules.add(connectedModule);
  207. }
  208. } else {
  209. // Handle 'all' or unspecified types
  210. collectedModules.add(connectedModule);
  211. }
  212. // Add connected module to the stack and mark it as visited
  213. visitedModules.add(connectedModule);
  214. stack.push(connectedModule);
  215. }
  216. }
  217. }
  218. return collectedModules;
  219. }
  220. module.exports = HoistContainerReferences;