123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Zackary Jackson @ScriptedAlchemy
- */
- "use strict";
- const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
- const ExternalModule = require("../ExternalModule");
- const { STAGE_ADVANCED } = require("../OptimizationStages");
- const memoize = require("../util/memoize");
- const { forEachRuntime } = require("../util/runtime");
- /** @typedef {import("../Compilation")} Compilation */
- /** @typedef {import("../Compiler")} Compiler */
- /** @typedef {import("../Dependency")} Dependency */
- /** @typedef {import("../Module")} Module */
- const getModuleFederationPlugin = memoize(() =>
- require("./ModuleFederationPlugin")
- );
- const PLUGIN_NAME = "HoistContainerReferences";
- /**
- * This class is used to hoist container references in the code.
- */
- class HoistContainerReferences {
- /**
- * Apply the plugin to the compiler.
- * @param {Compiler} compiler The webpack compiler instance.
- */
- apply(compiler) {
- compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => {
- const hooks =
- getModuleFederationPlugin().getCompilationHooks(compilation);
- const depsToTrace = new Set();
- const entryExternalsToHoist = new Set();
- hooks.addContainerEntryDependency.tap(PLUGIN_NAME, dep => {
- depsToTrace.add(dep);
- });
- hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, dep => {
- depsToTrace.add(dep);
- });
- compilation.hooks.addEntry.tap(PLUGIN_NAME, entryDep => {
- if (entryDep.type === "entry") {
- entryExternalsToHoist.add(entryDep);
- }
- });
- // Hook into the optimizeChunks phase
- compilation.hooks.optimizeChunks.tap(
- {
- name: PLUGIN_NAME,
- // advanced stage is where SplitChunksPlugin runs.
- stage: STAGE_ADVANCED + 1
- },
- chunks => {
- this.hoistModulesInChunks(
- compilation,
- depsToTrace,
- entryExternalsToHoist
- );
- }
- );
- });
- }
- /**
- * Hoist modules in chunks.
- * @param {Compilation} compilation The webpack compilation instance.
- * @param {Set<Dependency>} depsToTrace Set of container entry dependencies.
- * @param {Set<Dependency>} entryExternalsToHoist Set of container entry dependencies to hoist.
- */
- hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) {
- const { chunkGraph, moduleGraph } = compilation;
- // loop over entry points
- for (const dep of entryExternalsToHoist) {
- const entryModule = moduleGraph.getModule(dep);
- if (!entryModule) continue;
- // get all the external module types and hoist them to the runtime chunk, this will get RemoteModule externals
- const allReferencedModules = getAllReferencedModules(
- compilation,
- entryModule,
- "external",
- false
- );
- const containerRuntimes = chunkGraph.getModuleRuntimes(entryModule);
- const runtimes = new Set();
- for (const runtimeSpec of containerRuntimes) {
- forEachRuntime(runtimeSpec, runtimeKey => {
- if (runtimeKey) {
- runtimes.add(runtimeKey);
- }
- });
- }
- for (const runtime of runtimes) {
- const runtimeChunk = compilation.namedChunks.get(runtime);
- if (!runtimeChunk) continue;
- for (const module of allReferencedModules) {
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
- }
- }
- }
- this.cleanUpChunks(compilation, allReferencedModules);
- }
- // handle container entry specifically
- for (const dep of depsToTrace) {
- const containerEntryModule = moduleGraph.getModule(dep);
- if (!containerEntryModule) continue;
- const allReferencedModules = getAllReferencedModules(
- compilation,
- containerEntryModule,
- "initial",
- false
- );
- const allRemoteReferences = getAllReferencedModules(
- compilation,
- containerEntryModule,
- "external",
- false
- );
- for (const remote of allRemoteReferences) {
- allReferencedModules.add(remote);
- }
- const containerRuntimes =
- chunkGraph.getModuleRuntimes(containerEntryModule);
- const runtimes = new Set();
- for (const runtimeSpec of containerRuntimes) {
- forEachRuntime(runtimeSpec, runtimeKey => {
- if (runtimeKey) {
- runtimes.add(runtimeKey);
- }
- });
- }
- for (const runtime of runtimes) {
- const runtimeChunk = compilation.namedChunks.get(runtime);
- if (!runtimeChunk) continue;
- for (const module of allReferencedModules) {
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
- }
- }
- }
- this.cleanUpChunks(compilation, allReferencedModules);
- }
- }
- /**
- * Clean up chunks by disconnecting unused modules.
- * @param {Compilation} compilation The webpack compilation instance.
- * @param {Set<Module>} modules Set of modules to clean up.
- */
- cleanUpChunks(compilation, modules) {
- const { chunkGraph } = compilation;
- for (const module of modules) {
- for (const chunk of chunkGraph.getModuleChunks(module)) {
- if (!chunk.hasRuntime()) {
- chunkGraph.disconnectChunkAndModule(chunk, module);
- if (
- chunkGraph.getNumberOfChunkModules(chunk) === 0 &&
- chunkGraph.getNumberOfEntryModules(chunk) === 0
- ) {
- chunkGraph.disconnectChunk(chunk);
- compilation.chunks.delete(chunk);
- if (chunk.name) {
- compilation.namedChunks.delete(chunk.name);
- }
- }
- }
- }
- }
- modules.clear();
- }
- }
- /**
- * Helper method to collect all referenced modules recursively.
- * @param {Compilation} compilation The webpack compilation instance.
- * @param {Module} module The module to start collecting from.
- * @param {string} type The type of modules to collect ("initial", "external", or "all").
- * @param {boolean} includeInitial Should include the referenced module passed
- * @returns {Set<Module>} Set of collected modules.
- */
- function getAllReferencedModules(compilation, module, type, includeInitial) {
- const collectedModules = new Set(includeInitial ? [module] : []);
- const visitedModules = new WeakSet([module]);
- const stack = [module];
- while (stack.length > 0) {
- const currentModule = stack.pop();
- if (!currentModule) continue;
- const outgoingConnections =
- compilation.moduleGraph.getOutgoingConnections(currentModule);
- if (outgoingConnections) {
- for (const connection of outgoingConnections) {
- const connectedModule = connection.module;
- // Skip if module has already been visited
- if (!connectedModule || visitedModules.has(connectedModule)) {
- continue;
- }
- // Handle 'initial' type (skipping async blocks)
- if (type === "initial") {
- const parentBlock = compilation.moduleGraph.getParentBlock(
- /** @type {Dependency} */
- (connection.dependency)
- );
- if (parentBlock instanceof AsyncDependenciesBlock) {
- continue;
- }
- }
- // Handle 'external' type (collecting only external modules)
- if (type === "external") {
- if (connection.module instanceof ExternalModule) {
- collectedModules.add(connectedModule);
- }
- } else {
- // Handle 'all' or unspecified types
- collectedModules.add(connectedModule);
- }
- // Add connected module to the stack and mark it as visited
- visitedModules.add(connectedModule);
- stack.push(connectedModule);
- }
- }
- }
- return collectedModules;
- }
- module.exports = HoistContainerReferences;
|