HoistContainerReferencesPlugin.js 7.4 KB

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