Skip to content

Commit b42ddf1

Browse files
feat: enhance HoistContainerReferencesPlugin for better module hoisting
- Separate handling for container, federation, and remote dependencies - Improved support for runtimeChunk: 'single' configuration - Proper remote module hoisting using the new addRemoteDependency hook - Simplified cleanup logic for better performance - Changed runtime chunk detection to include all chunks with runtime - Added comprehensive unit tests
1 parent 5836303 commit b42ddf1

File tree

3 files changed

+672
-42
lines changed

3 files changed

+672
-42
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@module-federation/enhanced": patch
3+
---
4+
5+
feat: enhance HoistContainerReferencesPlugin for better module hoisting
6+
7+
- Separate handling for container, federation, and remote dependencies
8+
- Improved support for `runtimeChunk: 'single'` configuration
9+
- Proper remote module hoisting using the new `addRemoteDependency` hook
10+
- Simplified cleanup logic for better performance
11+
- Changed runtime chunk detection to include all chunks with runtime (not just entry chunks)
12+
- Added comprehensive unit tests for the plugin functionality

packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {
2+
Chunk,
23
Compiler,
34
Compilation,
4-
Chunk,
55
WebpackPluginInstance,
66
Module,
77
Dependency,
@@ -10,6 +10,8 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-p
1010
import FederationModulesPlugin from './runtime/FederationModulesPlugin';
1111
import ContainerEntryDependency from './ContainerEntryDependency';
1212
import FederationRuntimeDependency from './runtime/FederationRuntimeDependency';
13+
import RemoteToExternalDependency from './RemoteToExternalDependency';
14+
import FallbackDependency from './FallbackDependency';
1315

1416
const { AsyncDependenciesBlock, ExternalModule } = require(
1517
normalizeWebpackPath('webpack'),
@@ -18,16 +20,19 @@ const { AsyncDependenciesBlock, ExternalModule } = require(
1820
const PLUGIN_NAME = 'HoistContainerReferences';
1921

2022
/**
21-
* This class is used to hoist container references in the code.
23+
* This plugin hoists container-related modules into runtime chunks when using runtimeChunk: single configuration.
2224
*/
23-
export class HoistContainerReferences implements WebpackPluginInstance {
25+
class HoistContainerReferences implements WebpackPluginInstance {
2426
apply(compiler: Compiler): void {
2527
compiler.hooks.thisCompilation.tap(
2628
PLUGIN_NAME,
2729
(compilation: Compilation) => {
2830
const logger = compilation.getLogger(PLUGIN_NAME);
2931
const hooks = FederationModulesPlugin.getCompilationHooks(compilation);
3032
const containerEntryDependencies = new Set<Dependency>();
33+
const federationRuntimeDependencies = new Set<Dependency>();
34+
const remoteDependencies = new Set<Dependency>();
35+
3136
hooks.addContainerEntryDependency.tap(
3237
'HoistContainerReferences',
3338
(dep: ContainerEntryDependency) => {
@@ -37,7 +42,13 @@ export class HoistContainerReferences implements WebpackPluginInstance {
3742
hooks.addFederationRuntimeDependency.tap(
3843
'HoistContainerReferences',
3944
(dep: FederationRuntimeDependency) => {
40-
containerEntryDependencies.add(dep);
45+
federationRuntimeDependencies.add(dep);
46+
},
47+
);
48+
hooks.addRemoteDependency.tap(
49+
'HoistContainerReferences',
50+
(dep: RemoteToExternalDependency | FallbackDependency) => {
51+
remoteDependencies.add(dep);
4152
},
4253
);
4354

@@ -53,9 +64,10 @@ export class HoistContainerReferences implements WebpackPluginInstance {
5364
this.hoistModulesInChunks(
5465
compilation,
5566
runtimeChunks,
56-
chunks,
5767
logger,
5868
containerEntryDependencies,
69+
federationRuntimeDependencies,
70+
remoteDependencies,
5971
);
6072
},
6173
);
@@ -67,37 +79,60 @@ export class HoistContainerReferences implements WebpackPluginInstance {
6779
private hoistModulesInChunks(
6880
compilation: Compilation,
6981
runtimeChunks: Set<Chunk>,
70-
chunks: Iterable<Chunk>,
7182
logger: ReturnType<Compilation['getLogger']>,
7283
containerEntryDependencies: Set<Dependency>,
84+
federationRuntimeDependencies: Set<Dependency>,
85+
remoteDependencies: Set<Dependency>,
7386
): void {
7487
const { chunkGraph, moduleGraph } = compilation;
75-
// when runtimeChunk: single is set - ContainerPlugin will create a "partial" chunk we can use to
76-
// move modules into the runtime chunk
88+
const allModulesToHoist = new Set<Module>();
89+
90+
// Process container entry dependencies (needed for nextjs-mf exposed modules)
7791
for (const dep of containerEntryDependencies) {
7892
const containerEntryModule = moduleGraph.getModule(dep);
7993
if (!containerEntryModule) continue;
80-
const allReferencedModules = getAllReferencedModules(
94+
const referencedModules = getAllReferencedModules(
8195
compilation,
8296
containerEntryModule,
8397
'initial',
8498
);
99+
referencedModules.forEach((m: Module) => allModulesToHoist.add(m));
100+
const moduleRuntimes = chunkGraph.getModuleRuntimes(containerEntryModule);
101+
const runtimes = new Set<string>();
102+
for (const runtimeSpec of moduleRuntimes) {
103+
compilation.compiler.webpack.util.runtime.forEachRuntime(
104+
runtimeSpec,
105+
(runtimeKey) => {
106+
if (runtimeKey) {
107+
runtimes.add(runtimeKey);
108+
}
109+
},
110+
);
111+
}
112+
for (const runtime of runtimes) {
113+
const runtimeChunk = compilation.namedChunks.get(runtime);
114+
if (!runtimeChunk) continue;
115+
for (const module of referencedModules) {
116+
if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
117+
chunkGraph.connectChunkAndModule(runtimeChunk, module);
118+
}
119+
}
120+
}
121+
}
85122

86-
const allRemoteReferences = getAllReferencedModules(
123+
// Federation Runtime Dependencies: use 'initial' (not 'all')
124+
for (const dep of federationRuntimeDependencies) {
125+
const runtimeModule = moduleGraph.getModule(dep);
126+
if (!runtimeModule) continue;
127+
const referencedModules = getAllReferencedModules(
87128
compilation,
88-
containerEntryModule,
89-
'external',
129+
runtimeModule,
130+
'initial',
90131
);
91-
92-
for (const remote of allRemoteReferences) {
93-
allReferencedModules.add(remote);
94-
}
95-
96-
const containerRuntimes =
97-
chunkGraph.getModuleRuntimes(containerEntryModule);
132+
referencedModules.forEach((m: Module) => allModulesToHoist.add(m));
133+
const moduleRuntimes = chunkGraph.getModuleRuntimes(runtimeModule);
98134
const runtimes = new Set<string>();
99-
100-
for (const runtimeSpec of containerRuntimes) {
135+
for (const runtimeSpec of moduleRuntimes) {
101136
compilation.compiler.webpack.util.runtime.forEachRuntime(
102137
runtimeSpec,
103138
(runtimeKey) => {
@@ -107,19 +142,49 @@ export class HoistContainerReferences implements WebpackPluginInstance {
107142
},
108143
);
109144
}
110-
111145
for (const runtime of runtimes) {
112146
const runtimeChunk = compilation.namedChunks.get(runtime);
113147
if (!runtimeChunk) continue;
148+
for (const module of referencedModules) {
149+
if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
150+
chunkGraph.connectChunkAndModule(runtimeChunk, module);
151+
}
152+
}
153+
}
154+
}
114155

115-
for (const module of allReferencedModules) {
156+
// Process remote dependencies
157+
for (const remoteDep of remoteDependencies) {
158+
const remoteModule = moduleGraph.getModule(remoteDep);
159+
if (!remoteModule) continue;
160+
const referencedRemoteModules = getAllReferencedModules(
161+
compilation,
162+
remoteModule,
163+
'initial',
164+
);
165+
referencedRemoteModules.forEach((m: Module) => allModulesToHoist.add(m));
166+
const remoteModuleRuntimes = chunkGraph.getModuleRuntimes(remoteModule);
167+
const remoteRuntimes = new Set<string>();
168+
for (const runtimeSpec of remoteModuleRuntimes) {
169+
compilation.compiler.webpack.util.runtime.forEachRuntime(
170+
runtimeSpec,
171+
(runtimeKey) => {
172+
if (runtimeKey) remoteRuntimes.add(runtimeKey);
173+
},
174+
);
175+
}
176+
for (const runtime of remoteRuntimes) {
177+
const runtimeChunk = compilation.namedChunks.get(runtime);
178+
if (!runtimeChunk) continue;
179+
for (const module of referencedRemoteModules) {
116180
if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
117181
chunkGraph.connectChunkAndModule(runtimeChunk, module);
118182
}
119183
}
120184
}
121-
this.cleanUpChunks(compilation, allReferencedModules);
122185
}
186+
187+
this.cleanUpChunks(compilation, allModulesToHoist);
123188
}
124189

125190
// Method to clean up chunks by disconnecting unused modules
@@ -129,31 +194,17 @@ export class HoistContainerReferences implements WebpackPluginInstance {
129194
for (const chunk of chunkGraph.getModuleChunks(module)) {
130195
if (!chunk.hasRuntime()) {
131196
chunkGraph.disconnectChunkAndModule(chunk, module);
132-
if (
133-
chunkGraph.getNumberOfChunkModules(chunk) === 0 &&
134-
chunkGraph.getNumberOfEntryModules(chunk) === 0
135-
) {
136-
chunkGraph.disconnectChunk(chunk);
137-
compilation.chunks.delete(chunk);
138-
if (chunk.name) {
139-
compilation.namedChunks.delete(chunk.name);
140-
}
141-
}
142197
}
143198
}
144199
}
145-
modules.clear();
146200
}
147201

148-
// Helper method to get runtime chunks from the compilation
202+
// Method to get runtime chunks
149203
private getRuntimeChunks(compilation: Compilation): Set<Chunk> {
150204
const runtimeChunks = new Set<Chunk>();
151-
const entries = compilation.entrypoints;
152-
153-
for (const entrypoint of entries.values()) {
154-
const runtimeChunk = entrypoint.getRuntimeChunk();
155-
if (runtimeChunk) {
156-
runtimeChunks.add(runtimeChunk);
205+
for (const chunk of compilation.chunks) {
206+
if (chunk.hasRuntime()) {
207+
runtimeChunks.add(chunk);
157208
}
158209
}
159210
return runtimeChunks;

0 commit comments

Comments
 (0)