-
-
Notifications
You must be signed in to change notification settings - Fork 393
Expand file tree
/
Copy pathEmbedFederationRuntimePlugin.ts
More file actions
164 lines (142 loc) · 5.74 KB
/
EmbedFederationRuntimePlugin.ts
File metadata and controls
164 lines (142 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path';
import EmbedFederationRuntimeModule from './EmbedFederationRuntimeModule';
import FederationModulesPlugin from './FederationModulesPlugin';
import type { Compiler, Chunk, Compilation } from 'webpack';
import { getFederationGlobalScope } from './utils';
import ContainerEntryDependency from '../ContainerEntryDependency';
import FederationRuntimeDependency from './FederationRuntimeDependency';
const { RuntimeGlobals } = require(
normalizeWebpackPath('webpack'),
) as typeof import('webpack');
const PLUGIN_NAME = 'EmbedFederationRuntimePlugin';
const federationGlobal = getFederationGlobalScope(RuntimeGlobals);
interface EmbedFederationRuntimePluginOptions {
/**
* Whether to enable runtime module embedding for all chunks.
* If false, only chunks that explicitly require it will be embedded.
*/
enableForAllChunks?: boolean;
}
/**
* Plugin that embeds Module Federation runtime code into chunks.
* It ensures proper initialization of federated modules and manages runtime requirements.
*/
class EmbedFederationRuntimePlugin {
private readonly options: EmbedFederationRuntimePluginOptions;
private readonly processedChunks = new WeakMap<Chunk, boolean>();
constructor(options: EmbedFederationRuntimePluginOptions = {}) {
this.options = {
enableForAllChunks: false,
...options,
};
}
/**
* Determines if runtime embedding should be enabled for a given chunk.
*/
private isEnabledForChunk(chunk: Chunk): boolean {
// Disable for our special "build time chunk"
if (chunk.id === 'build time chunk') return false;
return this.options.enableForAllChunks || chunk.hasRuntime();
}
/**
* Checks if a hook has already been tapped by this plugin.
*/
private isHookAlreadyTapped(
taps: Array<{ name: string }>,
hookName: string,
): boolean {
return taps.some((tap) => tap.name === hookName);
}
apply(compiler: Compiler): void {
// Prevent double application of the plugin.
const compilationTaps = compiler.hooks.thisCompilation.taps || [];
if (this.isHookAlreadyTapped(compilationTaps, PLUGIN_NAME)) {
return;
}
// Tap into the compilation to modify renderStartup and runtime requirements.
compiler.hooks.thisCompilation.tap(
PLUGIN_NAME,
(compilation: Compilation) => {
// --- Part 1: Modify renderStartup to append a startup call when none is added automatically ---
const { renderStartup } =
compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(
compilation,
);
renderStartup.tap(
PLUGIN_NAME,
(startupSource, _lastInlinedModule, renderContext) => {
const { chunk, chunkGraph } = renderContext;
if (!this.isEnabledForChunk(chunk)) {
return startupSource;
}
const runtimeRequirements =
chunkGraph.getTreeRuntimeRequirements(chunk);
const entryModuleCount = chunkGraph.getNumberOfEntryModules(chunk);
// The default renderBootstrap automatically pushes a startup call when either:
// - There is at least one entry module, OR
// - runtimeRequirements.has(RuntimeGlobals.startupNoDefault) is true.
if (
entryModuleCount > 0 ||
runtimeRequirements.has(RuntimeGlobals.startupNoDefault)
) {
return startupSource;
}
// Otherwise, append a startup call.
return new compiler.webpack.sources.ConcatSource(
startupSource,
'\n// Custom hook: appended startup call because none was added automatically\n',
`${RuntimeGlobals.startup}();\n`,
);
},
);
// --- Part 2: Embed Federation Runtime Module and adjust runtime requirements ---
const federationHooks =
FederationModulesPlugin.getCompilationHooks(compilation);
const containerEntrySet: Set<
ContainerEntryDependency | FederationRuntimeDependency
> = new Set();
// Proactively add startupOnlyBefore target chunks.
compilation.hooks.additionalChunkRuntimeRequirements.tap(
PLUGIN_NAME,
(chunk: Chunk, runtimeRequirements: Set<string>) => {
if (!this.isEnabledForChunk(chunk)) {
return;
}
runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore);
},
);
// Collect federation runtime dependencies.
federationHooks.addFederationRuntimeDependency.tap(
PLUGIN_NAME,
(dependency: FederationRuntimeDependency) => {
containerEntrySet.add(dependency);
},
);
// Handle additional runtime requirements when federation is enabled.
const handleRuntimeRequirements = (
chunk: Chunk,
runtimeRequirements: Set<string>,
) => {
if (!this.isEnabledForChunk(chunk)) {
return;
}
// Skip if already processed or if not a federation chunk.
if (runtimeRequirements.has('embeddedFederationRuntime')) return;
if (!runtimeRequirements.has(federationGlobal)) {
return;
}
// Mark as embedded and add the runtime module.
runtimeRequirements.add('embeddedFederationRuntime');
const runtimeModule = new EmbedFederationRuntimeModule(
containerEntrySet,
);
compilation.addRuntimeModule(chunk, runtimeModule);
};
compilation.hooks.runtimeRequirementInTree
.for(federationGlobal)
.tap(PLUGIN_NAME, handleRuntimeRequirements);
},
);
}
}
export default EmbedFederationRuntimePlugin;