@module-federation/vite 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/lib/index.cjs +2113 -2109
- package/lib/index.d.cts +131 -0
- package/lib/index.d.mts +131 -0
- package/lib/index.mjs +2239 -0
- package/package.json +40 -28
- package/lib/index.d.ts +0 -4
- package/lib/index.esm.js +0 -2236
- package/lib/index.modern.js +0 -2288
- package/lib/index.umd.js +0 -2257
- package/lib/plugins/__tests__/pluginCheckAliasConflicts.test.d.ts +0 -1
- package/lib/plugins/__tests__/pluginDts.test.d.ts +0 -1
- package/lib/plugins/pluginAddEntry.d.ts +0 -10
- package/lib/plugins/pluginCheckAliasConflicts.d.ts +0 -9
- package/lib/plugins/pluginDevProxyModuleTopLevelAwait.d.ts +0 -2
- package/lib/plugins/pluginDts.d.ts +0 -3
- package/lib/plugins/pluginMFManifest.d.ts +0 -3
- package/lib/plugins/pluginModuleParseEnd.d.ts +0 -10
- package/lib/plugins/pluginProxyRemoteEntry.d.ts +0 -2
- package/lib/plugins/pluginProxyRemotes.d.ts +0 -3
- package/lib/plugins/pluginProxySharedModule_preBuild.d.ts +0 -7
- package/lib/plugins/pluginVarRemoteEntry.d.ts +0 -3
- package/lib/utils/PromiseStore.d.ts +0 -16
- package/lib/utils/VirtualModule.d.ts +0 -26
- package/lib/utils/__tests__/VirtualModule.test.d.ts +0 -1
- package/lib/utils/__tests__/cssModuleHelpers.test.d.ts +0 -1
- package/lib/utils/__tests__/helpers.d.ts +0 -2
- package/lib/utils/__tests__/normalizeModuleFederationOption.test.d.ts +0 -1
- package/lib/utils/__tests__/publicPath.test.d.ts +0 -1
- package/lib/utils/__tests__/serializeRuntimeOptions.test.d.ts +0 -1
- package/lib/utils/aliasToArrayPlugin.d.ts +0 -10
- package/lib/utils/bundleHelpers.d.ts +0 -2
- package/lib/utils/cssModuleHelpers.d.ts +0 -75
- package/lib/utils/localSharedImportMap_temp.d.ts +0 -2
- package/lib/utils/mapCodeToCodeWithSourcemap.d.ts +0 -4
- package/lib/utils/normalizeModuleFederationOptions.d.ts +0 -164
- package/lib/utils/normalizeOptimizeDeps.d.ts +0 -9
- package/lib/utils/packageNameUtils.d.ts +0 -22
- package/lib/utils/publicPath.d.ts +0 -9
- package/lib/utils/serializeRuntimeOptions.d.ts +0 -10
- package/lib/utils/wrapManualChunks.d.ts +0 -1
- package/lib/virtualModules/index.d.ts +0 -6
- package/lib/virtualModules/virtualExposes.d.ts +0 -2
- package/lib/virtualModules/virtualRemoteEntry.d.ts +0 -16
- package/lib/virtualModules/virtualRemotes.d.ts +0 -6
- package/lib/virtualModules/virtualRuntimeInitStatus.d.ts +0 -3
- package/lib/virtualModules/virtualShared_preBuild.d.ts +0 -17
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,2239 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import defu from "defu";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFile, writeFileSync } from "fs";
|
|
5
|
+
import * as path$1 from "pathe";
|
|
6
|
+
import path, { basename, dirname, join, parse, resolve } from "pathe";
|
|
7
|
+
import MagicString from "magic-string";
|
|
8
|
+
import { createFilter } from "@rollup/pluginutils";
|
|
9
|
+
import { walk } from "estree-walker";
|
|
10
|
+
import { normalizeOptions } from "@module-federation/sdk";
|
|
11
|
+
import { consumeTypesAPI, generateTypesAPI, isTSProject, normalizeConsumeTypesOptions, normalizeDtsOptions, normalizeGenerateTypesOptions } from "@module-federation/dts-plugin";
|
|
12
|
+
import { rpc } from "@module-federation/dts-plugin/core";
|
|
13
|
+
import { createRequire as createRequire$1 } from "module";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
|
|
16
|
+
//#region \0rolldown/runtime.js
|
|
17
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/utils/mapCodeToCodeWithSourcemap.ts
|
|
21
|
+
async function mapCodeToCodeWithSourcemap(code) {
|
|
22
|
+
const resolvedCode = await code;
|
|
23
|
+
if (resolvedCode === void 0) return;
|
|
24
|
+
const s = new MagicString(resolvedCode);
|
|
25
|
+
return {
|
|
26
|
+
code: s.toString(),
|
|
27
|
+
map: s.generateMap({ hires: true })
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/plugins/pluginAddEntry.ts
|
|
33
|
+
function getFirstHtmlEntryFile(entryFiles) {
|
|
34
|
+
return entryFiles.find((file) => file.endsWith(".html"));
|
|
35
|
+
}
|
|
36
|
+
const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
|
|
37
|
+
let devEntryPath = entryPath.startsWith("virtual:mf") ? "@id/" + entryPath : entryPath;
|
|
38
|
+
let entryFiles = [];
|
|
39
|
+
let htmlFilePath;
|
|
40
|
+
let _command;
|
|
41
|
+
let emitFileId;
|
|
42
|
+
let viteConfig;
|
|
43
|
+
function injectHtml() {
|
|
44
|
+
return inject === "html" && htmlFilePath;
|
|
45
|
+
}
|
|
46
|
+
function injectEntry() {
|
|
47
|
+
return inject === "entry" || !htmlFilePath;
|
|
48
|
+
}
|
|
49
|
+
return [{
|
|
50
|
+
name: "add-entry",
|
|
51
|
+
apply: "serve",
|
|
52
|
+
config(config, { command }) {
|
|
53
|
+
_command = command;
|
|
54
|
+
},
|
|
55
|
+
configResolved(config) {
|
|
56
|
+
viteConfig = config;
|
|
57
|
+
devEntryPath = config.base + devEntryPath.replace(/\\\\?/g, "/").replace(/.+?\:([/\\])[/\\]?/, "$1").replace(/^\//, "");
|
|
58
|
+
},
|
|
59
|
+
configureServer(server) {
|
|
60
|
+
server.middlewares.use((req, res, next) => {
|
|
61
|
+
if (!fileName) {
|
|
62
|
+
next();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (req.url && req.url.startsWith((viteConfig.base + fileName).replace(/^\/?/, "/"))) req.url = devEntryPath;
|
|
66
|
+
next();
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
transformIndexHtml(c) {
|
|
70
|
+
if (!injectHtml()) return;
|
|
71
|
+
return c.replace("<head>", `<head><script type="module" src=${JSON.stringify(devEntryPath.replace(/.+?\:([/\\])[/\\]?/, "$1").replace(/\\\\?/g, "/"))}><\/script>`);
|
|
72
|
+
},
|
|
73
|
+
transform(code, id) {
|
|
74
|
+
if (id.includes("node_modules") || inject !== "html" || htmlFilePath) return;
|
|
75
|
+
if (id.includes(".svelte-kit") && id.includes("internal.js")) {
|
|
76
|
+
const src = devEntryPath.replace(/.+?\:([/\\])[/\\]?/, "$1").replace(/\\\\?/g, "/");
|
|
77
|
+
return code.replace(/<head>/g, "<head><script type=\\\"module\\\" src=\\\"" + src + "\\\"><\/script>");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, {
|
|
81
|
+
name: "add-entry",
|
|
82
|
+
enforce: "post",
|
|
83
|
+
configResolved(config) {
|
|
84
|
+
viteConfig = config;
|
|
85
|
+
const inputOptions = config.build.rollupOptions.input;
|
|
86
|
+
if (!inputOptions) htmlFilePath = path$1.resolve(config.root, "index.html");
|
|
87
|
+
else if (typeof inputOptions === "string") entryFiles = [inputOptions];
|
|
88
|
+
else if (Array.isArray(inputOptions)) entryFiles = inputOptions;
|
|
89
|
+
else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions);
|
|
90
|
+
if (entryFiles && entryFiles.length > 0) htmlFilePath = getFirstHtmlEntryFile(entryFiles);
|
|
91
|
+
},
|
|
92
|
+
buildStart() {
|
|
93
|
+
if (_command === "serve") return;
|
|
94
|
+
const hasHash = fileName?.includes?.("[hash");
|
|
95
|
+
const emitFileOptions = {
|
|
96
|
+
name: entryName,
|
|
97
|
+
type: "chunk",
|
|
98
|
+
id: entryPath,
|
|
99
|
+
preserveSignature: "strict"
|
|
100
|
+
};
|
|
101
|
+
if (!hasHash) emitFileOptions.fileName = fileName;
|
|
102
|
+
emitFileId = this.emitFile(emitFileOptions);
|
|
103
|
+
if (htmlFilePath && fs.existsSync(htmlFilePath)) {
|
|
104
|
+
const htmlContent = fs.readFileSync(htmlFilePath, "utf-8");
|
|
105
|
+
const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/gi;
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = scriptRegex.exec(htmlContent)) !== null) entryFiles.push(match[1]);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
generateBundle(options, bundle) {
|
|
111
|
+
if (!injectHtml()) return;
|
|
112
|
+
const file = this.getFileName(emitFileId);
|
|
113
|
+
const resolvePath = (htmlFileName) => {
|
|
114
|
+
if (!viteConfig.experimental?.renderBuiltUrl) return viteConfig.base + file;
|
|
115
|
+
const result = viteConfig.experimental.renderBuiltUrl(file, {
|
|
116
|
+
hostId: htmlFileName,
|
|
117
|
+
hostType: "html",
|
|
118
|
+
type: "asset",
|
|
119
|
+
ssr: false
|
|
120
|
+
});
|
|
121
|
+
if (typeof result === "string") return result;
|
|
122
|
+
if (result && typeof result === "object") {
|
|
123
|
+
if ("runtime" in result) {
|
|
124
|
+
console.warn("[vite-plugin-federation] renderBuiltUrl returned runtime code for HTML injection. Runtime code cannot be used in <script src=\"\">. Falling back to base path.");
|
|
125
|
+
return viteConfig.base + file;
|
|
126
|
+
}
|
|
127
|
+
if (result.relative) return file;
|
|
128
|
+
}
|
|
129
|
+
return viteConfig.base + file;
|
|
130
|
+
};
|
|
131
|
+
for (const fileName in bundle) if (fileName.endsWith(".html")) {
|
|
132
|
+
let htmlAsset = bundle[fileName];
|
|
133
|
+
if (htmlAsset.type === "chunk") return;
|
|
134
|
+
const scriptContent = `
|
|
135
|
+
<script type="module" src="${resolvePath(fileName)}"><\/script>
|
|
136
|
+
`;
|
|
137
|
+
let htmlContent = htmlAsset.source.toString() || "";
|
|
138
|
+
htmlContent = htmlContent.replace("<head>", `<head>${scriptContent}`);
|
|
139
|
+
htmlAsset.source = htmlContent;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
transform(code, id) {
|
|
143
|
+
if (injectEntry() && entryFiles.some((file) => id.endsWith(file))) return mapCodeToCodeWithSourcemap(`
|
|
144
|
+
import ${JSON.stringify(entryPath)};
|
|
145
|
+
` + code);
|
|
146
|
+
}
|
|
147
|
+
}];
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/plugins/pluginCheckAliasConflicts.ts
|
|
152
|
+
/**
|
|
153
|
+
* Check if user-defined alias conflicts with shared modules
|
|
154
|
+
* This should run after aliasToArrayPlugin to ensure alias is an array
|
|
155
|
+
*/
|
|
156
|
+
function checkAliasConflicts(options) {
|
|
157
|
+
const { shared = {} } = options;
|
|
158
|
+
const sharedKeys = Object.keys(shared);
|
|
159
|
+
return {
|
|
160
|
+
name: "check-alias-conflicts",
|
|
161
|
+
configResolved(config) {
|
|
162
|
+
if (sharedKeys.length === 0) return;
|
|
163
|
+
const userAliases = config.resolve?.alias || [];
|
|
164
|
+
const conflicts = [];
|
|
165
|
+
for (const sharedKey of sharedKeys) for (const aliasEntry of userAliases) {
|
|
166
|
+
const findPattern = aliasEntry.find;
|
|
167
|
+
const replacement = aliasEntry.replacement;
|
|
168
|
+
if (typeof replacement !== "string") continue;
|
|
169
|
+
if (replacement === "$1") continue;
|
|
170
|
+
let isMatch = false;
|
|
171
|
+
if (typeof findPattern === "string") isMatch = findPattern === sharedKey || sharedKey.startsWith(findPattern + "/");
|
|
172
|
+
else if (findPattern instanceof RegExp) isMatch = findPattern.test(sharedKey);
|
|
173
|
+
if (isMatch) conflicts.push({
|
|
174
|
+
sharedModule: sharedKey,
|
|
175
|
+
alias: String(findPattern),
|
|
176
|
+
target: replacement
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (conflicts.length > 0) {
|
|
180
|
+
config.logger.warn("\n[Module Federation] Detected alias conflicts with shared modules:");
|
|
181
|
+
conflicts.forEach(({ sharedModule, alias, target }) => {
|
|
182
|
+
config.logger.warn(` - Shared module "${sharedModule}" is aliased by "${alias}" to "${target}"`);
|
|
183
|
+
});
|
|
184
|
+
config.logger.warn(" This may cause runtime errors as the shared module will bypass Module Federation's sharing mechanism.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/plugins/pluginDevProxyModuleTopLevelAwait.ts
|
|
192
|
+
/**
|
|
193
|
+
* Solve the problem that dev mode dependency prebunding does not support top-level await syntax
|
|
194
|
+
*/
|
|
195
|
+
function PluginDevProxyModuleTopLevelAwait() {
|
|
196
|
+
const filterFunction = createFilter();
|
|
197
|
+
const processedFlag = "/* already-processed-by-dev-proxy-module-top-level-await */";
|
|
198
|
+
return {
|
|
199
|
+
name: "dev-proxy-module-top-level-await",
|
|
200
|
+
apply: "serve",
|
|
201
|
+
transform(code, id) {
|
|
202
|
+
if (code.includes(processedFlag)) return null;
|
|
203
|
+
if (!code.includes("/*mf top-level-await placeholder replacement mf*/")) return null;
|
|
204
|
+
if (!filterFunction(id)) return null;
|
|
205
|
+
let ast;
|
|
206
|
+
try {
|
|
207
|
+
ast = this.parse(code, { allowReturnOutsideFunction: true });
|
|
208
|
+
} catch (e) {
|
|
209
|
+
throw new Error(`${id}: ${e}`);
|
|
210
|
+
}
|
|
211
|
+
const magicString = new MagicString(code);
|
|
212
|
+
walk(ast, { enter(node) {
|
|
213
|
+
if (node.type === "ExportNamedDeclaration" && node.specifiers) {
|
|
214
|
+
const exportSpecifiers = node.specifiers.map((specifier) => specifier.exported.name);
|
|
215
|
+
const proxyStatements = exportSpecifiers.map((name) => `
|
|
216
|
+
const __mfproxy__await${name} = await ${name}();
|
|
217
|
+
const __mfproxy__${name} = () => __mfproxy__await${name};
|
|
218
|
+
`).join("\n");
|
|
219
|
+
const exportStatements = exportSpecifiers.map((name) => `__mfproxy__${name} as ${name}`).join(", ");
|
|
220
|
+
const start = node.start;
|
|
221
|
+
const end = node.end;
|
|
222
|
+
const replacement = `${proxyStatements}\nexport { ${exportStatements} };`;
|
|
223
|
+
magicString.overwrite(start, end, replacement);
|
|
224
|
+
}
|
|
225
|
+
if (node.type === "ExportDefaultDeclaration") {
|
|
226
|
+
const declaration = node.declaration;
|
|
227
|
+
const start = node.start;
|
|
228
|
+
const end = node.end;
|
|
229
|
+
let proxyStatement;
|
|
230
|
+
let exportStatement = "default";
|
|
231
|
+
if (declaration.type === "Identifier") proxyStatement = `
|
|
232
|
+
const __mfproxy__awaitdefault = await ${declaration.name}();
|
|
233
|
+
const __mfproxy__default = __mfproxy__awaitdefault;
|
|
234
|
+
`;
|
|
235
|
+
else if (declaration.type === "CallExpression" || declaration.type === "FunctionDeclaration") proxyStatement = `
|
|
236
|
+
const __mfproxy__awaitdefault = await (${code.slice(declaration.start, declaration.end)});
|
|
237
|
+
const __mfproxy__default = __mfproxy__awaitdefault;
|
|
238
|
+
`;
|
|
239
|
+
else proxyStatement = `
|
|
240
|
+
const __mfproxy__awaitdefault = await (${code.slice(declaration.start, declaration.end)});
|
|
241
|
+
const __mfproxy__default = __mfproxy__awaitdefault;
|
|
242
|
+
`;
|
|
243
|
+
const replacement = `${proxyStatement}\nexport { __mfproxy__default as ${exportStatement} };`;
|
|
244
|
+
magicString.overwrite(start, end, replacement);
|
|
245
|
+
}
|
|
246
|
+
} });
|
|
247
|
+
return {
|
|
248
|
+
code: `${processedFlag}\n${magicString.toString()}`,
|
|
249
|
+
map: magicString.generateMap({ hires: true })
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/plugins/pluginDts.ts
|
|
257
|
+
const DEFAULT_DEV_OPTIONS = {
|
|
258
|
+
disableLiveReload: true,
|
|
259
|
+
disableHotTypesReload: false,
|
|
260
|
+
disableDynamicRemoteTypeHints: false
|
|
261
|
+
};
|
|
262
|
+
const DYNAMIC_HINTS_PLUGIN = "@module-federation/dts-plugin/dynamic-remote-type-hints-plugin";
|
|
263
|
+
const getIPv4 = () => process.env["FEDERATION_IPV4"] || "127.0.0.1";
|
|
264
|
+
const forkDevWorkerPath = __require.resolve("@module-federation/dts-plugin/dist/fork-dev-worker.js");
|
|
265
|
+
var DevWorker = class {
|
|
266
|
+
constructor(options) {
|
|
267
|
+
this.worker = rpc.createRpcWorker(forkDevWorkerPath, {}, void 0, false);
|
|
268
|
+
this.worker.connect(options);
|
|
269
|
+
}
|
|
270
|
+
update() {
|
|
271
|
+
this.worker.process?.send?.({
|
|
272
|
+
type: rpc.RpcGMCallTypes.CALL,
|
|
273
|
+
id: this.worker.id,
|
|
274
|
+
args: [void 0, "update"]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
exit() {
|
|
278
|
+
this.worker.terminate();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const normalizeDevOptions = (dev) => {
|
|
282
|
+
if (dev === false) return false;
|
|
283
|
+
if (dev === true || typeof dev === "undefined") return { ...DEFAULT_DEV_OPTIONS };
|
|
284
|
+
return {
|
|
285
|
+
...DEFAULT_DEV_OPTIONS,
|
|
286
|
+
...dev
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
const buildDtsModuleFederationConfig = (options) => {
|
|
290
|
+
const exposes = {};
|
|
291
|
+
Object.entries(options.exposes).forEach(([key, value]) => {
|
|
292
|
+
if (typeof value === "string") {
|
|
293
|
+
exposes[key] = value;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const importValue = Array.isArray(value.import) ? value.import[0] : value.import;
|
|
297
|
+
if (importValue) exposes[key] = importValue;
|
|
298
|
+
});
|
|
299
|
+
const remotes = {};
|
|
300
|
+
Object.entries(options.remotes).forEach(([key, remote]) => {
|
|
301
|
+
if (typeof remote === "string") {
|
|
302
|
+
remotes[key] = remote;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!remote.entry) return;
|
|
306
|
+
remotes[key] = `${remote.entryGlobalName?.startsWith("http") || remote.entryGlobalName?.includes(".json") ? remote.name || key : remote.entryGlobalName || remote.name || key}@${remote.entry}`;
|
|
307
|
+
});
|
|
308
|
+
return {
|
|
309
|
+
...options,
|
|
310
|
+
exposes,
|
|
311
|
+
remotes
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
const resolveOutputDir = (config) => {
|
|
315
|
+
const { outDir } = config.build;
|
|
316
|
+
if (path$1.isAbsolute(outDir)) return path$1.relative(config.root, outDir);
|
|
317
|
+
return outDir;
|
|
318
|
+
};
|
|
319
|
+
const ensureRuntimePlugin = (options, pluginId) => {
|
|
320
|
+
if (!options.runtimePlugins.some((plugin) => {
|
|
321
|
+
if (typeof plugin === "string") return plugin === pluginId;
|
|
322
|
+
return plugin[0] === pluginId;
|
|
323
|
+
})) options.runtimePlugins.push(pluginId);
|
|
324
|
+
};
|
|
325
|
+
const normalizeDevDtsOptions = (dts, context) => {
|
|
326
|
+
return normalizeOptions(isTSProject(dts, context), {
|
|
327
|
+
generateTypes: { compileInChildProcess: true },
|
|
328
|
+
consumeTypes: { consumeAPITypes: true },
|
|
329
|
+
extraOptions: {},
|
|
330
|
+
displayErrorInTerminal: typeof dts === "object" && dts ? dts.displayErrorInTerminal : void 0
|
|
331
|
+
}, "mfOptions.dts")(dts);
|
|
332
|
+
};
|
|
333
|
+
const logDtsError = (error, dtsOptions) => {
|
|
334
|
+
if (dtsOptions === false) return;
|
|
335
|
+
if (typeof dtsOptions === "object" && dtsOptions && dtsOptions.displayErrorInTerminal === false) return;
|
|
336
|
+
console.error(error);
|
|
337
|
+
};
|
|
338
|
+
function pluginDts(options) {
|
|
339
|
+
if (options.dts === false) return [];
|
|
340
|
+
const dtsModuleFederationConfig = buildDtsModuleFederationConfig(options);
|
|
341
|
+
let resolvedConfig;
|
|
342
|
+
let devWorker;
|
|
343
|
+
let normalizedDevOptions;
|
|
344
|
+
let hasGeneratedBundle = false;
|
|
345
|
+
return [{
|
|
346
|
+
name: "module-federation-dts-dev",
|
|
347
|
+
apply: "serve",
|
|
348
|
+
config(config) {
|
|
349
|
+
normalizedDevOptions = normalizeDevOptions(options.dev);
|
|
350
|
+
if (!normalizedDevOptions) return;
|
|
351
|
+
if (normalizedDevOptions.disableDynamicRemoteTypeHints) return;
|
|
352
|
+
ensureRuntimePlugin(options, DYNAMIC_HINTS_PLUGIN);
|
|
353
|
+
const define = config.define ? { ...config.define } : {};
|
|
354
|
+
if (!("FEDERATION_IPV4" in define)) define.FEDERATION_IPV4 = JSON.stringify(getIPv4());
|
|
355
|
+
config.define = define;
|
|
356
|
+
},
|
|
357
|
+
configResolved(config) {
|
|
358
|
+
resolvedConfig = config;
|
|
359
|
+
},
|
|
360
|
+
configureServer(server) {
|
|
361
|
+
if (!normalizedDevOptions || !resolvedConfig) return;
|
|
362
|
+
const devOptions = normalizedDevOptions;
|
|
363
|
+
if (devOptions.disableDynamicRemoteTypeHints && devOptions.disableHotTypesReload && devOptions.disableLiveReload) return;
|
|
364
|
+
if (!options.name) throw new Error("name is required if you want to enable dev server!");
|
|
365
|
+
const outputDir = resolveOutputDir(resolvedConfig);
|
|
366
|
+
const normalizedDtsOptions = normalizeDevDtsOptions(options.dts, resolvedConfig.root);
|
|
367
|
+
if (typeof normalizedDtsOptions !== "object") return;
|
|
368
|
+
const normalizedGenerateTypes = normalizeOptions(Boolean(normalizedDtsOptions), { compileInChildProcess: true }, "mfOptions.dts.generateTypes")(normalizedDtsOptions.generateTypes);
|
|
369
|
+
const remote = normalizedGenerateTypes === false ? void 0 : {
|
|
370
|
+
implementation: normalizedDtsOptions.implementation,
|
|
371
|
+
context: resolvedConfig.root,
|
|
372
|
+
outputDir,
|
|
373
|
+
moduleFederationConfig: { ...dtsModuleFederationConfig },
|
|
374
|
+
hostRemoteTypesFolder: normalizedGenerateTypes.typesFolder || "@mf-types",
|
|
375
|
+
...normalizedGenerateTypes,
|
|
376
|
+
typesFolder: ".dev-server"
|
|
377
|
+
};
|
|
378
|
+
if (remote && !remote.tsConfigPath && typeof normalizedDtsOptions === "object" && normalizedDtsOptions.tsConfigPath) remote.tsConfigPath = normalizedDtsOptions.tsConfigPath;
|
|
379
|
+
const normalizedConsumeTypes = normalizeOptions(Boolean(normalizedDtsOptions), { consumeAPITypes: true }, "mfOptions.dts.consumeTypes")(normalizedDtsOptions.consumeTypes);
|
|
380
|
+
const host = normalizedConsumeTypes === false ? void 0 : {
|
|
381
|
+
implementation: normalizedDtsOptions.implementation,
|
|
382
|
+
context: resolvedConfig.root,
|
|
383
|
+
moduleFederationConfig: dtsModuleFederationConfig,
|
|
384
|
+
typesFolder: normalizedConsumeTypes.typesFolder || "@mf-types",
|
|
385
|
+
abortOnError: false,
|
|
386
|
+
...normalizedConsumeTypes
|
|
387
|
+
};
|
|
388
|
+
const extraOptions = normalizedDtsOptions.extraOptions || {};
|
|
389
|
+
if (!remote && !host && devOptions.disableLiveReload) return;
|
|
390
|
+
const startDevWorker = async () => {
|
|
391
|
+
let remoteTypeUrls;
|
|
392
|
+
if (host) remoteTypeUrls = await new Promise((resolve) => {
|
|
393
|
+
consumeTypesAPI({
|
|
394
|
+
host,
|
|
395
|
+
extraOptions,
|
|
396
|
+
displayErrorInTerminal: normalizedDtsOptions.displayErrorInTerminal
|
|
397
|
+
}, resolve);
|
|
398
|
+
});
|
|
399
|
+
devWorker = new DevWorker({
|
|
400
|
+
name: options.name,
|
|
401
|
+
remote,
|
|
402
|
+
host: host ? {
|
|
403
|
+
...host,
|
|
404
|
+
remoteTypeUrls
|
|
405
|
+
} : void 0,
|
|
406
|
+
extraOptions,
|
|
407
|
+
disableLiveReload: devOptions.disableLiveReload,
|
|
408
|
+
disableHotTypesReload: devOptions.disableHotTypesReload
|
|
409
|
+
});
|
|
410
|
+
const update = () => devWorker?.update();
|
|
411
|
+
server.watcher.on("change", update);
|
|
412
|
+
server.watcher.on("add", update);
|
|
413
|
+
server.watcher.on("unlink", update);
|
|
414
|
+
server.httpServer?.once("close", () => {
|
|
415
|
+
devWorker?.exit();
|
|
416
|
+
server.watcher.off("change", update);
|
|
417
|
+
server.watcher.off("add", update);
|
|
418
|
+
server.watcher.off("unlink", update);
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
startDevWorker().catch((error) => {
|
|
422
|
+
logDtsError(error, normalizedDtsOptions);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}, {
|
|
426
|
+
name: "module-federation-dts-build",
|
|
427
|
+
apply: "build",
|
|
428
|
+
configResolved(config) {
|
|
429
|
+
resolvedConfig = config;
|
|
430
|
+
},
|
|
431
|
+
async generateBundle() {
|
|
432
|
+
if (hasGeneratedBundle) return;
|
|
433
|
+
hasGeneratedBundle = true;
|
|
434
|
+
if (!resolvedConfig) return;
|
|
435
|
+
let normalizedDtsOptions;
|
|
436
|
+
try {
|
|
437
|
+
normalizedDtsOptions = normalizeDtsOptions(dtsModuleFederationConfig, resolvedConfig.root);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
logDtsError(error, options.dts);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (typeof normalizedDtsOptions !== "object") return;
|
|
443
|
+
const context = resolvedConfig.root;
|
|
444
|
+
const outputDir = resolveOutputDir(resolvedConfig);
|
|
445
|
+
let consumeOptions;
|
|
446
|
+
try {
|
|
447
|
+
consumeOptions = normalizeConsumeTypesOptions({
|
|
448
|
+
context,
|
|
449
|
+
dtsOptions: normalizedDtsOptions,
|
|
450
|
+
pluginOptions: dtsModuleFederationConfig
|
|
451
|
+
});
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logDtsError(error, normalizedDtsOptions);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (consumeOptions?.host?.typesOnBuild) try {
|
|
457
|
+
await consumeTypesAPI(consumeOptions);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
logDtsError(error, normalizedDtsOptions);
|
|
460
|
+
}
|
|
461
|
+
let generateOptions;
|
|
462
|
+
try {
|
|
463
|
+
generateOptions = normalizeGenerateTypesOptions({
|
|
464
|
+
context,
|
|
465
|
+
outputDir,
|
|
466
|
+
dtsOptions: normalizedDtsOptions,
|
|
467
|
+
pluginOptions: dtsModuleFederationConfig
|
|
468
|
+
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
logDtsError(error, normalizedDtsOptions);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (!generateOptions) return;
|
|
474
|
+
try {
|
|
475
|
+
await generateTypesAPI({ dtsManagerOptions: generateOptions });
|
|
476
|
+
} catch (error) {
|
|
477
|
+
logDtsError(error, normalizedDtsOptions);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/utils/normalizeModuleFederationOptions.ts
|
|
485
|
+
function normalizeExposesItem(key, item) {
|
|
486
|
+
let importPath = "";
|
|
487
|
+
if (typeof item === "string") importPath = item;
|
|
488
|
+
if (typeof item === "object") importPath = item.import;
|
|
489
|
+
return { import: importPath };
|
|
490
|
+
}
|
|
491
|
+
function normalizeExposes(exposes) {
|
|
492
|
+
if (!exposes) return {};
|
|
493
|
+
const res = {};
|
|
494
|
+
Object.keys(exposes).forEach((key) => {
|
|
495
|
+
res[key] = normalizeExposesItem(key, exposes[key]);
|
|
496
|
+
});
|
|
497
|
+
return res;
|
|
498
|
+
}
|
|
499
|
+
function normalizeRemotes(remotes) {
|
|
500
|
+
if (!remotes) return {};
|
|
501
|
+
const result = {};
|
|
502
|
+
if (typeof remotes === "object") Object.keys(remotes).forEach((key) => {
|
|
503
|
+
result[key] = normalizeRemoteItem(key, remotes[key]);
|
|
504
|
+
});
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
function normalizeRemoteItem(key, remote) {
|
|
508
|
+
if (typeof remote === "string") {
|
|
509
|
+
const [entryGlobalName] = remote.split("@");
|
|
510
|
+
return {
|
|
511
|
+
type: "var",
|
|
512
|
+
name: key,
|
|
513
|
+
entry: remote.replace(entryGlobalName + "@", ""),
|
|
514
|
+
entryGlobalName,
|
|
515
|
+
shareScope: "default"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return Object.assign({
|
|
519
|
+
type: "var",
|
|
520
|
+
name: key,
|
|
521
|
+
shareScope: "default",
|
|
522
|
+
entryGlobalName: key
|
|
523
|
+
}, remote);
|
|
524
|
+
}
|
|
525
|
+
function removePathFromNpmPackage(packageString) {
|
|
526
|
+
const match = packageString.match(/^(?:@[^/]+\/)?[^/]+/);
|
|
527
|
+
return match ? match[0] : packageString;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Tries to find the package.json's version of a shared package
|
|
531
|
+
* if `package.json` is not declared in `exports`
|
|
532
|
+
* @param {string} sharedName
|
|
533
|
+
* @returns {string | undefined}
|
|
534
|
+
*/
|
|
535
|
+
function searchPackageVersion(sharedName) {
|
|
536
|
+
try {
|
|
537
|
+
const sharedPath = __require.resolve(sharedName);
|
|
538
|
+
let potentialPackageJsonDir = path$1.dirname(sharedPath);
|
|
539
|
+
const rootDir = path$1.parse(potentialPackageJsonDir).root;
|
|
540
|
+
while (path$1.parse(potentialPackageJsonDir).base !== "node_modules" && potentialPackageJsonDir !== rootDir) {
|
|
541
|
+
const potentialPackageJsonPath = path$1.join(potentialPackageJsonDir, "package.json");
|
|
542
|
+
if (fs.existsSync(potentialPackageJsonPath)) {
|
|
543
|
+
const potentialPackageJson = __require(potentialPackageJsonPath);
|
|
544
|
+
if (typeof potentialPackageJson == "object" && potentialPackageJson !== null && typeof potentialPackageJson.version === "string" && potentialPackageJson.name === sharedName) return potentialPackageJson.version;
|
|
545
|
+
}
|
|
546
|
+
potentialPackageJsonDir = path$1.dirname(potentialPackageJsonDir);
|
|
547
|
+
}
|
|
548
|
+
} catch (_) {}
|
|
549
|
+
}
|
|
550
|
+
function normalizeShareItem(key, shareItem) {
|
|
551
|
+
let version;
|
|
552
|
+
try {
|
|
553
|
+
try {
|
|
554
|
+
version = __require(path$1.join(removePathFromNpmPackage(key), "package.json")).version;
|
|
555
|
+
} catch (e1) {
|
|
556
|
+
try {
|
|
557
|
+
const localPath = path$1.join(process.cwd(), "node_modules", removePathFromNpmPackage(key), "package.json");
|
|
558
|
+
version = __require(localPath).version;
|
|
559
|
+
} catch (e2) {
|
|
560
|
+
version = searchPackageVersion(key);
|
|
561
|
+
if (!version) console.error(e1);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
} catch (e) {
|
|
565
|
+
console.error(`Unexpected error resolving version for ${key}:`, e);
|
|
566
|
+
}
|
|
567
|
+
if (typeof shareItem === "string") return {
|
|
568
|
+
name: shareItem,
|
|
569
|
+
version,
|
|
570
|
+
scope: "default",
|
|
571
|
+
from: "",
|
|
572
|
+
shareConfig: {
|
|
573
|
+
import: void 0,
|
|
574
|
+
singleton: false,
|
|
575
|
+
requiredVersion: version ? `^${version}` : "*"
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
return {
|
|
579
|
+
name: key,
|
|
580
|
+
from: "",
|
|
581
|
+
version: shareItem.version || version,
|
|
582
|
+
scope: shareItem.shareScope || "default",
|
|
583
|
+
shareConfig: {
|
|
584
|
+
import: typeof shareItem === "object" ? shareItem.import : void 0,
|
|
585
|
+
singleton: shareItem.singleton || false,
|
|
586
|
+
requiredVersion: shareItem.requiredVersion || (version ? `^${version}` : "*"),
|
|
587
|
+
strictVersion: !!shareItem.strictVersion
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function normalizeShared(shared) {
|
|
592
|
+
if (!shared) return {};
|
|
593
|
+
const result = {};
|
|
594
|
+
if (Array.isArray(shared)) {
|
|
595
|
+
shared.forEach((key) => {
|
|
596
|
+
result[key] = normalizeShareItem(key, key);
|
|
597
|
+
});
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
if (typeof shared === "object") Object.keys(shared).forEach((key) => {
|
|
601
|
+
result[key] = normalizeShareItem(key, shared[key]);
|
|
602
|
+
});
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
function normalizeLibrary(library) {
|
|
606
|
+
if (!library) return void 0;
|
|
607
|
+
return library;
|
|
608
|
+
}
|
|
609
|
+
function normalizeManifest(manifest = false) {
|
|
610
|
+
if (typeof manifest === "boolean") return manifest;
|
|
611
|
+
return Object.assign({
|
|
612
|
+
filePath: "",
|
|
613
|
+
disableAssetsAnalyze: false,
|
|
614
|
+
fileName: "mf-manifest.json"
|
|
615
|
+
}, manifest);
|
|
616
|
+
}
|
|
617
|
+
let config;
|
|
618
|
+
function getNormalizeModuleFederationOptions() {
|
|
619
|
+
return config;
|
|
620
|
+
}
|
|
621
|
+
function getNormalizeShareItem(key) {
|
|
622
|
+
const options = getNormalizeModuleFederationOptions();
|
|
623
|
+
return options.shared[key] || options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"];
|
|
624
|
+
}
|
|
625
|
+
function normalizeModuleFederationOptions(options) {
|
|
626
|
+
if (options.virtualModuleDir && options.virtualModuleDir.includes("/")) throw new Error(`Invalid virtualModuleDir: "${options.virtualModuleDir}". The virtualModuleDir option cannot contain slashes (/). Please use a single directory name like '__mf__virtual__your_app_name'.`);
|
|
627
|
+
return config = {
|
|
628
|
+
exposes: normalizeExposes(options.exposes),
|
|
629
|
+
filename: options.filename || "remoteEntry-[hash]",
|
|
630
|
+
library: normalizeLibrary(options.library),
|
|
631
|
+
name: options.name,
|
|
632
|
+
remotes: normalizeRemotes(options.remotes),
|
|
633
|
+
runtime: options.runtime,
|
|
634
|
+
shareScope: options.shareScope || "default",
|
|
635
|
+
shared: normalizeShared(options.shared),
|
|
636
|
+
runtimePlugins: options.runtimePlugins || [],
|
|
637
|
+
implementation: options.implementation || __require.resolve("@module-federation/runtime"),
|
|
638
|
+
manifest: normalizeManifest(options.manifest),
|
|
639
|
+
dev: options.dev,
|
|
640
|
+
dts: options.dts,
|
|
641
|
+
getPublicPath: options.getPublicPath,
|
|
642
|
+
publicPath: options.publicPath,
|
|
643
|
+
shareStrategy: options.shareStrategy || "version-first",
|
|
644
|
+
ignoreOrigin: options.ignoreOrigin || false,
|
|
645
|
+
virtualModuleDir: options.virtualModuleDir || "__mf__virtual",
|
|
646
|
+
hostInitInjectLocation: options.hostInitInjectLocation || "html",
|
|
647
|
+
bundleAllCSS: options.bundleAllCSS || false,
|
|
648
|
+
moduleParseTimeout: options.moduleParseTimeout || 10,
|
|
649
|
+
varFilename: options.varFilename,
|
|
650
|
+
target: options.target
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
//#endregion
|
|
655
|
+
//#region src/utils/packageNameUtils.ts
|
|
656
|
+
/**
|
|
657
|
+
* Escaping rules:
|
|
658
|
+
* Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names.
|
|
659
|
+
* @ => 1
|
|
660
|
+
* / => 2
|
|
661
|
+
* - => 3
|
|
662
|
+
* . => 4
|
|
663
|
+
*/
|
|
664
|
+
/**
|
|
665
|
+
* Encodes a package name into a valid file name.
|
|
666
|
+
* @param {string} name - The package name, e.g., "@scope/xx-xx.xx".
|
|
667
|
+
* @returns {string} - The encoded file name.
|
|
668
|
+
*/
|
|
669
|
+
function packageNameEncode(name) {
|
|
670
|
+
if (typeof name !== "string") throw new Error("A string package name is required");
|
|
671
|
+
return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_");
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Decodes an encoded file name back to the original package name.
|
|
675
|
+
* @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx".
|
|
676
|
+
* @returns {string} - The decoded package name.
|
|
677
|
+
*/
|
|
678
|
+
function packageNameDecode(encoded) {
|
|
679
|
+
if (typeof encoded !== "string") throw new Error("A string encoded file name is required");
|
|
680
|
+
return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, ".");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region src/utils/localSharedImportMap_temp.ts
|
|
685
|
+
/**
|
|
686
|
+
* https://github.com/module-federation/vite/issues/68
|
|
687
|
+
*/
|
|
688
|
+
function getLocalSharedImportMapPath_temp() {
|
|
689
|
+
const { name } = getNormalizeModuleFederationOptions();
|
|
690
|
+
return path.resolve(".__mf__temp", packageNameEncode(name), "localSharedImportMap");
|
|
691
|
+
}
|
|
692
|
+
function writeLocalSharedImportMap_temp(content) {
|
|
693
|
+
createFile(getLocalSharedImportMapPath_temp() + ".js", "\n// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68\n" + content);
|
|
694
|
+
}
|
|
695
|
+
function createFile(filePath, content) {
|
|
696
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
697
|
+
writeFileSync(filePath, content);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/utils/serializeRuntimeOptions.ts
|
|
702
|
+
/**
|
|
703
|
+
* Serializes a JavaScript object into a string of source code that can be evaluated.
|
|
704
|
+
* This function is used to create runtime plugin options without relying solely on JSON.stringify,
|
|
705
|
+
* allowing support for non-JSON types like RegExp, Date, Map, Set, and Functions.
|
|
706
|
+
* It also safely handles circular references.
|
|
707
|
+
*
|
|
708
|
+
* @param {Record<string, unknown>} options - The options object to serialize.
|
|
709
|
+
* @returns {string} The resulting JavaScript source code string.
|
|
710
|
+
*/
|
|
711
|
+
function serializeRuntimeOptions(options) {
|
|
712
|
+
const seenObjects = /* @__PURE__ */ new WeakSet();
|
|
713
|
+
/**
|
|
714
|
+
* Recursive inner function to serialize any value into a source code string.
|
|
715
|
+
*/
|
|
716
|
+
function valueToCode(val) {
|
|
717
|
+
if (val === null) return "null";
|
|
718
|
+
const type = typeof val;
|
|
719
|
+
if (type === "string") return JSON.stringify(val);
|
|
720
|
+
if (type === "number" || type === "boolean") return String(val);
|
|
721
|
+
if (type === "undefined") return "undefined";
|
|
722
|
+
if (type === "symbol") {
|
|
723
|
+
const desc = val.description ?? "";
|
|
724
|
+
return `Symbol(${JSON.stringify(desc)})`;
|
|
725
|
+
}
|
|
726
|
+
if (type === "function") return val.toString();
|
|
727
|
+
if (val instanceof Date) return `new Date(${JSON.stringify(val.toISOString())})`;
|
|
728
|
+
if (val instanceof RegExp) return `new RegExp(${JSON.stringify(val.source)}, ${JSON.stringify(val.flags)})`;
|
|
729
|
+
if (type === "object") {
|
|
730
|
+
if (seenObjects.has(val)) return `"__circular__"`;
|
|
731
|
+
seenObjects.add(val);
|
|
732
|
+
}
|
|
733
|
+
if (Array.isArray(val)) return `[${val.map(valueToCode).join(", ")}]`;
|
|
734
|
+
if (val instanceof Map) return `new Map([${Array.from(val.entries()).map(([k, v]) => `[${valueToCode(k)}, ${valueToCode(v)}]`).join(", ")}])`;
|
|
735
|
+
if (val instanceof Set) return `new Set([${Array.from(val.values()).map(valueToCode).join(", ")}])`;
|
|
736
|
+
if (type === "object") {
|
|
737
|
+
const properties = [];
|
|
738
|
+
for (const key in val) if (Object.prototype.hasOwnProperty.call(val, key)) properties.push(`${JSON.stringify(key)}: ${valueToCode(val[key])}`);
|
|
739
|
+
return `{${properties.join(", ")}}`;
|
|
740
|
+
}
|
|
741
|
+
return JSON.stringify(String(val));
|
|
742
|
+
}
|
|
743
|
+
const topLevelProps = [];
|
|
744
|
+
for (const key in options) if (Object.prototype.hasOwnProperty.call(options, key)) topLevelProps.push(`${JSON.stringify(key)}: ${valueToCode(options[key])}`);
|
|
745
|
+
return `{${topLevelProps.join(", ")}}`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
//#endregion
|
|
749
|
+
//#region src/utils/VirtualModule.ts
|
|
750
|
+
/**
|
|
751
|
+
* Initialize virtual module infrastructure BEFORE VirtualModule class is used.
|
|
752
|
+
* This must be called in the config hook to ensure the directory exists
|
|
753
|
+
* before Vite's optimization phase.
|
|
754
|
+
*/
|
|
755
|
+
function initVirtualModuleInfrastructure(root, virtualModuleDir = "__mf__virtual") {
|
|
756
|
+
const virtualPackagePath = join(join(root, "node_modules"), virtualModuleDir);
|
|
757
|
+
if (!existsSync(virtualPackagePath)) {
|
|
758
|
+
mkdirSync(virtualPackagePath, { recursive: true });
|
|
759
|
+
writeFileSync(join(virtualPackagePath, "empty.js"), "");
|
|
760
|
+
writeFileSync(join(virtualPackagePath, "package.json"), JSON.stringify({
|
|
761
|
+
name: virtualModuleDir,
|
|
762
|
+
main: "empty.js"
|
|
763
|
+
}));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
let rootDir;
|
|
767
|
+
function findNodeModulesDir(root = process.cwd()) {
|
|
768
|
+
let currentDir = root;
|
|
769
|
+
while (currentDir !== parse(currentDir).root) {
|
|
770
|
+
const nodeModulesPath = join(currentDir, "node_modules");
|
|
771
|
+
if (existsSync(nodeModulesPath)) return nodeModulesPath;
|
|
772
|
+
currentDir = dirname(currentDir);
|
|
773
|
+
}
|
|
774
|
+
return "";
|
|
775
|
+
}
|
|
776
|
+
let cachedNodeModulesDir;
|
|
777
|
+
function getNodeModulesDir() {
|
|
778
|
+
if (!cachedNodeModulesDir) cachedNodeModulesDir = findNodeModulesDir(rootDir);
|
|
779
|
+
return cachedNodeModulesDir;
|
|
780
|
+
}
|
|
781
|
+
function getSuffix(name) {
|
|
782
|
+
const base = basename(name);
|
|
783
|
+
const dotIndex = base.lastIndexOf(".");
|
|
784
|
+
if (dotIndex > 0 && dotIndex < base.length - 1) return base.slice(dotIndex);
|
|
785
|
+
return ".js";
|
|
786
|
+
}
|
|
787
|
+
const patternMap = {};
|
|
788
|
+
const cacheMap = {};
|
|
789
|
+
/**
|
|
790
|
+
* Physically generate files as virtual modules under node_modules/__mf__virtual/*
|
|
791
|
+
*/
|
|
792
|
+
function assertModuleFound(tag, str = "") {
|
|
793
|
+
const module = VirtualModule.findModule(tag, str);
|
|
794
|
+
if (!module) throw new Error(`Module Federation shared module '${str}' not found. Please ensure it's installed as a dependency in your package.json.`);
|
|
795
|
+
return module;
|
|
796
|
+
}
|
|
797
|
+
var VirtualModule = class {
|
|
798
|
+
/**
|
|
799
|
+
* Set the root path for finding node_modules
|
|
800
|
+
* @param root - Root path
|
|
801
|
+
*/
|
|
802
|
+
static setRoot(root) {
|
|
803
|
+
rootDir = root;
|
|
804
|
+
cachedNodeModulesDir = void 0;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Ensure virtual package directory exists
|
|
808
|
+
*/
|
|
809
|
+
static ensureVirtualPackageExists() {
|
|
810
|
+
const nodeModulesDir = getNodeModulesDir();
|
|
811
|
+
const { virtualModuleDir } = getNormalizeModuleFederationOptions();
|
|
812
|
+
const virtualPackagePath = resolve(nodeModulesDir, virtualModuleDir);
|
|
813
|
+
if (!existsSync(virtualPackagePath)) {
|
|
814
|
+
mkdirSync(virtualPackagePath);
|
|
815
|
+
writeFileSync(resolve(virtualPackagePath, "empty.js"), "");
|
|
816
|
+
writeFileSync(resolve(virtualPackagePath, "package.json"), JSON.stringify({
|
|
817
|
+
name: virtualModuleDir,
|
|
818
|
+
main: "empty.js"
|
|
819
|
+
}));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
static findModule(tag, str = "") {
|
|
823
|
+
if (!patternMap[tag]) patternMap[tag] = new RegExp(`(.*${packageNameEncode(tag)}(.+?)${packageNameEncode(tag)}.*)`);
|
|
824
|
+
const moduleName = (str.match(patternMap[tag]) || [])[2];
|
|
825
|
+
if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)];
|
|
826
|
+
}
|
|
827
|
+
constructor(name, tag = "__mf_v__", suffix = "") {
|
|
828
|
+
this.inited = false;
|
|
829
|
+
this.name = name;
|
|
830
|
+
this.tag = tag;
|
|
831
|
+
this.suffix = suffix || getSuffix(name);
|
|
832
|
+
if (!cacheMap[this.tag]) cacheMap[this.tag] = {};
|
|
833
|
+
cacheMap[this.tag][this.name] = this;
|
|
834
|
+
}
|
|
835
|
+
getPath() {
|
|
836
|
+
return resolve(getNodeModulesDir(), this.getImportId());
|
|
837
|
+
}
|
|
838
|
+
getImportId() {
|
|
839
|
+
const { name: mfName, virtualModuleDir } = getNormalizeModuleFederationOptions();
|
|
840
|
+
return `${virtualModuleDir}/${packageNameEncode(`${mfName}${this.tag}${this.name}${this.tag}`)}${this.suffix}`;
|
|
841
|
+
}
|
|
842
|
+
writeSync(code, force) {
|
|
843
|
+
if (!force && this.inited) return;
|
|
844
|
+
if (!this.inited) this.inited = true;
|
|
845
|
+
writeFileSync(this.getPath(), code);
|
|
846
|
+
}
|
|
847
|
+
write(code) {
|
|
848
|
+
writeFile(this.getPath(), code, function() {});
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/virtualModules/virtualExposes.ts
|
|
854
|
+
function getVirtualExposesId(options) {
|
|
855
|
+
return `virtual:mf-exposes:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
856
|
+
}
|
|
857
|
+
function generateExposes(options) {
|
|
858
|
+
return `
|
|
859
|
+
export default {
|
|
860
|
+
${Object.keys(options.exposes).map((key) => {
|
|
861
|
+
return `
|
|
862
|
+
${JSON.stringify(key)}: async () => {
|
|
863
|
+
const importModule = await import(${JSON.stringify(options.exposes[key].import)})
|
|
864
|
+
const exportModule = {}
|
|
865
|
+
Object.assign(exportModule, importModule)
|
|
866
|
+
Object.defineProperty(exportModule, "__esModule", {
|
|
867
|
+
value: true,
|
|
868
|
+
enumerable: false
|
|
869
|
+
})
|
|
870
|
+
return exportModule
|
|
871
|
+
}
|
|
872
|
+
`;
|
|
873
|
+
}).join(",")}
|
|
874
|
+
}
|
|
875
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
//#endregion
|
|
879
|
+
//#region src/virtualModules/virtualRuntimeInitStatus.ts
|
|
880
|
+
const virtualRuntimeInitStatus = new VirtualModule("runtimeInit");
|
|
881
|
+
function writeRuntimeInitStatus(command) {
|
|
882
|
+
const globalKey = `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
|
|
883
|
+
const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject } = globalThis[globalKey];
|
|
884
|
+
export { initPromise, initResolve, initReject };` : `module.exports = globalThis[globalKey];`;
|
|
885
|
+
virtualRuntimeInitStatus.writeSync(`
|
|
886
|
+
const globalKey = ${JSON.stringify(globalKey)};
|
|
887
|
+
if (!globalThis[globalKey]) {
|
|
888
|
+
let initResolve, initReject;
|
|
889
|
+
const initPromise = new Promise((re, rj) => {
|
|
890
|
+
initResolve = re;
|
|
891
|
+
initReject = rj;
|
|
892
|
+
});
|
|
893
|
+
globalThis[globalKey] = {
|
|
894
|
+
initPromise,
|
|
895
|
+
initResolve,
|
|
896
|
+
initReject,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
${exportStatement}
|
|
900
|
+
`);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region src/virtualModules/virtualRemotes.ts
|
|
905
|
+
const cacheRemoteMap = {};
|
|
906
|
+
const LOAD_REMOTE_TAG = "__loadRemote__";
|
|
907
|
+
function getRemoteVirtualModule(remote, command, isRolldown) {
|
|
908
|
+
if (!cacheRemoteMap[remote]) {
|
|
909
|
+
cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, isRolldown ? ".mjs" : ".js");
|
|
910
|
+
cacheRemoteMap[remote].writeSync(generateRemotes(remote, command, isRolldown));
|
|
911
|
+
}
|
|
912
|
+
return cacheRemoteMap[remote];
|
|
913
|
+
}
|
|
914
|
+
const usedRemotesMap = {};
|
|
915
|
+
function addUsedRemote(remoteKey, remoteModule) {
|
|
916
|
+
if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = /* @__PURE__ */ new Set();
|
|
917
|
+
usedRemotesMap[remoteKey].add(remoteModule);
|
|
918
|
+
}
|
|
919
|
+
function getUsedRemotesMap() {
|
|
920
|
+
return usedRemotesMap;
|
|
921
|
+
}
|
|
922
|
+
function generateRemotes(id, command, isRolldown) {
|
|
923
|
+
const useESM = command === "build" || isRolldown;
|
|
924
|
+
const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
925
|
+
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
926
|
+
const exportLine = command === "serve" && useESM ? "export default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
|
|
927
|
+
return `
|
|
928
|
+
${importLine}
|
|
929
|
+
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
|
|
930
|
+
const exportModule = ${awaitOrPlaceholder}initPromise.then(_ => res)
|
|
931
|
+
${exportLine}
|
|
932
|
+
`;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/virtualModules/virtualShared_preBuild.ts
|
|
937
|
+
/**
|
|
938
|
+
* Even the resolveId hook cannot interfere with vite pre-build,
|
|
939
|
+
* and adding query parameter virtual modules will also fail.
|
|
940
|
+
* You can only proxy to the real file through alias
|
|
941
|
+
*/
|
|
942
|
+
/**
|
|
943
|
+
* shared will be proxied:
|
|
944
|
+
* 1. __prebuild__: export shareModule (pre-built source code of modules such as vue, react, etc.)
|
|
945
|
+
* 2. __loadShare__: load shareModule (mfRuntime.loadShare('vue'))
|
|
946
|
+
*/
|
|
947
|
+
function getPackageNamedExports(pkg) {
|
|
948
|
+
try {
|
|
949
|
+
const mod = createRequire$1(new URL("file://" + process.cwd() + "/package.json"))(pkg);
|
|
950
|
+
return Object.keys(mod).filter((k) => k !== "default" && k !== "__esModule");
|
|
951
|
+
} catch {
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const preBuildCacheMap = {};
|
|
956
|
+
const PREBUILD_TAG = "__prebuild__";
|
|
957
|
+
function writePreBuildLibPath(pkg) {
|
|
958
|
+
if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG);
|
|
959
|
+
preBuildCacheMap[pkg].writeSync("");
|
|
960
|
+
}
|
|
961
|
+
function getPreBuildLibImportId(pkg) {
|
|
962
|
+
if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG);
|
|
963
|
+
return preBuildCacheMap[pkg].getImportId();
|
|
964
|
+
}
|
|
965
|
+
const LOAD_SHARE_TAG = "__loadShare__";
|
|
966
|
+
const loadShareCacheMap = {};
|
|
967
|
+
function getLoadShareModulePath(pkg, isRolldown, command) {
|
|
968
|
+
if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
|
|
969
|
+
return loadShareCacheMap[pkg].getPath();
|
|
970
|
+
}
|
|
971
|
+
function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
972
|
+
if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
|
|
973
|
+
const useESM = command === "build" || isRolldown;
|
|
974
|
+
const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
975
|
+
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
976
|
+
const namedExports = getPackageNamedExports(pkg);
|
|
977
|
+
let exportLine;
|
|
978
|
+
if (namedExports.length > 0) {
|
|
979
|
+
const destructure = `const { ${namedExports.join(", ")} } = exportModule;`;
|
|
980
|
+
const namedExportLine = `export { ${namedExports.join(", ")} };`;
|
|
981
|
+
exportLine = useESM ? `export default exportModule;\n ${destructure}\n ${namedExportLine}` : `module.exports = exportModule;\n ${destructure}\n Object.assign(module.exports, { ${namedExports.join(", ")} });`;
|
|
982
|
+
} else exportLine = useESM ? `export default exportModule\n export * from ${JSON.stringify(getPreBuildLibImportId(pkg))}` : "module.exports = exportModule";
|
|
983
|
+
loadShareCacheMap[pkg].writeSync(`
|
|
984
|
+
import ${JSON.stringify(getPreBuildLibImportId(pkg))};
|
|
985
|
+
${command !== "build" ? `;() => import(${JSON.stringify(pkg)}).catch(() => {});` : ""}
|
|
986
|
+
${importLine}
|
|
987
|
+
const res = initPromise.then(runtime => runtime.loadShare(${JSON.stringify(pkg)}, {
|
|
988
|
+
customShareInfo: {shareConfig:{
|
|
989
|
+
singleton: ${shareItem.shareConfig.singleton},
|
|
990
|
+
strictVersion: ${shareItem.shareConfig.strictVersion},
|
|
991
|
+
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
|
|
992
|
+
}}
|
|
993
|
+
}))
|
|
994
|
+
const exportModule = ${awaitOrPlaceholder}res.then((factory) => (typeof factory === "function" ? factory() : factory))
|
|
995
|
+
${exportLine}
|
|
996
|
+
`);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/virtualModules/virtualRemoteEntry.ts
|
|
1001
|
+
let usedShares = /* @__PURE__ */ new Set();
|
|
1002
|
+
function getUsedShares() {
|
|
1003
|
+
return usedShares;
|
|
1004
|
+
}
|
|
1005
|
+
function addUsedShares(pkg) {
|
|
1006
|
+
usedShares.add(pkg);
|
|
1007
|
+
}
|
|
1008
|
+
new VirtualModule("localSharedImportMap");
|
|
1009
|
+
function getLocalSharedImportMapPath() {
|
|
1010
|
+
return getLocalSharedImportMapPath_temp();
|
|
1011
|
+
}
|
|
1012
|
+
let prevSharedCount;
|
|
1013
|
+
function writeLocalSharedImportMap() {
|
|
1014
|
+
const sharedCount = getUsedShares().size;
|
|
1015
|
+
if (prevSharedCount !== sharedCount) {
|
|
1016
|
+
prevSharedCount = sharedCount;
|
|
1017
|
+
writeLocalSharedImportMap_temp(generateLocalSharedImportMap());
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function generateLocalSharedImportMap() {
|
|
1021
|
+
const options = getNormalizeModuleFederationOptions();
|
|
1022
|
+
return `
|
|
1023
|
+
import {loadShare} from "@module-federation/runtime";
|
|
1024
|
+
const importMap = {
|
|
1025
|
+
${Array.from(getUsedShares()).sort().map((pkg) => {
|
|
1026
|
+
const shareItem = getNormalizeShareItem(pkg);
|
|
1027
|
+
return `
|
|
1028
|
+
${JSON.stringify(pkg)}: async () => {
|
|
1029
|
+
${shareItem?.shareConfig.import === false ? `throw new Error(\`Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` : `let pkg = await import("${getPreBuildLibImportId(pkg)}");
|
|
1030
|
+
return pkg;`}
|
|
1031
|
+
}
|
|
1032
|
+
`;
|
|
1033
|
+
}).join(",")}
|
|
1034
|
+
}
|
|
1035
|
+
const usedShared = {
|
|
1036
|
+
${Array.from(getUsedShares()).sort().map((key) => {
|
|
1037
|
+
const shareItem = getNormalizeShareItem(key);
|
|
1038
|
+
if (!shareItem) return null;
|
|
1039
|
+
return `
|
|
1040
|
+
${JSON.stringify(key)}: {
|
|
1041
|
+
name: ${JSON.stringify(key)},
|
|
1042
|
+
version: ${JSON.stringify(shareItem.version)},
|
|
1043
|
+
scope: [${JSON.stringify(shareItem.scope)}],
|
|
1044
|
+
loaded: false,
|
|
1045
|
+
from: ${JSON.stringify(options.name)},
|
|
1046
|
+
async get () {
|
|
1047
|
+
if (${shareItem.shareConfig.import === false}) {
|
|
1048
|
+
throw new Error(\`Shared module '\${${JSON.stringify(key)}}' must be provided by host\`);
|
|
1049
|
+
}
|
|
1050
|
+
usedShared[${JSON.stringify(key)}].loaded = true
|
|
1051
|
+
const {${JSON.stringify(key)}: pkgDynamicImport} = importMap
|
|
1052
|
+
const res = await pkgDynamicImport()
|
|
1053
|
+
const exportModule = {...res}
|
|
1054
|
+
// All npm packages pre-built by vite will be converted to esm
|
|
1055
|
+
Object.defineProperty(exportModule, "__esModule", {
|
|
1056
|
+
value: true,
|
|
1057
|
+
enumerable: false
|
|
1058
|
+
})
|
|
1059
|
+
return function () {
|
|
1060
|
+
return exportModule
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
shareConfig: {
|
|
1064
|
+
singleton: ${shareItem.shareConfig.singleton},
|
|
1065
|
+
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)},
|
|
1066
|
+
${shareItem.shareConfig.import === false ? "import: false," : ""}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
`;
|
|
1070
|
+
}).filter((x) => x !== null).join(",")}
|
|
1071
|
+
}
|
|
1072
|
+
const usedRemotes = [${Object.keys(getUsedRemotesMap()).map((key) => {
|
|
1073
|
+
const remote = options.remotes[key];
|
|
1074
|
+
if (!remote) return null;
|
|
1075
|
+
return `
|
|
1076
|
+
{
|
|
1077
|
+
entryGlobalName: ${JSON.stringify(remote.entryGlobalName)},
|
|
1078
|
+
name: ${JSON.stringify(remote.name)},
|
|
1079
|
+
type: ${JSON.stringify(remote.type)},
|
|
1080
|
+
entry: ${JSON.stringify(remote.entry)},
|
|
1081
|
+
shareScope: ${JSON.stringify(remote.shareScope) ?? "default"},
|
|
1082
|
+
}
|
|
1083
|
+
`;
|
|
1084
|
+
}).filter((x) => x !== null).join(",")}
|
|
1085
|
+
]
|
|
1086
|
+
export {
|
|
1087
|
+
usedShared,
|
|
1088
|
+
usedRemotes
|
|
1089
|
+
}
|
|
1090
|
+
`;
|
|
1091
|
+
}
|
|
1092
|
+
const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
|
|
1093
|
+
function getRemoteEntryId(options) {
|
|
1094
|
+
return `${REMOTE_ENTRY_ID}:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
1095
|
+
}
|
|
1096
|
+
function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options)) {
|
|
1097
|
+
const pluginImportNames = options.runtimePlugins.map((p, i) => {
|
|
1098
|
+
if (typeof p === "string") return [
|
|
1099
|
+
`$runtimePlugin_${i}`,
|
|
1100
|
+
`import $runtimePlugin_${i} from "${p}";`,
|
|
1101
|
+
`undefined`
|
|
1102
|
+
];
|
|
1103
|
+
else return [
|
|
1104
|
+
`$runtimePlugin_${i}`,
|
|
1105
|
+
`import $runtimePlugin_${i} from "${p[0]}";`,
|
|
1106
|
+
serializeRuntimeOptions(p[1])
|
|
1107
|
+
];
|
|
1108
|
+
});
|
|
1109
|
+
return `
|
|
1110
|
+
import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
|
|
1111
|
+
${pluginImportNames.map((item) => item[1]).join("\n")}
|
|
1112
|
+
import exposesMap from "${virtualExposesId}"
|
|
1113
|
+
import {usedShared, usedRemotes} from "${getLocalSharedImportMapPath()}"
|
|
1114
|
+
import {
|
|
1115
|
+
initResolve
|
|
1116
|
+
} from "${virtualRuntimeInitStatus.getImportId()}"
|
|
1117
|
+
const initTokens = {}
|
|
1118
|
+
const shareScopeName = ${JSON.stringify(options.shareScope)}
|
|
1119
|
+
const mfName = ${JSON.stringify(options.name)}
|
|
1120
|
+
async function init(shared = {}, initScope = []) {
|
|
1121
|
+
const initRes = runtimeInit({
|
|
1122
|
+
name: mfName,
|
|
1123
|
+
remotes: usedRemotes,
|
|
1124
|
+
shared: usedShared,
|
|
1125
|
+
plugins: [${pluginImportNames.map((item) => `${item[0]}(${item[2]})`).join(", ")}],
|
|
1126
|
+
${options.shareStrategy ? `shareStrategy: '${options.shareStrategy}'` : ""}
|
|
1127
|
+
});
|
|
1128
|
+
// handling circular init calls
|
|
1129
|
+
var initToken = initTokens[shareScopeName];
|
|
1130
|
+
if (!initToken)
|
|
1131
|
+
initToken = initTokens[shareScopeName] = { from: mfName };
|
|
1132
|
+
if (initScope.indexOf(initToken) >= 0) return;
|
|
1133
|
+
initScope.push(initToken);
|
|
1134
|
+
initRes.initShareScopeMap('${options.shareScope}', shared);
|
|
1135
|
+
try {
|
|
1136
|
+
await Promise.all(await initRes.initializeSharing('${options.shareScope}', {
|
|
1137
|
+
strategy: '${options.shareStrategy}',
|
|
1138
|
+
from: "build",
|
|
1139
|
+
initScope
|
|
1140
|
+
}));
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
console.error(e)
|
|
1143
|
+
}
|
|
1144
|
+
initResolve(initRes)
|
|
1145
|
+
return initRes
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function getExposes(moduleName) {
|
|
1149
|
+
if (!(moduleName in exposesMap)) throw new Error(\`Module \${moduleName} does not exist in container.\`)
|
|
1150
|
+
return (exposesMap[moduleName])().then(res => () => res)
|
|
1151
|
+
}
|
|
1152
|
+
export {
|
|
1153
|
+
init,
|
|
1154
|
+
getExposes as get
|
|
1155
|
+
}
|
|
1156
|
+
`;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Inject entry file, automatically init when used as host,
|
|
1160
|
+
* and will not inject remoteEntry
|
|
1161
|
+
*/
|
|
1162
|
+
const HOST_AUTO_INIT_TAG = "__H_A_I__";
|
|
1163
|
+
const hostAutoInitModule = new VirtualModule("hostAutoInit", HOST_AUTO_INIT_TAG);
|
|
1164
|
+
function writeHostAutoInit(remoteEntryId = REMOTE_ENTRY_ID) {
|
|
1165
|
+
hostAutoInitModule.writeSync(`
|
|
1166
|
+
const remoteEntryPromise = import("${remoteEntryId}")
|
|
1167
|
+
// __tla only serves as a hack for vite-plugin-top-level-await.
|
|
1168
|
+
Promise.resolve(remoteEntryPromise)
|
|
1169
|
+
.then(remoteEntry => {
|
|
1170
|
+
return Promise.resolve(remoteEntry.__tla)
|
|
1171
|
+
.then(remoteEntry.init).catch(remoteEntry.init)
|
|
1172
|
+
})
|
|
1173
|
+
`);
|
|
1174
|
+
}
|
|
1175
|
+
function getHostAutoInitImportId() {
|
|
1176
|
+
return hostAutoInitModule.getImportId();
|
|
1177
|
+
}
|
|
1178
|
+
function getHostAutoInitPath() {
|
|
1179
|
+
return hostAutoInitModule.getPath();
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/virtualModules/index.ts
|
|
1184
|
+
function initVirtualModules(command, remoteEntryId) {
|
|
1185
|
+
writeLocalSharedImportMap();
|
|
1186
|
+
writeHostAutoInit(remoteEntryId);
|
|
1187
|
+
writeRuntimeInitStatus(command);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/utils/bundleHelpers.ts
|
|
1192
|
+
function findRemoteEntryFile(filename, bundle) {
|
|
1193
|
+
for (const [_, fileData] of Object.entries(bundle)) if (filename.replace(/[\[\]]/g, "_").replace(/\.[^/.]+$/, "") === fileData.name || fileData.name === "remoteEntry") return fileData.fileName;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/utils/cssModuleHelpers.ts
|
|
1198
|
+
const ASSET_TYPES = ["js", "css"];
|
|
1199
|
+
const LOAD_TIMINGS = ["sync", "async"];
|
|
1200
|
+
const JS_EXTENSIONS = [
|
|
1201
|
+
".ts",
|
|
1202
|
+
".tsx",
|
|
1203
|
+
".jsx",
|
|
1204
|
+
".mjs",
|
|
1205
|
+
".cjs"
|
|
1206
|
+
];
|
|
1207
|
+
/**
|
|
1208
|
+
* Creates an empty asset map structure for tracking JS and CSS assets
|
|
1209
|
+
* @returns Initialized asset map with sync/async arrays for JS and CSS
|
|
1210
|
+
*/
|
|
1211
|
+
const createEmptyAssetMap = () => ({
|
|
1212
|
+
js: {
|
|
1213
|
+
sync: [],
|
|
1214
|
+
async: []
|
|
1215
|
+
},
|
|
1216
|
+
css: {
|
|
1217
|
+
sync: [],
|
|
1218
|
+
async: []
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
/**
|
|
1222
|
+
* Tracks an asset in the preload map with deduplication
|
|
1223
|
+
* @param map - The preload map to update
|
|
1224
|
+
* @param key - The module key to track under
|
|
1225
|
+
* @param fileName - The asset filename to track
|
|
1226
|
+
* @param isAsync - Whether the asset is loaded async
|
|
1227
|
+
* @param type - The asset type ('js' or 'css')
|
|
1228
|
+
*/
|
|
1229
|
+
const trackAsset = (map, key, fileName, isAsync, type) => {
|
|
1230
|
+
if (!map[key]) map[key] = createEmptyAssetMap();
|
|
1231
|
+
const target = isAsync ? map[key][type].async : map[key][type].sync;
|
|
1232
|
+
if (!target.includes(fileName)) target.push(fileName);
|
|
1233
|
+
};
|
|
1234
|
+
/**
|
|
1235
|
+
* Checks if a file is a CSS file by extension
|
|
1236
|
+
* @param fileName - The filename to check
|
|
1237
|
+
* @returns True if file has a CSS extension (.css, .scss, .less)
|
|
1238
|
+
*/
|
|
1239
|
+
const isCSSFile = (fileName) => {
|
|
1240
|
+
return fileName.endsWith(".css") || fileName.endsWith(".scss") || fileName.endsWith(".less");
|
|
1241
|
+
};
|
|
1242
|
+
/**
|
|
1243
|
+
* Collects all CSS assets from the bundle
|
|
1244
|
+
* @param bundle - The Rollup output bundle
|
|
1245
|
+
* @returns Set of CSS asset filenames
|
|
1246
|
+
*/
|
|
1247
|
+
const collectCssAssets = (bundle) => {
|
|
1248
|
+
const cssAssets = /* @__PURE__ */ new Set();
|
|
1249
|
+
for (const [fileName, fileData] of Object.entries(bundle)) if (fileData.type === "asset" && isCSSFile(fileName)) cssAssets.add(fileName);
|
|
1250
|
+
return cssAssets;
|
|
1251
|
+
};
|
|
1252
|
+
/**
|
|
1253
|
+
* Checks if a chunk contains CSS modules (e.g. .css, .vanilla.css, .scss, .less)
|
|
1254
|
+
* by scanning its module list
|
|
1255
|
+
*/
|
|
1256
|
+
const chunkContainsCssModules = (modules) => {
|
|
1257
|
+
for (const modulePath of Object.keys(modules)) if (isCSSFile(modulePath)) return true;
|
|
1258
|
+
return false;
|
|
1259
|
+
};
|
|
1260
|
+
/**
|
|
1261
|
+
* Processes module assets and tracks them in the files map
|
|
1262
|
+
* @param bundle - The Rollup output bundle
|
|
1263
|
+
* @param filesMap - The preload map to populate
|
|
1264
|
+
* @param moduleMatcher - Function that matches module paths to keys
|
|
1265
|
+
*/
|
|
1266
|
+
const processModuleAssets = (bundle, filesMap, moduleMatcher) => {
|
|
1267
|
+
const bundleCssAssets = collectCssAssets(bundle);
|
|
1268
|
+
for (const [fileName, fileData] of Object.entries(bundle)) {
|
|
1269
|
+
if (fileData.type !== "chunk") continue;
|
|
1270
|
+
if (!fileData.modules) continue;
|
|
1271
|
+
for (const modulePath of Object.keys(fileData.modules)) {
|
|
1272
|
+
const matchKey = moduleMatcher(modulePath);
|
|
1273
|
+
if (!matchKey) continue;
|
|
1274
|
+
trackAsset(filesMap, matchKey, fileName, false, "js");
|
|
1275
|
+
let foundCssViaMetadata = false;
|
|
1276
|
+
if (fileData.viteMetadata?.importedCss?.size) for (const cssFile of fileData.viteMetadata.importedCss) {
|
|
1277
|
+
trackAsset(filesMap, matchKey, cssFile, false, "css");
|
|
1278
|
+
foundCssViaMetadata = true;
|
|
1279
|
+
}
|
|
1280
|
+
if (!foundCssViaMetadata && chunkContainsCssModules(fileData.modules)) for (const cssAsset of bundleCssAssets) trackAsset(filesMap, matchKey, cssAsset, false, "css");
|
|
1281
|
+
if (fileData.dynamicImports) for (const dynamicImport of fileData.dynamicImports) {
|
|
1282
|
+
if (!bundle[dynamicImport]) continue;
|
|
1283
|
+
trackAsset(filesMap, matchKey, dynamicImport, true, isCSSFile(dynamicImport) ? "css" : "js");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
/**
|
|
1289
|
+
* Deduplicates assets in the files map
|
|
1290
|
+
* @param filesMap - The preload map to deduplicate
|
|
1291
|
+
* @returns New deduplicated preload map
|
|
1292
|
+
*/
|
|
1293
|
+
const deduplicateAssets = (filesMap) => {
|
|
1294
|
+
const result = {};
|
|
1295
|
+
for (const [key, assetMaps] of Object.entries(filesMap)) {
|
|
1296
|
+
result[key] = createEmptyAssetMap();
|
|
1297
|
+
for (const type of ASSET_TYPES) for (const timing of LOAD_TIMINGS) result[key][type][timing] = Array.from(new Set(assetMaps[type][timing]));
|
|
1298
|
+
}
|
|
1299
|
+
return result;
|
|
1300
|
+
};
|
|
1301
|
+
/**
|
|
1302
|
+
* Builds a mapping between module files and their share keys
|
|
1303
|
+
* @param shareKeys - Set of share keys to map
|
|
1304
|
+
* @param resolveFn - Function to resolve module paths
|
|
1305
|
+
* @returns Map of file paths to their corresponding share keys
|
|
1306
|
+
*/
|
|
1307
|
+
const buildFileToShareKeyMap = async (shareKeys, resolveFn) => {
|
|
1308
|
+
const fileToShareKey = /* @__PURE__ */ new Map();
|
|
1309
|
+
const resolutions = await Promise.all(Array.from(shareKeys).map((shareKey) => resolveFn(getPreBuildLibImportId(shareKey)).then((resolution) => ({
|
|
1310
|
+
shareKey,
|
|
1311
|
+
file: resolution?.id?.split("?")[0]
|
|
1312
|
+
})).catch(() => null)));
|
|
1313
|
+
for (const resolution of resolutions) if (resolution?.file) fileToShareKey.set(resolution.file, resolution.shareKey);
|
|
1314
|
+
return fileToShareKey;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/utils/publicPath.ts
|
|
1319
|
+
/**
|
|
1320
|
+
* Resolves the public path for remote entries
|
|
1321
|
+
* @param options - Module Federation options
|
|
1322
|
+
* @param viteBase - Vite's base config value
|
|
1323
|
+
* @param originalBase - Original base config before any transformations
|
|
1324
|
+
* @returns The resolved public path
|
|
1325
|
+
*/
|
|
1326
|
+
function resolvePublicPath(options, viteBase, originalBase) {
|
|
1327
|
+
if (options.publicPath) return options.publicPath;
|
|
1328
|
+
if (originalBase === "") return "auto";
|
|
1329
|
+
if (viteBase) return viteBase.replace(/\/?$/, "/");
|
|
1330
|
+
return "auto";
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region src/plugins/pluginMFManifest.ts
|
|
1335
|
+
const Manifest = () => {
|
|
1336
|
+
const mfOptions = getNormalizeModuleFederationOptions();
|
|
1337
|
+
const { name, filename, getPublicPath, manifest: manifestOptions, varFilename } = mfOptions;
|
|
1338
|
+
let mfManifestName = "";
|
|
1339
|
+
if (manifestOptions === true) mfManifestName = "mf-manifest.json";
|
|
1340
|
+
if (typeof manifestOptions !== "boolean") mfManifestName = path$1.join(manifestOptions?.filePath || "", manifestOptions?.fileName || "");
|
|
1341
|
+
let root;
|
|
1342
|
+
let remoteEntryFile;
|
|
1343
|
+
let publicPath;
|
|
1344
|
+
let _command;
|
|
1345
|
+
let _originalConfigBase;
|
|
1346
|
+
let viteConfig;
|
|
1347
|
+
/**
|
|
1348
|
+
* Adds global CSS assets to all module exports
|
|
1349
|
+
* @param filesMap - The preload map to update
|
|
1350
|
+
* @param cssAssets - Set of CSS asset filenames to add
|
|
1351
|
+
*/
|
|
1352
|
+
const addCssAssetsToAllExports = (filesMap, cssAssets) => {
|
|
1353
|
+
Object.keys(filesMap).forEach((key) => {
|
|
1354
|
+
cssAssets.forEach((cssAsset) => {
|
|
1355
|
+
trackAsset(filesMap, key, cssAsset, false, "css");
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
};
|
|
1359
|
+
return [{
|
|
1360
|
+
name: "module-federation-manifest",
|
|
1361
|
+
apply: "serve",
|
|
1362
|
+
configResolved(config) {
|
|
1363
|
+
viteConfig = config;
|
|
1364
|
+
},
|
|
1365
|
+
configureServer(server) {
|
|
1366
|
+
server.middlewares.use((req, res, next) => {
|
|
1367
|
+
if (!mfManifestName) {
|
|
1368
|
+
next();
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (req.url?.replace(/\?.*/, "") === (viteConfig.base + mfManifestName).replace(/^\/?/, "/")) {
|
|
1372
|
+
res.setHeader("Content-Type", "application/json");
|
|
1373
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1374
|
+
res.end(JSON.stringify({
|
|
1375
|
+
...generateMFManifest({}),
|
|
1376
|
+
id: name,
|
|
1377
|
+
name,
|
|
1378
|
+
metaData: {
|
|
1379
|
+
name,
|
|
1380
|
+
type: "app",
|
|
1381
|
+
buildInfo: {
|
|
1382
|
+
buildVersion: "1.0.0",
|
|
1383
|
+
buildName: name
|
|
1384
|
+
},
|
|
1385
|
+
remoteEntry: {
|
|
1386
|
+
name: filename,
|
|
1387
|
+
path: "",
|
|
1388
|
+
type: "module"
|
|
1389
|
+
},
|
|
1390
|
+
ssrRemoteEntry: {
|
|
1391
|
+
name: filename,
|
|
1392
|
+
path: "",
|
|
1393
|
+
type: "module"
|
|
1394
|
+
},
|
|
1395
|
+
varRemoteEntry: varFilename ? {
|
|
1396
|
+
name: varFilename,
|
|
1397
|
+
path: "",
|
|
1398
|
+
type: "var"
|
|
1399
|
+
} : void 0,
|
|
1400
|
+
types: {
|
|
1401
|
+
path: "",
|
|
1402
|
+
name: ""
|
|
1403
|
+
},
|
|
1404
|
+
globalName: name,
|
|
1405
|
+
pluginVersion: "0.2.5",
|
|
1406
|
+
publicPath
|
|
1407
|
+
}
|
|
1408
|
+
}));
|
|
1409
|
+
} else next();
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}, {
|
|
1413
|
+
name: "module-federation-manifest",
|
|
1414
|
+
enforce: "post",
|
|
1415
|
+
config(config, { command }) {
|
|
1416
|
+
if (!config.build) config.build = {};
|
|
1417
|
+
if (!config.build.manifest) config.build.manifest = config.build.manifest || !!manifestOptions;
|
|
1418
|
+
_command = command;
|
|
1419
|
+
_originalConfigBase = config.base;
|
|
1420
|
+
},
|
|
1421
|
+
configResolved(config) {
|
|
1422
|
+
root = config.root;
|
|
1423
|
+
let base = config.base;
|
|
1424
|
+
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
1425
|
+
publicPath = resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
1426
|
+
},
|
|
1427
|
+
async generateBundle(options, bundle) {
|
|
1428
|
+
if (!mfManifestName) return;
|
|
1429
|
+
let filesMap = {};
|
|
1430
|
+
const foundRemoteEntryFile = findRemoteEntryFile(mfOptions.filename, bundle);
|
|
1431
|
+
if (foundRemoteEntryFile) remoteEntryFile = foundRemoteEntryFile;
|
|
1432
|
+
const allCssAssets = mfOptions.bundleAllCSS ? collectCssAssets(bundle) : /* @__PURE__ */ new Set();
|
|
1433
|
+
const exposesModules = Object.keys(mfOptions.exposes).map((item) => mfOptions.exposes[item].import);
|
|
1434
|
+
processModuleAssets(bundle, filesMap, (modulePath) => {
|
|
1435
|
+
const absoluteModulePath = path$1.resolve(root, modulePath);
|
|
1436
|
+
return exposesModules.find((exposeModule) => {
|
|
1437
|
+
const exposePath = path$1.resolve(root, exposeModule);
|
|
1438
|
+
if (absoluteModulePath === exposePath) return true;
|
|
1439
|
+
const getPathWithoutKnownExt = (filePath) => {
|
|
1440
|
+
const ext = path$1.extname(filePath);
|
|
1441
|
+
return JS_EXTENSIONS.includes(ext) ? path$1.join(path$1.dirname(filePath), path$1.basename(filePath, ext)) : filePath;
|
|
1442
|
+
};
|
|
1443
|
+
return getPathWithoutKnownExt(absoluteModulePath) === getPathWithoutKnownExt(exposePath);
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
const fileToShareKey = await buildFileToShareKeyMap(getUsedShares(), this.resolve.bind(this));
|
|
1447
|
+
processModuleAssets(bundle, filesMap, (modulePath) => fileToShareKey.get(modulePath));
|
|
1448
|
+
if (mfOptions.bundleAllCSS) addCssAssetsToAllExports(filesMap, allCssAssets);
|
|
1449
|
+
filesMap = deduplicateAssets(filesMap);
|
|
1450
|
+
this.emitFile({
|
|
1451
|
+
type: "asset",
|
|
1452
|
+
fileName: mfManifestName,
|
|
1453
|
+
source: JSON.stringify(generateMFManifest(filesMap))
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
}];
|
|
1457
|
+
/**
|
|
1458
|
+
* Generates the final manifest JSON structure
|
|
1459
|
+
* @param preloadMap - Map of module assets to include
|
|
1460
|
+
* @returns Complete manifest object
|
|
1461
|
+
*/
|
|
1462
|
+
function generateMFManifest(preloadMap) {
|
|
1463
|
+
const options = getNormalizeModuleFederationOptions();
|
|
1464
|
+
const { name, varFilename } = options;
|
|
1465
|
+
const remoteEntry = {
|
|
1466
|
+
name: remoteEntryFile,
|
|
1467
|
+
path: "",
|
|
1468
|
+
type: "module"
|
|
1469
|
+
};
|
|
1470
|
+
const varRemoteEntry = varFilename ? {
|
|
1471
|
+
name: varFilename,
|
|
1472
|
+
path: "",
|
|
1473
|
+
type: "module"
|
|
1474
|
+
} : void 0;
|
|
1475
|
+
const remotes = Array.from(Object.entries(getUsedRemotesMap())).flatMap(([remoteKey, modules]) => Array.from(modules).map((moduleKey) => ({
|
|
1476
|
+
federationContainerName: options.remotes[remoteKey].entry,
|
|
1477
|
+
moduleName: moduleKey.replace(remoteKey, "").replace("/", ""),
|
|
1478
|
+
alias: remoteKey,
|
|
1479
|
+
entry: "*"
|
|
1480
|
+
})));
|
|
1481
|
+
const shared = Array.from(getUsedShares()).map((shareKey) => {
|
|
1482
|
+
const shareItem = getNormalizeShareItem(shareKey);
|
|
1483
|
+
const assets = preloadMap[shareKey] || createEmptyAssetMap();
|
|
1484
|
+
return {
|
|
1485
|
+
id: `${name}:${shareKey}`,
|
|
1486
|
+
name: shareKey,
|
|
1487
|
+
version: shareItem.version,
|
|
1488
|
+
requiredVersion: shareItem.shareConfig.requiredVersion,
|
|
1489
|
+
assets: {
|
|
1490
|
+
js: {
|
|
1491
|
+
async: assets.js.async,
|
|
1492
|
+
sync: assets.js.sync
|
|
1493
|
+
},
|
|
1494
|
+
css: {
|
|
1495
|
+
async: assets.css.async,
|
|
1496
|
+
sync: assets.css.sync
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
}).filter(Boolean);
|
|
1501
|
+
const exposes = Object.entries(options.exposes).map(([key, value]) => {
|
|
1502
|
+
const formatKey = key.replace("./", "");
|
|
1503
|
+
const assets = preloadMap[value.import] || createEmptyAssetMap();
|
|
1504
|
+
return {
|
|
1505
|
+
id: `${name}:${formatKey}`,
|
|
1506
|
+
name: formatKey,
|
|
1507
|
+
assets: {
|
|
1508
|
+
js: {
|
|
1509
|
+
async: assets.js.async,
|
|
1510
|
+
sync: assets.js.sync
|
|
1511
|
+
},
|
|
1512
|
+
css: {
|
|
1513
|
+
async: assets.css.async,
|
|
1514
|
+
sync: assets.css.sync
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
path: key
|
|
1518
|
+
};
|
|
1519
|
+
}).filter(Boolean);
|
|
1520
|
+
return {
|
|
1521
|
+
id: name,
|
|
1522
|
+
name,
|
|
1523
|
+
metaData: {
|
|
1524
|
+
name,
|
|
1525
|
+
type: "app",
|
|
1526
|
+
buildInfo: {
|
|
1527
|
+
buildVersion: "1.0.0",
|
|
1528
|
+
buildName: name
|
|
1529
|
+
},
|
|
1530
|
+
remoteEntry,
|
|
1531
|
+
ssrRemoteEntry: remoteEntry,
|
|
1532
|
+
varRemoteEntry,
|
|
1533
|
+
types: {
|
|
1534
|
+
path: "",
|
|
1535
|
+
name: ""
|
|
1536
|
+
},
|
|
1537
|
+
globalName: name,
|
|
1538
|
+
pluginVersion: "0.2.5",
|
|
1539
|
+
...!!getPublicPath ? { getPublicPath } : { publicPath }
|
|
1540
|
+
},
|
|
1541
|
+
shared,
|
|
1542
|
+
remotes,
|
|
1543
|
+
exposes
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
//#endregion
|
|
1549
|
+
//#region src/plugins/pluginModuleParseEnd.ts
|
|
1550
|
+
let _resolve, _parseTimeout;
|
|
1551
|
+
const promise = new Promise((resolve, reject) => {
|
|
1552
|
+
_resolve = (v) => {
|
|
1553
|
+
clearTimeout(_parseTimeout);
|
|
1554
|
+
_parseTimeout = null;
|
|
1555
|
+
resolve(v);
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
function setParseTimeout(timeout) {
|
|
1559
|
+
if (!_parseTimeout) _parseTimeout = setTimeout(() => {
|
|
1560
|
+
console.warn(`Parse timeout (${timeout}s) - forcing resolve`);
|
|
1561
|
+
_resolve(1);
|
|
1562
|
+
}, timeout * 1e3);
|
|
1563
|
+
}
|
|
1564
|
+
let parsePromise = promise;
|
|
1565
|
+
let exposesParseEnd = false;
|
|
1566
|
+
const parseStartSet = /* @__PURE__ */ new Set();
|
|
1567
|
+
const parseEndSet = /* @__PURE__ */ new Set();
|
|
1568
|
+
function pluginModuleParseEnd_default(excludeFn, options) {
|
|
1569
|
+
setParseTimeout(options.moduleParseTimeout);
|
|
1570
|
+
return [
|
|
1571
|
+
{
|
|
1572
|
+
name: "_",
|
|
1573
|
+
apply: "serve",
|
|
1574
|
+
config() {
|
|
1575
|
+
_resolve(1);
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
enforce: "pre",
|
|
1580
|
+
name: "parseStart",
|
|
1581
|
+
apply: "build",
|
|
1582
|
+
load(id) {
|
|
1583
|
+
if (excludeFn(id)) return;
|
|
1584
|
+
parseStartSet.add(id);
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
enforce: "post",
|
|
1589
|
+
name: "parseEnd",
|
|
1590
|
+
apply: "build",
|
|
1591
|
+
moduleParsed(module) {
|
|
1592
|
+
const id = module.id;
|
|
1593
|
+
if (id === options.virtualExposesId) exposesParseEnd = true;
|
|
1594
|
+
if (excludeFn(id)) return;
|
|
1595
|
+
parseEndSet.add(id);
|
|
1596
|
+
if (exposesParseEnd && parseStartSet.size === parseEndSet.size) _resolve(1);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
];
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
//#endregion
|
|
1603
|
+
//#region src/plugins/pluginProxyRemoteEntry.ts
|
|
1604
|
+
const filter = createFilter();
|
|
1605
|
+
function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposesId }) {
|
|
1606
|
+
let viteConfig, _command;
|
|
1607
|
+
return {
|
|
1608
|
+
name: "proxyRemoteEntry",
|
|
1609
|
+
enforce: "post",
|
|
1610
|
+
configResolved(config) {
|
|
1611
|
+
viteConfig = config;
|
|
1612
|
+
},
|
|
1613
|
+
config(config, { command }) {
|
|
1614
|
+
_command = command;
|
|
1615
|
+
},
|
|
1616
|
+
async buildStart() {
|
|
1617
|
+
if (_command !== "build") return;
|
|
1618
|
+
for (const expose of Object.values(options.exposes)) {
|
|
1619
|
+
const resolved = await this.resolve(expose.import);
|
|
1620
|
+
if (resolved) this.emitFile({
|
|
1621
|
+
type: "chunk",
|
|
1622
|
+
id: resolved.id
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
async resolveId(id, importer) {
|
|
1627
|
+
if (id === remoteEntryId) return remoteEntryId;
|
|
1628
|
+
if (id === virtualExposesId) return virtualExposesId;
|
|
1629
|
+
if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
|
|
1630
|
+
if (importer === remoteEntryId && !id.startsWith(".") && !id.startsWith("/") && !id.startsWith("\0") && !id.startsWith("virtual:")) {
|
|
1631
|
+
const importPath = typeof __filename === "string" ? __filename : fileURLToPath(import.meta.url);
|
|
1632
|
+
const resolved = await this.resolve(id, importPath, { skipSelf: true });
|
|
1633
|
+
if (resolved) return resolved;
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
1636
|
+
load(id) {
|
|
1637
|
+
if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
|
|
1638
|
+
if (id === virtualExposesId) return generateExposes(options);
|
|
1639
|
+
if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
|
|
1640
|
+
},
|
|
1641
|
+
transform(code, id) {
|
|
1642
|
+
return mapCodeToCodeWithSourcemap((() => {
|
|
1643
|
+
if (!filter(id)) return;
|
|
1644
|
+
if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
|
|
1645
|
+
if (id === virtualExposesId) return generateExposes(options);
|
|
1646
|
+
if (id.includes(getHostAutoInitPath())) {
|
|
1647
|
+
if (_command === "serve") {
|
|
1648
|
+
const host = typeof viteConfig.server?.host === "string" && viteConfig.server.host !== "0.0.0.0" ? viteConfig.server.host : "localhost";
|
|
1649
|
+
const publicPath = JSON.stringify(resolvePublicPath(options, viteConfig.base) + options.filename);
|
|
1650
|
+
return `
|
|
1651
|
+
const origin = (window && ${!options.ignoreOrigin}) ? window.origin : "//${host}:${viteConfig.server?.port}"
|
|
1652
|
+
const remoteEntryPromise = await import(origin + ${publicPath})
|
|
1653
|
+
// __tla only serves as a hack for vite-plugin-top-level-await.
|
|
1654
|
+
Promise.resolve(remoteEntryPromise)
|
|
1655
|
+
.then(remoteEntry => {
|
|
1656
|
+
return Promise.resolve(remoteEntry.__tla)
|
|
1657
|
+
.then(remoteEntry.init).catch(remoteEntry.init)
|
|
1658
|
+
})
|
|
1659
|
+
`;
|
|
1660
|
+
}
|
|
1661
|
+
return code;
|
|
1662
|
+
}
|
|
1663
|
+
})());
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
//#endregion
|
|
1669
|
+
//#region src/plugins/pluginProxyRemotes.ts
|
|
1670
|
+
createFilter();
|
|
1671
|
+
function pluginProxyRemotes_default(options) {
|
|
1672
|
+
const { remotes } = options;
|
|
1673
|
+
return {
|
|
1674
|
+
name: "proxyRemotes",
|
|
1675
|
+
config(config, { command: _command }) {
|
|
1676
|
+
const isRolldown = !!this?.meta?.rolldownVersion;
|
|
1677
|
+
Object.keys(remotes).forEach((key) => {
|
|
1678
|
+
const remote = remotes[key];
|
|
1679
|
+
config.resolve.alias.push({
|
|
1680
|
+
find: new RegExp(`^(${remote.name}(\/.*|$))`),
|
|
1681
|
+
replacement: "$1",
|
|
1682
|
+
customResolver(source) {
|
|
1683
|
+
const remoteModule = getRemoteVirtualModule(source, _command, isRolldown);
|
|
1684
|
+
addUsedRemote(remote.name, source);
|
|
1685
|
+
return remoteModule.getPath();
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
//#endregion
|
|
1694
|
+
//#region src/utils/PromiseStore.ts
|
|
1695
|
+
/**
|
|
1696
|
+
* example:
|
|
1697
|
+
* const store = new PromiseStore<number>();
|
|
1698
|
+
* store.get("example").then((result) => {
|
|
1699
|
+
* console.log("Result from example:", result); // 42
|
|
1700
|
+
* });
|
|
1701
|
+
* setTimeout(() => {
|
|
1702
|
+
* store.set("example", Promise.resolve(42));
|
|
1703
|
+
* }, 2000);
|
|
1704
|
+
*/
|
|
1705
|
+
var PromiseStore = class {
|
|
1706
|
+
constructor() {
|
|
1707
|
+
this.promiseMap = /* @__PURE__ */ new Map();
|
|
1708
|
+
this.resolveMap = /* @__PURE__ */ new Map();
|
|
1709
|
+
}
|
|
1710
|
+
set(id, promise) {
|
|
1711
|
+
if (this.resolveMap.has(id)) {
|
|
1712
|
+
promise.then(this.resolveMap.get(id));
|
|
1713
|
+
this.resolveMap.delete(id);
|
|
1714
|
+
}
|
|
1715
|
+
this.promiseMap.set(id, promise);
|
|
1716
|
+
}
|
|
1717
|
+
get(id) {
|
|
1718
|
+
if (this.promiseMap.has(id)) return this.promiseMap.get(id);
|
|
1719
|
+
const pendingPromise = new Promise((resolve) => {
|
|
1720
|
+
this.resolveMap.set(id, resolve);
|
|
1721
|
+
});
|
|
1722
|
+
this.promiseMap.set(id, pendingPromise);
|
|
1723
|
+
return pendingPromise;
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
//#endregion
|
|
1728
|
+
//#region src/plugins/pluginProxySharedModule_preBuild.ts
|
|
1729
|
+
function proxySharedModule(options) {
|
|
1730
|
+
const { shared = {} } = options;
|
|
1731
|
+
let _config;
|
|
1732
|
+
let _command = "serve";
|
|
1733
|
+
const savePrebuild = new PromiseStore();
|
|
1734
|
+
return [{
|
|
1735
|
+
name: "generateLocalSharedImportMap",
|
|
1736
|
+
enforce: "post",
|
|
1737
|
+
load(id) {
|
|
1738
|
+
if (id.includes(getLocalSharedImportMapPath())) return parsePromise.then((_) => generateLocalSharedImportMap());
|
|
1739
|
+
},
|
|
1740
|
+
transform(_, id) {
|
|
1741
|
+
if (id.includes(getLocalSharedImportMapPath())) return mapCodeToCodeWithSourcemap(parsePromise.then((_) => generateLocalSharedImportMap()));
|
|
1742
|
+
}
|
|
1743
|
+
}, {
|
|
1744
|
+
name: "proxyPreBuildShared",
|
|
1745
|
+
enforce: "post",
|
|
1746
|
+
config(config, { command }) {
|
|
1747
|
+
const isRolldown = !!this?.meta?.rolldownVersion;
|
|
1748
|
+
_command = command;
|
|
1749
|
+
config.resolve.alias.push(...Object.keys(shared).map((key) => {
|
|
1750
|
+
const keyBase = key.endsWith("/") ? key.slice(0, -1) : key;
|
|
1751
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1752
|
+
const escapedKeyBase = escapeRegex(keyBase);
|
|
1753
|
+
const pattern = key.endsWith("/") ? `^(${escapedKeyBase}(?:\\/.*)?)$` : `^(${escapedKeyBase})$`;
|
|
1754
|
+
return {
|
|
1755
|
+
find: new RegExp(pattern),
|
|
1756
|
+
replacement: "$1",
|
|
1757
|
+
customResolver(source, importer) {
|
|
1758
|
+
if (/\.css$/.test(source)) return;
|
|
1759
|
+
if (importer && importer.includes("localSharedImportMap")) return;
|
|
1760
|
+
if (key.endsWith("/") && source !== key.slice(0, -1)) return;
|
|
1761
|
+
const loadSharePath = getLoadShareModulePath(source, isRolldown, command);
|
|
1762
|
+
writeLoadShareModule(source, shared[key], command, isRolldown);
|
|
1763
|
+
writePreBuildLibPath(source);
|
|
1764
|
+
addUsedShares(source);
|
|
1765
|
+
writeLocalSharedImportMap();
|
|
1766
|
+
return this.resolve(loadSharePath, importer);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
}));
|
|
1770
|
+
config.resolve.alias.push(...Object.keys(shared).map((key) => {
|
|
1771
|
+
return command === "build" ? {
|
|
1772
|
+
find: new RegExp(`(.*${PREBUILD_TAG}.*)`),
|
|
1773
|
+
replacement: function($1) {
|
|
1774
|
+
return assertModuleFound(PREBUILD_TAG, $1).name;
|
|
1775
|
+
}
|
|
1776
|
+
} : {
|
|
1777
|
+
find: new RegExp(`(.*${PREBUILD_TAG}.*)`),
|
|
1778
|
+
replacement: "$1",
|
|
1779
|
+
async customResolver(source, importer) {
|
|
1780
|
+
const pkgName = assertModuleFound(PREBUILD_TAG, source).name;
|
|
1781
|
+
const result = await this.resolve(pkgName, importer).then((item) => item.id);
|
|
1782
|
+
if (_config && !result.includes(_config.cacheDir)) savePrebuild.set(pkgName, Promise.resolve(result));
|
|
1783
|
+
return await this.resolve(await savePrebuild.get(pkgName), importer);
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
}));
|
|
1787
|
+
},
|
|
1788
|
+
configResolved(config) {
|
|
1789
|
+
_config = config;
|
|
1790
|
+
const isRolldown = !!config.experimental?.rolldownDev;
|
|
1791
|
+
Object.keys(shared).forEach((key) => {
|
|
1792
|
+
if (key.endsWith("/")) return;
|
|
1793
|
+
writeLoadShareModule(key, shared[key], _command, isRolldown);
|
|
1794
|
+
writePreBuildLibPath(key);
|
|
1795
|
+
addUsedShares(key);
|
|
1796
|
+
});
|
|
1797
|
+
writeLocalSharedImportMap();
|
|
1798
|
+
}
|
|
1799
|
+
}];
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
//#endregion
|
|
1803
|
+
//#region src/plugins/pluginVarRemoteEntry.ts
|
|
1804
|
+
const VarRemoteEntry = () => {
|
|
1805
|
+
const mfOptions = getNormalizeModuleFederationOptions();
|
|
1806
|
+
const { name, varFilename, filename } = mfOptions;
|
|
1807
|
+
let viteConfig;
|
|
1808
|
+
return [{
|
|
1809
|
+
name: "module-federation-var-remote-entry",
|
|
1810
|
+
apply: "serve",
|
|
1811
|
+
configResolved(config) {
|
|
1812
|
+
viteConfig = config;
|
|
1813
|
+
},
|
|
1814
|
+
configureServer(server) {
|
|
1815
|
+
server.middlewares.use((req, res, next) => {
|
|
1816
|
+
if (!varFilename) {
|
|
1817
|
+
next();
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
if (req.url?.replace(/\?.*/, "") === (viteConfig.base + varFilename).replace(/^\/?/, "/")) {
|
|
1821
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
1822
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1823
|
+
console.log({ filename });
|
|
1824
|
+
res.end(generateVarRemoteEntry(filename));
|
|
1825
|
+
} else next();
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}, {
|
|
1829
|
+
name: "module-federation-var-remote-entry",
|
|
1830
|
+
enforce: "post",
|
|
1831
|
+
config(config, { command }) {
|
|
1832
|
+
if (!config.build) config.build = {};
|
|
1833
|
+
},
|
|
1834
|
+
configResolved(config) {
|
|
1835
|
+
viteConfig = config;
|
|
1836
|
+
},
|
|
1837
|
+
async generateBundle(options, bundle) {
|
|
1838
|
+
if (!varFilename) return;
|
|
1839
|
+
if (!isValidVarName(name)) viteConfig.logger.warn(`Provided remote name "${name}" is not valid for "var" remoteEntry type, thus it's placed in globalThis['${name}'].\nIt may cause problems, so you would better want to use valid var name (see https://www.w3schools.com/js/js_variables.asp).`);
|
|
1840
|
+
const remoteEntryFile = findRemoteEntryFile(mfOptions.filename, bundle);
|
|
1841
|
+
if (!remoteEntryFile) throw new Error(`Couldn't find a remoteEntry chunk file for ${mfOptions.filename}, can't generate varRemoteEntry file`);
|
|
1842
|
+
this.emitFile({
|
|
1843
|
+
type: "asset",
|
|
1844
|
+
fileName: varFilename,
|
|
1845
|
+
source: generateVarRemoteEntry(remoteEntryFile)
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
}];
|
|
1849
|
+
function isValidVarName(name) {
|
|
1850
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Generates the final "var" remote entry file
|
|
1854
|
+
* @param remoteEntryFile - Path to esm remote entry file
|
|
1855
|
+
* @returns Complete "var" remoteEntry.js file source
|
|
1856
|
+
*/
|
|
1857
|
+
function generateVarRemoteEntry(remoteEntryFile) {
|
|
1858
|
+
const { name, varFilename } = getNormalizeModuleFederationOptions();
|
|
1859
|
+
const isValidName = isValidVarName(name);
|
|
1860
|
+
return `
|
|
1861
|
+
${isValidName ? `var ${name};` : ""}
|
|
1862
|
+
${isValidName ? name : `globalThis['${name}']`} = (function () {
|
|
1863
|
+
function getScriptUrl() {
|
|
1864
|
+
const currentScript = document.currentScript;
|
|
1865
|
+
if (!currentScript) {
|
|
1866
|
+
console.error("[VarRemoteEntry] ${varFilename} script should be called from sync <script> tag (document.currentScript is undefined)")
|
|
1867
|
+
return '/';
|
|
1868
|
+
}
|
|
1869
|
+
return document.currentScript.src.replace(/\\/[^/]*$/, '/');
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const entry = getScriptUrl() + '${remoteEntryFile}';
|
|
1873
|
+
|
|
1874
|
+
return {
|
|
1875
|
+
get: (...args) => import(entry).then(m => m.get(...args)),
|
|
1876
|
+
init: (...args) => import(entry).then(m => m.init(...args)),
|
|
1877
|
+
};
|
|
1878
|
+
})();
|
|
1879
|
+
`;
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
//#endregion
|
|
1884
|
+
//#region src/utils/aliasToArrayPlugin.ts
|
|
1885
|
+
var aliasToArrayPlugin_default = {
|
|
1886
|
+
name: "alias-transform-plugin",
|
|
1887
|
+
config: (config, { command }) => {
|
|
1888
|
+
if (!config.resolve) config.resolve = {};
|
|
1889
|
+
if (!config.resolve.alias) config.resolve.alias = [];
|
|
1890
|
+
const { alias } = config.resolve;
|
|
1891
|
+
if (typeof alias === "object" && !Array.isArray(alias)) config.resolve.alias = Object.entries(alias).map(([find, replacement]) => ({
|
|
1892
|
+
find,
|
|
1893
|
+
replacement
|
|
1894
|
+
}));
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
//#endregion
|
|
1899
|
+
//#region src/utils/normalizeOptimizeDeps.ts
|
|
1900
|
+
var normalizeOptimizeDeps_default = {
|
|
1901
|
+
name: "normalizeOptimizeDeps",
|
|
1902
|
+
config: (config, { command }) => {
|
|
1903
|
+
let { optimizeDeps } = config;
|
|
1904
|
+
if (!optimizeDeps) {
|
|
1905
|
+
config.optimizeDeps = {};
|
|
1906
|
+
optimizeDeps = config.optimizeDeps;
|
|
1907
|
+
}
|
|
1908
|
+
if (!optimizeDeps.include) optimizeDeps.include = [];
|
|
1909
|
+
if (!optimizeDeps.exclude) optimizeDeps.exclude = [];
|
|
1910
|
+
if (!optimizeDeps.needsInterop) optimizeDeps.needsInterop = [];
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1914
|
+
//#endregion
|
|
1915
|
+
//#region src/index.ts
|
|
1916
|
+
/**
|
|
1917
|
+
* Plugin that runs FIRST to create virtual module files in the config hook.
|
|
1918
|
+
* This prevents 504 "Outdated Optimize Dep" errors by ensuring files exist
|
|
1919
|
+
* before Vite's optimization phase.
|
|
1920
|
+
*/
|
|
1921
|
+
function createEarlyVirtualModulesPlugin(options) {
|
|
1922
|
+
const { shared, remotes, virtualModuleDir } = options;
|
|
1923
|
+
return {
|
|
1924
|
+
name: "vite:module-federation-early-init",
|
|
1925
|
+
enforce: "pre",
|
|
1926
|
+
config(config, { command: _command }) {
|
|
1927
|
+
if (_command !== "serve") return;
|
|
1928
|
+
const isRolldown = !!this?.meta?.rolldownVersion;
|
|
1929
|
+
const root = config.root || process.cwd();
|
|
1930
|
+
initVirtualModuleInfrastructure(root, virtualModuleDir);
|
|
1931
|
+
VirtualModule.setRoot(root);
|
|
1932
|
+
VirtualModule.ensureVirtualPackageExists();
|
|
1933
|
+
initVirtualModules(_command, getRemoteEntryId(options));
|
|
1934
|
+
if (remotes && Object.keys(remotes).length > 0) for (const key of Object.keys(remotes)) addUsedRemote(key, key);
|
|
1935
|
+
if (shared && Object.keys(shared).length > 0) {
|
|
1936
|
+
config.optimizeDeps = config.optimizeDeps || {};
|
|
1937
|
+
config.optimizeDeps.include = config.optimizeDeps.include || [];
|
|
1938
|
+
config.optimizeDeps.include.push(virtualRuntimeInitStatus.getImportId());
|
|
1939
|
+
for (const key of Object.keys(shared)) {
|
|
1940
|
+
if (key.endsWith("/")) continue;
|
|
1941
|
+
const shareItem = shared[key];
|
|
1942
|
+
getLoadShareModulePath(key, isRolldown);
|
|
1943
|
+
writeLoadShareModule(key, shareItem, _command, isRolldown);
|
|
1944
|
+
writePreBuildLibPath(key);
|
|
1945
|
+
addUsedShares(key);
|
|
1946
|
+
config.optimizeDeps.include.push(getPreBuildLibImportId(key));
|
|
1947
|
+
}
|
|
1948
|
+
writeLocalSharedImportMap();
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
function federation(mfUserOptions) {
|
|
1954
|
+
const options = normalizeModuleFederationOptions(mfUserOptions);
|
|
1955
|
+
const { name, remotes, shared, filename, hostInitInjectLocation } = options;
|
|
1956
|
+
if (!name) throw new Error("name is required");
|
|
1957
|
+
const remoteEntryId = getRemoteEntryId(options);
|
|
1958
|
+
const virtualExposesId = getVirtualExposesId(options);
|
|
1959
|
+
let command;
|
|
1960
|
+
return [
|
|
1961
|
+
createEarlyVirtualModulesPlugin(options),
|
|
1962
|
+
{
|
|
1963
|
+
name: "vite:module-federation-config",
|
|
1964
|
+
enforce: "pre",
|
|
1965
|
+
config(_config, env) {
|
|
1966
|
+
command = env.command;
|
|
1967
|
+
},
|
|
1968
|
+
configResolved(config) {
|
|
1969
|
+
VirtualModule.setRoot(config.root);
|
|
1970
|
+
VirtualModule.ensureVirtualPackageExists();
|
|
1971
|
+
initVirtualModules(command, remoteEntryId);
|
|
1972
|
+
}
|
|
1973
|
+
},
|
|
1974
|
+
aliasToArrayPlugin_default,
|
|
1975
|
+
checkAliasConflicts({ shared }),
|
|
1976
|
+
normalizeOptimizeDeps_default,
|
|
1977
|
+
...pluginDts(options),
|
|
1978
|
+
...addEntry({
|
|
1979
|
+
entryName: "remoteEntry",
|
|
1980
|
+
entryPath: remoteEntryId,
|
|
1981
|
+
fileName: filename
|
|
1982
|
+
}),
|
|
1983
|
+
...addEntry({
|
|
1984
|
+
entryName: "hostInit",
|
|
1985
|
+
entryPath: getHostAutoInitPath(),
|
|
1986
|
+
inject: hostInitInjectLocation
|
|
1987
|
+
}),
|
|
1988
|
+
...addEntry({
|
|
1989
|
+
entryName: "virtualExposes",
|
|
1990
|
+
entryPath: virtualExposesId
|
|
1991
|
+
}),
|
|
1992
|
+
pluginProxyRemoteEntry_default({
|
|
1993
|
+
options,
|
|
1994
|
+
remoteEntryId,
|
|
1995
|
+
virtualExposesId
|
|
1996
|
+
}),
|
|
1997
|
+
pluginProxyRemotes_default(options),
|
|
1998
|
+
...pluginModuleParseEnd_default((id) => {
|
|
1999
|
+
return id.includes(getHostAutoInitImportId()) || id.includes(remoteEntryId) || id.includes(virtualExposesId) || id.includes(getLocalSharedImportMapPath());
|
|
2000
|
+
}, {
|
|
2001
|
+
moduleParseTimeout: options.moduleParseTimeout,
|
|
2002
|
+
virtualExposesId
|
|
2003
|
+
}),
|
|
2004
|
+
...proxySharedModule({ shared }),
|
|
2005
|
+
{
|
|
2006
|
+
name: "module-federation-esm-shims",
|
|
2007
|
+
enforce: "pre",
|
|
2008
|
+
apply: "build",
|
|
2009
|
+
config(config) {
|
|
2010
|
+
const runtimeInitId = virtualRuntimeInitStatus.getImportId();
|
|
2011
|
+
config.build = config.build || {};
|
|
2012
|
+
config.build.rollupOptions = config.build.rollupOptions || {};
|
|
2013
|
+
if (!Array.isArray(config.build.rollupOptions.output)) {
|
|
2014
|
+
const output = config.build.rollupOptions.output ||= {};
|
|
2015
|
+
const existingManualChunks = output.manualChunks;
|
|
2016
|
+
output.manualChunks = function(id) {
|
|
2017
|
+
if (id.includes(runtimeInitId)) return "runtimeInit";
|
|
2018
|
+
if (id.includes(LOAD_SHARE_TAG)) {
|
|
2019
|
+
const match = id.match(/([^/\\]+__loadShare__[^/\\]+)/);
|
|
2020
|
+
return match ? match[1] : "loadShare";
|
|
2021
|
+
}
|
|
2022
|
+
if (typeof existingManualChunks === "function") return existingManualChunks.apply(this, arguments);
|
|
2023
|
+
if (existingManualChunks && typeof existingManualChunks === "object") {
|
|
2024
|
+
for (const [key, ids] of Object.entries(existingManualChunks)) if (Array.isArray(ids) && ids.some((v) => id.includes(v))) return key;
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
},
|
|
2029
|
+
load(id) {
|
|
2030
|
+
if (id.startsWith("\0")) return;
|
|
2031
|
+
if (id.includes(LOAD_SHARE_TAG) || id.includes(LOAD_REMOTE_TAG)) {
|
|
2032
|
+
let code = readFileSync(id, "utf-8");
|
|
2033
|
+
code = code.replace(/import\s+["'][^"']*__prebuild__[^"']*["']\s*;?/g, "");
|
|
2034
|
+
code = code.replace(/export\s+\*\s+from\s+["'][^"']*__prebuild__[^"']*["']\s*;?/g, "");
|
|
2035
|
+
/**
|
|
2036
|
+
* Shared/remote shims only have `export default exportModule`.
|
|
2037
|
+
*
|
|
2038
|
+
* We add a second named export (__moduleExports) that holds the full
|
|
2039
|
+
* module namespace and point syntheticNamedExports at it. This lets
|
|
2040
|
+
* Rollup resolve named imports (e.g. `import { useState } from 'react'`)
|
|
2041
|
+
* from the namespace while still applying its normal default-export
|
|
2042
|
+
* interop — which is needed for libraries like @emotion/styled where
|
|
2043
|
+
* `import styled from '@emotion/styled'` must receive the .default
|
|
2044
|
+
* function, not the raw namespace object.
|
|
2045
|
+
*
|
|
2046
|
+
* Using 'default' as the syntheticNamedExports key would skip the
|
|
2047
|
+
* interop and break default imports.
|
|
2048
|
+
*
|
|
2049
|
+
* @see https://rollupjs.org/plugin-development/#synthetic-named-exports
|
|
2050
|
+
*/
|
|
2051
|
+
code = code.replace("export default exportModule", "export const __moduleExports = exportModule;\nexport default exportModule.__esModule ? exportModule.default : exportModule");
|
|
2052
|
+
return {
|
|
2053
|
+
code,
|
|
2054
|
+
syntheticNamedExports: "__moduleExports"
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
},
|
|
2058
|
+
generateBundle(_, bundle) {
|
|
2059
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
2060
|
+
if (chunk.type !== "chunk") continue;
|
|
2061
|
+
if (fileName.includes(LOAD_SHARE_TAG)) continue;
|
|
2062
|
+
let code = chunk.code;
|
|
2063
|
+
let m;
|
|
2064
|
+
const importedFromLoadShare = /* @__PURE__ */ new Set();
|
|
2065
|
+
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["'][^"']*__loadShare__[^"']*["']/g;
|
|
2066
|
+
while ((m = importRegex.exec(code)) !== null) for (const spec of m[1].split(",")) {
|
|
2067
|
+
const parts = spec.trim().split(/\s+as\s+/);
|
|
2068
|
+
const local = (parts[1] || parts[0]).trim();
|
|
2069
|
+
if (local) importedFromLoadShare.add(local);
|
|
2070
|
+
}
|
|
2071
|
+
const allInits = [];
|
|
2072
|
+
for (const v of importedFromLoadShare) if (new RegExp("\\(" + v + "\\(\\)\\s*,\\s*\\w+\\(\\w+\\)\\)").test(code)) allInits.push(v);
|
|
2073
|
+
if (allInits.length === 0) continue;
|
|
2074
|
+
const awaits = allInits.map((v) => `await ${v}();`).join("");
|
|
2075
|
+
const lastFromRegex = /\bfrom\s*["'][^"']*["']\s*;?/g;
|
|
2076
|
+
let lastFromEnd = -1;
|
|
2077
|
+
while ((m = lastFromRegex.exec(code)) !== null) lastFromEnd = m.index + m[0].length;
|
|
2078
|
+
if (lastFromEnd !== -1) {
|
|
2079
|
+
chunk.code = code.slice(0, lastFromEnd) + awaits + code.slice(lastFromEnd);
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
const exportIdx = code.search(/\bexport\s*[{d]/);
|
|
2083
|
+
if (exportIdx !== -1) {
|
|
2084
|
+
chunk.code = code.slice(0, exportIdx) + awaits + code.slice(exportIdx);
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
const proxyChunks = /* @__PURE__ */ new Map();
|
|
2089
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
2090
|
+
if (chunk.type !== "chunk") continue;
|
|
2091
|
+
if (fileName.includes(LOAD_SHARE_TAG) && fileName.includes("commonjs-proxy")) proxyChunks.set(fileName, {
|
|
2092
|
+
code: chunk.code,
|
|
2093
|
+
fileName
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
if (proxyChunks.size === 0) return;
|
|
2097
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
2098
|
+
if (chunk.type !== "chunk") continue;
|
|
2099
|
+
if (fileName.includes(LOAD_SHARE_TAG)) continue;
|
|
2100
|
+
let code = chunk.code;
|
|
2101
|
+
let modified = false;
|
|
2102
|
+
for (const [proxyFileName, proxyInfo] of proxyChunks) {
|
|
2103
|
+
const proxyBaseName = proxyFileName.replace(/^.*\//, "").replace(/\.js$/, "").replace(/-[A-Za-z0-9_-]+$/, "");
|
|
2104
|
+
const importMatch = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']([^"']*${proxyBaseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"']*)["']\\s*;?`).exec(code);
|
|
2105
|
+
if (!importMatch) continue;
|
|
2106
|
+
const fullImport = importMatch[0];
|
|
2107
|
+
const bindings = importMatch[1].split(",").map((s) => {
|
|
2108
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
2109
|
+
return {
|
|
2110
|
+
imported: parts[0].trim(),
|
|
2111
|
+
local: (parts[1] || parts[0]).trim()
|
|
2112
|
+
};
|
|
2113
|
+
});
|
|
2114
|
+
const proxyCode = proxyInfo.code;
|
|
2115
|
+
const exportMapMatch = proxyCode.match(/export\s*\{([^}]+)\}/);
|
|
2116
|
+
if (!exportMapMatch) continue;
|
|
2117
|
+
const exportMap = {};
|
|
2118
|
+
for (const entry of exportMapMatch[1].split(",")) {
|
|
2119
|
+
const parts = entry.trim().split(/\s+as\s+/);
|
|
2120
|
+
if (parts.length === 2) exportMap[parts[1].trim()] = parts[0].trim();
|
|
2121
|
+
}
|
|
2122
|
+
const inlineable = [];
|
|
2123
|
+
const nonInlineable = [];
|
|
2124
|
+
for (const b of bindings) {
|
|
2125
|
+
const proxyLocal = exportMap[b.imported];
|
|
2126
|
+
if (!proxyLocal) {
|
|
2127
|
+
nonInlineable.push(b);
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
const funcRe = new RegExp(`function\\s+${proxyLocal}\\s*\\([^)]*\\)\\s*\\{`);
|
|
2131
|
+
if (funcRe.test(proxyCode)) {
|
|
2132
|
+
const funcStart = proxyCode.search(funcRe);
|
|
2133
|
+
let depth = 0;
|
|
2134
|
+
let funcEnd = funcStart;
|
|
2135
|
+
for (let i = proxyCode.indexOf("{", funcStart); i < proxyCode.length; i++) if (proxyCode[i] === "{") depth++;
|
|
2136
|
+
else if (proxyCode[i] === "}") {
|
|
2137
|
+
depth--;
|
|
2138
|
+
if (depth === 0) {
|
|
2139
|
+
funcEnd = i + 1;
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
const renamedFunc = proxyCode.slice(funcStart, funcEnd).replace(new RegExp(`function\\s+${proxyLocal}\\s*\\(`), `function ${b.local}(`);
|
|
2144
|
+
inlineable.push({
|
|
2145
|
+
local: b.local,
|
|
2146
|
+
funcBody: renamedFunc
|
|
2147
|
+
});
|
|
2148
|
+
} else nonInlineable.push(b);
|
|
2149
|
+
}
|
|
2150
|
+
if (inlineable.length === 0) continue;
|
|
2151
|
+
let replacement = "";
|
|
2152
|
+
if (nonInlineable.length > 0) replacement = `import{${nonInlineable.map((b) => b.imported === b.local ? b.imported : `${b.imported} as ${b.local}`).join(",")}}from"${importMatch[2]}";`;
|
|
2153
|
+
replacement += inlineable.map((f) => f.funcBody).join("");
|
|
2154
|
+
code = code.replace(fullImport, replacement);
|
|
2155
|
+
modified = true;
|
|
2156
|
+
}
|
|
2157
|
+
if (modified) chunk.code = code;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
name: "module-federation-dev-await-shared-init",
|
|
2163
|
+
apply: "serve",
|
|
2164
|
+
enforce: "post",
|
|
2165
|
+
transform(code, id) {
|
|
2166
|
+
if (!id.includes(".vite/deps/")) return;
|
|
2167
|
+
const initPattern = /\b(init_\w+__loadShare__\w+)\b/g;
|
|
2168
|
+
const initFns = /* @__PURE__ */ new Set();
|
|
2169
|
+
let match;
|
|
2170
|
+
while ((match = initPattern.exec(code)) !== null) initFns.add(match[1]);
|
|
2171
|
+
if (initFns.size === 0) return;
|
|
2172
|
+
if (![...initFns].some((fn) => {
|
|
2173
|
+
return code.includes(`${fn}(),`) || code.includes(`${fn}()`);
|
|
2174
|
+
})) return;
|
|
2175
|
+
if (/await\s+init_\w+__loadShare__/.test(code)) return;
|
|
2176
|
+
if (code.includes("__esmMin")) return;
|
|
2177
|
+
const awaits = [...initFns].map((fn) => `await ${fn}();`).join("\n");
|
|
2178
|
+
const topLevelImportRe = /^import\s/gm;
|
|
2179
|
+
let lastImportIdx = -1;
|
|
2180
|
+
let importMatch;
|
|
2181
|
+
while ((importMatch = topLevelImportRe.exec(code)) !== null) lastImportIdx = importMatch.index;
|
|
2182
|
+
if (lastImportIdx === -1) return;
|
|
2183
|
+
const lineEnd = code.indexOf("\n", lastImportIdx);
|
|
2184
|
+
return code.slice(0, lineEnd + 1) + awaits + "\n" + code.slice(lineEnd + 1);
|
|
2185
|
+
}
|
|
2186
|
+
},
|
|
2187
|
+
PluginDevProxyModuleTopLevelAwait(),
|
|
2188
|
+
{
|
|
2189
|
+
name: "module-federation-vite",
|
|
2190
|
+
enforce: "post",
|
|
2191
|
+
_options: options,
|
|
2192
|
+
config(config, { command: _command }) {
|
|
2193
|
+
const isRolldown = !!this?.meta?.rolldownVersion;
|
|
2194
|
+
let implementation = options.implementation;
|
|
2195
|
+
if (isRolldown) implementation = implementation.replace(/\.cjs\.cjs$/, ".esm.js");
|
|
2196
|
+
config.resolve.alias.push({
|
|
2197
|
+
find: "@module-federation/runtime",
|
|
2198
|
+
replacement: implementation
|
|
2199
|
+
});
|
|
2200
|
+
config.build = defu(config.build || {}, { commonjsOptions: { strictRequires: "auto" } });
|
|
2201
|
+
const virtualDir = options.virtualModuleDir || "__mf__virtual";
|
|
2202
|
+
config.optimizeDeps?.include?.push("@module-federation/runtime");
|
|
2203
|
+
config.optimizeDeps?.include?.push(virtualDir);
|
|
2204
|
+
if (isRolldown) config.build = defu(config.build || {}, { target: "esnext" });
|
|
2205
|
+
else {
|
|
2206
|
+
config.optimizeDeps?.needsInterop?.push(virtualDir);
|
|
2207
|
+
config.optimizeDeps?.needsInterop?.push(getLocalSharedImportMapPath());
|
|
2208
|
+
}
|
|
2209
|
+
const resolvedTarget = options.target ?? (config.build?.ssr ? "node" : "web");
|
|
2210
|
+
if (!config.define) config.define = {};
|
|
2211
|
+
if (!("ENV_TARGET" in config.define)) config.define["ENV_TARGET"] = JSON.stringify(resolvedTarget);
|
|
2212
|
+
if (options.target && "ENV_TARGET" in config.define && config.define["ENV_TARGET"] !== JSON.stringify(options.target)) console.warn(`[module-federation] ENV_TARGET define (${config.define["ENV_TARGET"]}) differs from target option ("${options.target}"). ENV_TARGET will not be overridden.`);
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
...Manifest(),
|
|
2216
|
+
...VarRemoteEntry(),
|
|
2217
|
+
...Object.keys(options.exposes).length > 0 ? [{
|
|
2218
|
+
name: "module-federation-fix-preload",
|
|
2219
|
+
enforce: "post",
|
|
2220
|
+
apply: "build",
|
|
2221
|
+
generateBundle(_, bundle) {
|
|
2222
|
+
for (const chunk of Object.values(bundle)) {
|
|
2223
|
+
if (chunk.type !== "chunk") continue;
|
|
2224
|
+
if (!chunk.code.includes("modulepreload")) continue;
|
|
2225
|
+
const replacement = "=function($1){return new URL(\"../\"+$1,import.meta.url).href}";
|
|
2226
|
+
const replaced = chunk.code.replace(/=\(?(\w+)(?:,\w+)?\)?\s*=>\s*["'`][^"'`]*["'`]\s*\+\s*\1/, replacement);
|
|
2227
|
+
if (replaced !== chunk.code) {
|
|
2228
|
+
chunk.code = replaced;
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return\s*["'`][^"'`]*["'`]\s*\+\s*\1\s*\}/, replacement);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}] : []
|
|
2235
|
+
];
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
//#endregion
|
|
2239
|
+
export { federation };
|