@module-federation/vite 1.13.3 → 1.13.5
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 +12 -0
- package/lib/index.cjs +311 -17
- package/lib/index.mjs +311 -17
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ This plugin makes Module Federation work together with [Vite](https://vitejs.dev
|
|
|
23
23
|
|
|
24
24
|
### [More examples here](https://github.com/module-federation/vite/tree/main/examples)<br>
|
|
25
25
|
|
|
26
|
+
Includes a pure runtime host example in [`examples/vite-runtime-register`](./examples/vite-runtime-register).
|
|
27
|
+
|
|
26
28
|
## Try this crazy example with all these bundlers together
|
|
27
29
|
|
|
28
30
|
<img src="./docs/multi-example.png"/>
|
|
@@ -168,6 +170,16 @@ const RemoteMFE = defineAsyncComponent( 👈
|
|
|
168
170
|
</template>
|
|
169
171
|
```
|
|
170
172
|
|
|
173
|
+
## ⚠️ `codeSplitting: false` is not supported
|
|
174
|
+
|
|
175
|
+
Do not set `build.rolldownOptions.output.codeSplitting` to `false` with this plugin — it will be **automatically ignored**.
|
|
176
|
+
Module federation requires chunk splitting to isolate shared dependencies and remote entries into separate chunks.
|
|
177
|
+
|
|
178
|
+
## ⚠️ `manualChunks` is not supported
|
|
179
|
+
|
|
180
|
+
Do not use `build.rollupOptions.output.manualChunks` with this plugin — it will be **automatically ignored**.
|
|
181
|
+
Module federation transforms shared dependency imports with top-level `await`, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.
|
|
182
|
+
|
|
171
183
|
### So far so good 🎉
|
|
172
184
|
|
|
173
185
|
Now you are ready to use Module Federation in Vite!
|
package/lib/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ let _module_federation_dts_plugin = require("@module-federation/dts-plugin");
|
|
|
36
36
|
let _module_federation_dts_plugin_core = require("@module-federation/dts-plugin/core");
|
|
37
37
|
let node_module = require("node:module");
|
|
38
38
|
let url = require("url");
|
|
39
|
+
let es_module_lexer = require("es-module-lexer");
|
|
39
40
|
//#region src/utils/mapCodeToCodeWithSourcemap.ts
|
|
40
41
|
async function mapCodeToCodeWithSourcemap(code) {
|
|
41
42
|
const resolvedCode = await code;
|
|
@@ -151,7 +152,7 @@ function removePathFromNpmPackage(packageString) {
|
|
|
151
152
|
return match ? match[0] : packageString;
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
154
|
-
* Detect whether the current
|
|
155
|
+
* Detect whether the current runtime is Vite 8+ (with rolldown internally) by checking
|
|
155
156
|
* for `meta.rolldownVersion` on the plugin hook context.
|
|
156
157
|
*/
|
|
157
158
|
function getIsRolldown(ctx) {
|
|
@@ -380,15 +381,17 @@ function checkAliasConflicts(options) {
|
|
|
380
381
|
};
|
|
381
382
|
}
|
|
382
383
|
//#endregion
|
|
383
|
-
//#region src/
|
|
384
|
-
/**
|
|
385
|
-
* Solve the problem that dev mode dependency prebunding does not support top-level await syntax
|
|
386
|
-
*/
|
|
384
|
+
//#region src/utils/loadWalk.ts
|
|
387
385
|
let walkPromise = null;
|
|
388
386
|
function loadWalk() {
|
|
389
387
|
walkPromise ||= import("estree-walker").then(({ walk }) => walk);
|
|
390
388
|
return walkPromise;
|
|
391
389
|
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/plugins/pluginDevProxyModuleTopLevelAwait.ts
|
|
392
|
+
/**
|
|
393
|
+
* Solve the problem that dev mode dependency prebunding does not support top-level await syntax
|
|
394
|
+
*/
|
|
392
395
|
function PluginDevProxyModuleTopLevelAwait() {
|
|
393
396
|
const filterFunction = (0, _rollup_pluginutils.createFilter)();
|
|
394
397
|
const processedFlag = "/* already-processed-by-dev-proxy-module-top-level-await */";
|
|
@@ -746,7 +749,7 @@ function inferVersionFromRequiredVersion(requiredVersion) {
|
|
|
746
749
|
}
|
|
747
750
|
function normalizeShareItem(key, shareItem) {
|
|
748
751
|
let version;
|
|
749
|
-
try {
|
|
752
|
+
if (!(typeof shareItem === "object" && shareItem.import === false)) try {
|
|
750
753
|
try {
|
|
751
754
|
version = require(pathe.join(removePathFromNpmPackage(key), "package.json")).version;
|
|
752
755
|
} catch (e1) {
|
|
@@ -1197,7 +1200,7 @@ function generateRemotes(id, command, isRolldown) {
|
|
|
1197
1200
|
const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
|
|
1198
1201
|
const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
1199
1202
|
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
1200
|
-
const exportLine = command === "serve" && useESM ? "export default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
|
|
1203
|
+
const exportLine = command === "serve" && useESM ? "export const __moduleExports = exportModule;\nexport default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
|
|
1201
1204
|
return `
|
|
1202
1205
|
${importLine}
|
|
1203
1206
|
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
|
|
@@ -1386,6 +1389,28 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1386
1389
|
const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
|
|
1387
1390
|
const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
1388
1391
|
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
1392
|
+
if (shareItem.shareConfig.import === false) {
|
|
1393
|
+
const namedExports = useESM ? getPackageNamedExports(pkg) : [];
|
|
1394
|
+
let exportLine;
|
|
1395
|
+
if (useESM && namedExports.length > 0) exportLine = `export default exportModule.default ?? exportModule;\n ${`const { ${namedExports.map((name, i) => `${name}: __mf_${i}`).join(", ")} } = exportModule;`}\n ${`export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`}`;
|
|
1396
|
+
else {
|
|
1397
|
+
if (useESM) mfWarn(`Shared dependency "${pkg}" has import: false but is not installed locally.\n Named imports (e.g. import { ... } from '${pkg}') will not work in production builds.\n Install it as a devDependency to enable named export detection.`);
|
|
1398
|
+
exportLine = useESM ? "export default exportModule.default ?? exportModule" : "module.exports = exportModule";
|
|
1399
|
+
}
|
|
1400
|
+
loadShareCacheMap[pkg].writeSync(`
|
|
1401
|
+
${importLine}
|
|
1402
|
+
const res = initPromise.then(runtime => runtime.loadShare(${escapeGeneratedStringLiteral(pkg)}, {
|
|
1403
|
+
customShareInfo: {shareConfig:{
|
|
1404
|
+
singleton: ${shareItem.shareConfig.singleton},
|
|
1405
|
+
strictVersion: ${shareItem.shareConfig.strictVersion},
|
|
1406
|
+
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
|
|
1407
|
+
}}
|
|
1408
|
+
}))
|
|
1409
|
+
const exportModule = ${awaitOrPlaceholder}res.then((factory) => (typeof factory === "function" ? factory() : factory))
|
|
1410
|
+
${exportLine}
|
|
1411
|
+
`, true);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1389
1414
|
const useSsrProviderFallback = hasPackageDependency("vinext") && command === "build" && pkg === "react";
|
|
1390
1415
|
const concreteSharedImportSource = getConcreteSharedImportSource(pkg, shareItem);
|
|
1391
1416
|
const sharedImportSource = concreteSharedImportSource || getPreBuildLibImportId(pkg);
|
|
@@ -1533,6 +1558,14 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1533
1558
|
];
|
|
1534
1559
|
});
|
|
1535
1560
|
return `
|
|
1561
|
+
// Shim Vue HMR runtime for dev-compiled components loaded by a non-Vite host.
|
|
1562
|
+
// When a remote is served by a Vite dev server, Vue's SFC compiler injects HMR
|
|
1563
|
+
// hooks that reference __VUE_HMR_RUNTIME__. This global only exists on pages
|
|
1564
|
+
// served by Vite's client runtime. When a production host loads the remote,
|
|
1565
|
+
// the HMR calls would throw. This no-op shim prevents that.
|
|
1566
|
+
if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
1567
|
+
globalThis.__VUE_HMR_RUNTIME__ = { createRecord() {}, rerender() {}, reload() {} };
|
|
1568
|
+
}
|
|
1536
1569
|
import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
|
|
1537
1570
|
${pluginImportNames.map((item) => item[1]).join("\n")}
|
|
1538
1571
|
${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
|
|
@@ -2203,6 +2236,265 @@ function pluginProxyRemotes_default(options) {
|
|
|
2203
2236
|
};
|
|
2204
2237
|
}
|
|
2205
2238
|
//#endregion
|
|
2239
|
+
//#region src/plugins/pluginRemoteNamedExports.ts
|
|
2240
|
+
/**
|
|
2241
|
+
* Transforms consumer-side imports of remote modules so that named exports
|
|
2242
|
+
* are accessible even when the bundler does not support syntheticNamedExports
|
|
2243
|
+
* (Rolldown / Vite 8+).
|
|
2244
|
+
*
|
|
2245
|
+
* The remote proxy module exports:
|
|
2246
|
+
* export const __moduleExports = exportModule; // full namespace
|
|
2247
|
+
* export default exportModule.default ?? exportModule; // unwrapped default
|
|
2248
|
+
*
|
|
2249
|
+
* This plugin rewrites consumer code:
|
|
2250
|
+
* import { foo } from "remote/xxx"
|
|
2251
|
+
* → import { __moduleExports as __mf_ns_0 } from "remote/xxx"; const { foo } = __mf_ns_0;
|
|
2252
|
+
*
|
|
2253
|
+
* import("remote/xxx")
|
|
2254
|
+
* → import("remote/xxx").then(…) // spreads __moduleExports into namespace
|
|
2255
|
+
*
|
|
2256
|
+
* NOTE: `export * from "remote/xxx"` is not supported — Rolldown cannot
|
|
2257
|
+
* statically resolve the set of exported names from a federated remote at
|
|
2258
|
+
* build time. Use explicit named re-exports instead.
|
|
2259
|
+
*/
|
|
2260
|
+
const JS_EXTENSIONS_RE = /\.(?:[mc]?[jt]sx?|vue|svelte)(?:\?|$)/;
|
|
2261
|
+
function wrapDynamicImport(original) {
|
|
2262
|
+
return `${original}.then(function(__mf_m__) {\n if (!__mf_m__ || !__mf_m__.__moduleExports) return __mf_m__;\n var __mf_ns__ = Object.create(null);\n Object.defineProperty(__mf_ns__, Symbol.toStringTag, { value: "Module" });\n var __mf_e__ = __mf_m__.__moduleExports;\n Object.keys(__mf_e__).forEach(function(k) { if (k !== "__esModule") __mf_ns__[k] = __mf_e__[k] });\n if ("default" in __mf_m__) __mf_ns__.default = __mf_m__.default;\n return __mf_ns__;\n})`;
|
|
2263
|
+
}
|
|
2264
|
+
function applyRewrites(code, imports, id) {
|
|
2265
|
+
if (imports.length === 0) return;
|
|
2266
|
+
const ms = new magic_string.default(code);
|
|
2267
|
+
let changed = false;
|
|
2268
|
+
let counter = 0;
|
|
2269
|
+
for (const imp of imports) switch (imp.kind) {
|
|
2270
|
+
case "static": {
|
|
2271
|
+
const src = JSON.stringify(imp.source);
|
|
2272
|
+
if (imp.namespaceLocal && !imp.defaultLocal && imp.named.length === 0) ms.overwrite(imp.start, imp.end, `import { __moduleExports as ${imp.namespaceLocal} } from ${src};`);
|
|
2273
|
+
else {
|
|
2274
|
+
const nsId = `__mf_ns_${counter++}`;
|
|
2275
|
+
const importParts = [];
|
|
2276
|
+
if (imp.defaultLocal) importParts.push(`default as ${imp.defaultLocal}`);
|
|
2277
|
+
importParts.push(`__moduleExports as ${nsId}`);
|
|
2278
|
+
const destructParts = imp.named.map((s) => s.imported === s.local ? s.local : `${s.imported}: ${s.local}`);
|
|
2279
|
+
let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
|
|
2280
|
+
if (destructParts.length > 0) rewrite += `\nconst { ${destructParts.join(", ")} } = ${nsId};`;
|
|
2281
|
+
ms.overwrite(imp.start, imp.end, rewrite);
|
|
2282
|
+
}
|
|
2283
|
+
changed = true;
|
|
2284
|
+
break;
|
|
2285
|
+
}
|
|
2286
|
+
case "reexport": {
|
|
2287
|
+
const src = JSON.stringify(imp.source);
|
|
2288
|
+
const nsId = `__mf_ns_${counter++}`;
|
|
2289
|
+
const vars = imp.specifiers.map((s) => {
|
|
2290
|
+
const tmp = `__mf_re_${counter++}`;
|
|
2291
|
+
return {
|
|
2292
|
+
...s,
|
|
2293
|
+
tmp
|
|
2294
|
+
};
|
|
2295
|
+
});
|
|
2296
|
+
const importLine = `import { __moduleExports as ${nsId} } from ${src};`;
|
|
2297
|
+
const varLines = vars.map((v) => `const ${v.tmp} = ${nsId}[${JSON.stringify(v.local)}];`).join("\n");
|
|
2298
|
+
const exportLine = `export { ${vars.map((v) => `${v.tmp} as ${v.exported}`).join(", ")} };`;
|
|
2299
|
+
ms.overwrite(imp.start, imp.end, `${importLine}\n${varLines}\n${exportLine}`);
|
|
2300
|
+
changed = true;
|
|
2301
|
+
break;
|
|
2302
|
+
}
|
|
2303
|
+
case "export-all":
|
|
2304
|
+
console.warn(`[module-federation] "export * from '${imp.source}'" is not supported with Rolldown — use explicit named re-exports instead. (${id})`);
|
|
2305
|
+
break;
|
|
2306
|
+
case "dynamic":
|
|
2307
|
+
ms.overwrite(imp.start, imp.end, wrapDynamicImport(imp.originalText));
|
|
2308
|
+
changed = true;
|
|
2309
|
+
break;
|
|
2310
|
+
}
|
|
2311
|
+
if (!changed) return;
|
|
2312
|
+
return {
|
|
2313
|
+
code: ms.toString(),
|
|
2314
|
+
map: ms.generateMap({ hires: true })
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
async function collectFromAST(ast, code, isRemoteImport) {
|
|
2318
|
+
const walk = await loadWalk();
|
|
2319
|
+
const result = [];
|
|
2320
|
+
walk(ast, { enter(node) {
|
|
2321
|
+
if (node.type === "ImportDeclaration" && node.source?.value) {
|
|
2322
|
+
if (!isRemoteImport(node.source.value)) return;
|
|
2323
|
+
const specifiers = node.specifiers || [];
|
|
2324
|
+
const named = specifiers.filter((s) => s.type === "ImportSpecifier" && s.importKind !== "type").map((s) => ({
|
|
2325
|
+
imported: s.imported.name ?? s.imported.value,
|
|
2326
|
+
local: s.local.name
|
|
2327
|
+
}));
|
|
2328
|
+
const defaultSpec = specifiers.find((s) => s.type === "ImportDefaultSpecifier");
|
|
2329
|
+
const nsSpec = specifiers.find((s) => s.type === "ImportNamespaceSpecifier");
|
|
2330
|
+
if (named.length === 0 && !nsSpec) return;
|
|
2331
|
+
result.push({
|
|
2332
|
+
kind: "static",
|
|
2333
|
+
source: node.source.value,
|
|
2334
|
+
start: node.start,
|
|
2335
|
+
end: node.end,
|
|
2336
|
+
named,
|
|
2337
|
+
defaultLocal: defaultSpec?.local.name,
|
|
2338
|
+
namespaceLocal: nsSpec?.local.name
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
if (node.type === "ExportNamedDeclaration" && node.source?.value && isRemoteImport(node.source.value)) {
|
|
2342
|
+
const specifiers = (node.specifiers || []).filter((s) => s.exportKind !== "type").map((s) => ({
|
|
2343
|
+
local: s.local.name ?? s.local.value,
|
|
2344
|
+
exported: s.exported.name ?? s.exported.value
|
|
2345
|
+
}));
|
|
2346
|
+
if (specifiers.length === 0) return;
|
|
2347
|
+
result.push({
|
|
2348
|
+
kind: "reexport",
|
|
2349
|
+
source: node.source.value,
|
|
2350
|
+
start: node.start,
|
|
2351
|
+
end: node.end,
|
|
2352
|
+
specifiers
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
if (node.type === "ExportAllDeclaration" && node.source?.value && isRemoteImport(node.source.value)) {
|
|
2356
|
+
this.skip();
|
|
2357
|
+
result.push({
|
|
2358
|
+
kind: "export-all",
|
|
2359
|
+
source: node.source.value,
|
|
2360
|
+
start: node.start,
|
|
2361
|
+
end: node.end
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
if (node.type === "ImportExpression") {
|
|
2365
|
+
const source = node.source;
|
|
2366
|
+
if (source.type !== "Literal" && source.type !== "StringLiteral" && source.type !== "TemplateLiteral") return;
|
|
2367
|
+
const value = source.type === "TemplateLiteral" ? source.quasis?.length === 1 ? source.quasis[0].value?.cooked : void 0 : source.value;
|
|
2368
|
+
if (!value || !isRemoteImport(value)) return;
|
|
2369
|
+
result.push({
|
|
2370
|
+
kind: "dynamic",
|
|
2371
|
+
start: node.start,
|
|
2372
|
+
end: node.end,
|
|
2373
|
+
originalText: code.slice(node.start, node.end)
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
} });
|
|
2377
|
+
return result;
|
|
2378
|
+
}
|
|
2379
|
+
async function collectFromEsLexer(code, isRemoteImport) {
|
|
2380
|
+
await es_module_lexer.init;
|
|
2381
|
+
let imports;
|
|
2382
|
+
try {
|
|
2383
|
+
[imports] = (0, es_module_lexer.parse)(code);
|
|
2384
|
+
} catch {
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
const result = [];
|
|
2388
|
+
for (const imp of imports) {
|
|
2389
|
+
if (imp.d === -2) continue;
|
|
2390
|
+
if (!imp.n || !isRemoteImport(imp.n)) continue;
|
|
2391
|
+
const stmtText = code.slice(imp.ss, imp.se);
|
|
2392
|
+
if (imp.d >= 0) {
|
|
2393
|
+
result.push({
|
|
2394
|
+
kind: "dynamic",
|
|
2395
|
+
start: imp.ss,
|
|
2396
|
+
end: imp.se,
|
|
2397
|
+
originalText: stmtText
|
|
2398
|
+
});
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
if (/^\s*export\s*\*\s/.test(stmtText)) {
|
|
2402
|
+
result.push({
|
|
2403
|
+
kind: "export-all",
|
|
2404
|
+
source: imp.n,
|
|
2405
|
+
start: imp.ss,
|
|
2406
|
+
end: imp.se
|
|
2407
|
+
});
|
|
2408
|
+
continue;
|
|
2409
|
+
}
|
|
2410
|
+
if (/^\s*export\s/.test(stmtText)) {
|
|
2411
|
+
const braceMatch = stmtText.match(/\{([^}]*)\}/);
|
|
2412
|
+
if (!braceMatch) continue;
|
|
2413
|
+
const specs = braceMatch[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2414
|
+
if (specs.length === 0) continue;
|
|
2415
|
+
const specifiers = specs.map((s) => {
|
|
2416
|
+
const asMatch = s.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
2417
|
+
return {
|
|
2418
|
+
local: asMatch ? asMatch[1] : s,
|
|
2419
|
+
exported: asMatch ? asMatch[2] : s
|
|
2420
|
+
};
|
|
2421
|
+
});
|
|
2422
|
+
result.push({
|
|
2423
|
+
kind: "reexport",
|
|
2424
|
+
source: imp.n,
|
|
2425
|
+
start: imp.ss,
|
|
2426
|
+
end: imp.se,
|
|
2427
|
+
specifiers
|
|
2428
|
+
});
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
const importMatch = stmtText.match(/^import\s+([\s\S]*?)\s+from\s/);
|
|
2432
|
+
if (!importMatch) continue;
|
|
2433
|
+
const specifiersPart = importMatch[1].trim();
|
|
2434
|
+
if (/^type\s/.test(specifiersPart)) continue;
|
|
2435
|
+
const nsMatch = specifiersPart.match(/^\*\s+as\s+(\w+)$/);
|
|
2436
|
+
if (nsMatch) {
|
|
2437
|
+
result.push({
|
|
2438
|
+
kind: "static",
|
|
2439
|
+
source: imp.n,
|
|
2440
|
+
start: imp.ss,
|
|
2441
|
+
end: imp.se,
|
|
2442
|
+
named: [],
|
|
2443
|
+
namespaceLocal: nsMatch[1]
|
|
2444
|
+
});
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
const braceMatch = specifiersPart.match(/\{([^}]*)\}/);
|
|
2448
|
+
if (!braceMatch) continue;
|
|
2449
|
+
const namedSpecifiers = braceMatch[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("type "));
|
|
2450
|
+
if (namedSpecifiers.length === 0) continue;
|
|
2451
|
+
const defaultMatch = specifiersPart.match(/^(\w+)\s*,/);
|
|
2452
|
+
const named = namedSpecifiers.map((s) => {
|
|
2453
|
+
const asMatch = s.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
2454
|
+
return {
|
|
2455
|
+
imported: asMatch ? asMatch[1] : s,
|
|
2456
|
+
local: asMatch ? asMatch[2] : s
|
|
2457
|
+
};
|
|
2458
|
+
});
|
|
2459
|
+
result.push({
|
|
2460
|
+
kind: "static",
|
|
2461
|
+
source: imp.n,
|
|
2462
|
+
start: imp.ss,
|
|
2463
|
+
end: imp.se,
|
|
2464
|
+
named,
|
|
2465
|
+
defaultLocal: defaultMatch?.[1]
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
return result;
|
|
2469
|
+
}
|
|
2470
|
+
function pluginRemoteNamedExports(options) {
|
|
2471
|
+
const remoteNames = Object.keys(options.remotes);
|
|
2472
|
+
let rolldown;
|
|
2473
|
+
function isRemoteImport(source) {
|
|
2474
|
+
return remoteNames.some((name) => source === name || source.startsWith(name + "/"));
|
|
2475
|
+
}
|
|
2476
|
+
return {
|
|
2477
|
+
name: "module-federation-remote-named-exports",
|
|
2478
|
+
enforce: "pre",
|
|
2479
|
+
async transform(code, id) {
|
|
2480
|
+
rolldown ??= getIsRolldown(this);
|
|
2481
|
+
if (!rolldown) return;
|
|
2482
|
+
if (remoteNames.length === 0) return;
|
|
2483
|
+
if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
|
|
2484
|
+
if (!JS_EXTENSIONS_RE.test(id)) return;
|
|
2485
|
+
if (!remoteNames.some((name) => code.includes(name))) return;
|
|
2486
|
+
let imports;
|
|
2487
|
+
try {
|
|
2488
|
+
imports = await collectFromAST(this.parse(code), code, isRemoteImport);
|
|
2489
|
+
} catch {
|
|
2490
|
+
imports = await collectFromEsLexer(code, isRemoteImport);
|
|
2491
|
+
}
|
|
2492
|
+
if (!imports) return;
|
|
2493
|
+
return applyRewrites(code, imports, id);
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
//#endregion
|
|
2206
2498
|
//#region src/utils/PromiseStore.ts
|
|
2207
2499
|
/**
|
|
2208
2500
|
* example:
|
|
@@ -2278,7 +2570,7 @@ function proxySharedModule(options) {
|
|
|
2278
2570
|
if (key.endsWith("/") && source !== key.slice(0, -1)) return;
|
|
2279
2571
|
const loadSharePath = getLoadShareModulePath(source, isRolldown, command);
|
|
2280
2572
|
writeLoadShareModule(source, shared[key], command, isRolldown);
|
|
2281
|
-
writePreBuildLibPath(source, shared[key]);
|
|
2573
|
+
if (shared[key].shareConfig.import !== false) writePreBuildLibPath(source, shared[key]);
|
|
2282
2574
|
addUsedShares(source);
|
|
2283
2575
|
writeLocalSharedImportMap();
|
|
2284
2576
|
return this.resolve(loadSharePath, importer);
|
|
@@ -2309,7 +2601,7 @@ function proxySharedModule(options) {
|
|
|
2309
2601
|
},
|
|
2310
2602
|
configResolved(config) {
|
|
2311
2603
|
_config = config;
|
|
2312
|
-
const isRolldown =
|
|
2604
|
+
const isRolldown = getIsRolldown(this);
|
|
2313
2605
|
Object.keys(shared).forEach((key) => {
|
|
2314
2606
|
if (key.endsWith("/")) return;
|
|
2315
2607
|
if (isVinext && key === "react") {
|
|
@@ -2317,7 +2609,7 @@ function proxySharedModule(options) {
|
|
|
2317
2609
|
return;
|
|
2318
2610
|
}
|
|
2319
2611
|
writeLoadShareModule(key, shared[key], _command, isRolldown);
|
|
2320
|
-
writePreBuildLibPath(key, shared[key]);
|
|
2612
|
+
if (shared[key].shareConfig.import !== false) writePreBuildLibPath(key, shared[key]);
|
|
2321
2613
|
addUsedShares(key);
|
|
2322
2614
|
});
|
|
2323
2615
|
writeLocalSharedImportMap();
|
|
@@ -2545,9 +2837,9 @@ function createEarlyVirtualModulesPlugin(options) {
|
|
|
2545
2837
|
}
|
|
2546
2838
|
getLoadShareModulePath(key, isRolldown);
|
|
2547
2839
|
writeLoadShareModule(key, shareItem, _command, isRolldown);
|
|
2548
|
-
writePreBuildLibPath(key, shareItem);
|
|
2840
|
+
if (shareItem.shareConfig?.import !== false) writePreBuildLibPath(key, shareItem);
|
|
2549
2841
|
addUsedShares(key);
|
|
2550
|
-
if (_command === "serve") {
|
|
2842
|
+
if (_command === "serve" && shareItem.shareConfig?.import !== false) {
|
|
2551
2843
|
if (!isRolldown) config.optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
|
|
2552
2844
|
config.optimizeDeps.include.push(getPreBuildLibImportId(key));
|
|
2553
2845
|
}
|
|
@@ -2620,6 +2912,7 @@ function federation(mfUserOptions) {
|
|
|
2620
2912
|
virtualExposesId
|
|
2621
2913
|
}),
|
|
2622
2914
|
pluginProxyRemotes_default(options),
|
|
2915
|
+
pluginRemoteNamedExports(options),
|
|
2623
2916
|
...pluginModuleParseEnd_default((id) => {
|
|
2624
2917
|
return id.includes(getHostAutoInitImportId()) || id.includes(remoteEntryId) || id.includes(virtualExposesId) || id.includes(getLocalSharedImportMapPath());
|
|
2625
2918
|
}, {
|
|
@@ -2655,19 +2948,19 @@ function federation(mfUserOptions) {
|
|
|
2655
2948
|
warnedAboutCodeSplitting = true;
|
|
2656
2949
|
mfWarn("Ignoring `build.rolldownOptions.output.codeSplitting = false` because module federation requires chunk splitting.");
|
|
2657
2950
|
};
|
|
2951
|
+
let warnedAboutManualChunks = false;
|
|
2658
2952
|
const applyManualChunks = (output) => {
|
|
2659
2953
|
ensureCodeSplitting(output);
|
|
2660
|
-
|
|
2954
|
+
if (output.manualChunks && !warnedAboutManualChunks) {
|
|
2955
|
+
warnedAboutManualChunks = true;
|
|
2956
|
+
mfWarn("Ignoring `build.rollupOptions.output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
|
|
2957
|
+
}
|
|
2661
2958
|
output.manualChunks = function(id) {
|
|
2662
2959
|
if (id.includes(runtimeInitId)) return "runtimeInit";
|
|
2663
2960
|
if (id.includes("__loadShare__")) {
|
|
2664
2961
|
const match = id.match(/([^/\\]+__loadShare__[^/\\]+)/);
|
|
2665
2962
|
return match ? match[1] : "loadShare";
|
|
2666
2963
|
}
|
|
2667
|
-
if (typeof existingManualChunks === "function") return existingManualChunks.apply(this, arguments);
|
|
2668
|
-
if (existingManualChunks && typeof existingManualChunks === "object") {
|
|
2669
|
-
for (const [key, ids] of Object.entries(existingManualChunks)) if (Array.isArray(ids) && ids.some((v) => id.includes(v))) return key;
|
|
2670
|
-
}
|
|
2671
2964
|
};
|
|
2672
2965
|
};
|
|
2673
2966
|
config.build.rollupOptions = config.build.rollupOptions || {};
|
|
@@ -2699,6 +2992,7 @@ function federation(mfUserOptions) {
|
|
|
2699
2992
|
* @see https://rollupjs.org/plugin-development/#synthetic-named-exports
|
|
2700
2993
|
*/
|
|
2701
2994
|
code = code.replace("export default exportModule", "export const __moduleExports = exportModule;\nexport default exportModule.__esModule ? exportModule.default : exportModule");
|
|
2995
|
+
if (getIsRolldown(this)) return { code };
|
|
2702
2996
|
return {
|
|
2703
2997
|
code,
|
|
2704
2998
|
syntheticNamedExports: "__moduleExports"
|
package/lib/index.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { normalizeOptions } from "@module-federation/sdk";
|
|
|
11
11
|
import { consumeTypesAPI, generateTypesAPI, isTSProject, normalizeConsumeTypesOptions, normalizeDtsOptions, normalizeGenerateTypesOptions } from "@module-federation/dts-plugin";
|
|
12
12
|
import { rpc } from "@module-federation/dts-plugin/core";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
|
+
import { init, parse as parse$1 } from "es-module-lexer";
|
|
14
15
|
//#region \0rolldown/runtime.js
|
|
15
16
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
16
17
|
//#endregion
|
|
@@ -129,7 +130,7 @@ function removePathFromNpmPackage(packageString) {
|
|
|
129
130
|
return match ? match[0] : packageString;
|
|
130
131
|
}
|
|
131
132
|
/**
|
|
132
|
-
* Detect whether the current
|
|
133
|
+
* Detect whether the current runtime is Vite 8+ (with rolldown internally) by checking
|
|
133
134
|
* for `meta.rolldownVersion` on the plugin hook context.
|
|
134
135
|
*/
|
|
135
136
|
function getIsRolldown(ctx) {
|
|
@@ -358,15 +359,17 @@ function checkAliasConflicts(options) {
|
|
|
358
359
|
};
|
|
359
360
|
}
|
|
360
361
|
//#endregion
|
|
361
|
-
//#region src/
|
|
362
|
-
/**
|
|
363
|
-
* Solve the problem that dev mode dependency prebunding does not support top-level await syntax
|
|
364
|
-
*/
|
|
362
|
+
//#region src/utils/loadWalk.ts
|
|
365
363
|
let walkPromise = null;
|
|
366
364
|
function loadWalk() {
|
|
367
365
|
walkPromise ||= import("estree-walker").then(({ walk }) => walk);
|
|
368
366
|
return walkPromise;
|
|
369
367
|
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/plugins/pluginDevProxyModuleTopLevelAwait.ts
|
|
370
|
+
/**
|
|
371
|
+
* Solve the problem that dev mode dependency prebunding does not support top-level await syntax
|
|
372
|
+
*/
|
|
370
373
|
function PluginDevProxyModuleTopLevelAwait() {
|
|
371
374
|
const filterFunction = createFilter();
|
|
372
375
|
const processedFlag = "/* already-processed-by-dev-proxy-module-top-level-await */";
|
|
@@ -724,7 +727,7 @@ function inferVersionFromRequiredVersion(requiredVersion) {
|
|
|
724
727
|
}
|
|
725
728
|
function normalizeShareItem(key, shareItem) {
|
|
726
729
|
let version;
|
|
727
|
-
try {
|
|
730
|
+
if (!(typeof shareItem === "object" && shareItem.import === false)) try {
|
|
728
731
|
try {
|
|
729
732
|
version = __require(path$1.join(removePathFromNpmPackage(key), "package.json")).version;
|
|
730
733
|
} catch (e1) {
|
|
@@ -1174,7 +1177,7 @@ function generateRemotes(id, command, isRolldown) {
|
|
|
1174
1177
|
const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
|
|
1175
1178
|
const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
1176
1179
|
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
1177
|
-
const exportLine = command === "serve" && useESM ? "export default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
|
|
1180
|
+
const exportLine = command === "serve" && useESM ? "export const __moduleExports = exportModule;\nexport default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
|
|
1178
1181
|
return `
|
|
1179
1182
|
${importLine}
|
|
1180
1183
|
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
|
|
@@ -1363,6 +1366,28 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1363
1366
|
const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
|
|
1364
1367
|
const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
|
|
1365
1368
|
const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
|
|
1369
|
+
if (shareItem.shareConfig.import === false) {
|
|
1370
|
+
const namedExports = useESM ? getPackageNamedExports(pkg) : [];
|
|
1371
|
+
let exportLine;
|
|
1372
|
+
if (useESM && namedExports.length > 0) exportLine = `export default exportModule.default ?? exportModule;\n ${`const { ${namedExports.map((name, i) => `${name}: __mf_${i}`).join(", ")} } = exportModule;`}\n ${`export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`}`;
|
|
1373
|
+
else {
|
|
1374
|
+
if (useESM) mfWarn(`Shared dependency "${pkg}" has import: false but is not installed locally.\n Named imports (e.g. import { ... } from '${pkg}') will not work in production builds.\n Install it as a devDependency to enable named export detection.`);
|
|
1375
|
+
exportLine = useESM ? "export default exportModule.default ?? exportModule" : "module.exports = exportModule";
|
|
1376
|
+
}
|
|
1377
|
+
loadShareCacheMap[pkg].writeSync(`
|
|
1378
|
+
${importLine}
|
|
1379
|
+
const res = initPromise.then(runtime => runtime.loadShare(${escapeGeneratedStringLiteral(pkg)}, {
|
|
1380
|
+
customShareInfo: {shareConfig:{
|
|
1381
|
+
singleton: ${shareItem.shareConfig.singleton},
|
|
1382
|
+
strictVersion: ${shareItem.shareConfig.strictVersion},
|
|
1383
|
+
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
|
|
1384
|
+
}}
|
|
1385
|
+
}))
|
|
1386
|
+
const exportModule = ${awaitOrPlaceholder}res.then((factory) => (typeof factory === "function" ? factory() : factory))
|
|
1387
|
+
${exportLine}
|
|
1388
|
+
`, true);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1366
1391
|
const useSsrProviderFallback = hasPackageDependency("vinext") && command === "build" && pkg === "react";
|
|
1367
1392
|
const concreteSharedImportSource = getConcreteSharedImportSource(pkg, shareItem);
|
|
1368
1393
|
const sharedImportSource = concreteSharedImportSource || getPreBuildLibImportId(pkg);
|
|
@@ -1510,6 +1535,14 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1510
1535
|
];
|
|
1511
1536
|
});
|
|
1512
1537
|
return `
|
|
1538
|
+
// Shim Vue HMR runtime for dev-compiled components loaded by a non-Vite host.
|
|
1539
|
+
// When a remote is served by a Vite dev server, Vue's SFC compiler injects HMR
|
|
1540
|
+
// hooks that reference __VUE_HMR_RUNTIME__. This global only exists on pages
|
|
1541
|
+
// served by Vite's client runtime. When a production host loads the remote,
|
|
1542
|
+
// the HMR calls would throw. This no-op shim prevents that.
|
|
1543
|
+
if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
1544
|
+
globalThis.__VUE_HMR_RUNTIME__ = { createRecord() {}, rerender() {}, reload() {} };
|
|
1545
|
+
}
|
|
1513
1546
|
import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
|
|
1514
1547
|
${pluginImportNames.map((item) => item[1]).join("\n")}
|
|
1515
1548
|
${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
|
|
@@ -2180,6 +2213,265 @@ function pluginProxyRemotes_default(options) {
|
|
|
2180
2213
|
};
|
|
2181
2214
|
}
|
|
2182
2215
|
//#endregion
|
|
2216
|
+
//#region src/plugins/pluginRemoteNamedExports.ts
|
|
2217
|
+
/**
|
|
2218
|
+
* Transforms consumer-side imports of remote modules so that named exports
|
|
2219
|
+
* are accessible even when the bundler does not support syntheticNamedExports
|
|
2220
|
+
* (Rolldown / Vite 8+).
|
|
2221
|
+
*
|
|
2222
|
+
* The remote proxy module exports:
|
|
2223
|
+
* export const __moduleExports = exportModule; // full namespace
|
|
2224
|
+
* export default exportModule.default ?? exportModule; // unwrapped default
|
|
2225
|
+
*
|
|
2226
|
+
* This plugin rewrites consumer code:
|
|
2227
|
+
* import { foo } from "remote/xxx"
|
|
2228
|
+
* → import { __moduleExports as __mf_ns_0 } from "remote/xxx"; const { foo } = __mf_ns_0;
|
|
2229
|
+
*
|
|
2230
|
+
* import("remote/xxx")
|
|
2231
|
+
* → import("remote/xxx").then(…) // spreads __moduleExports into namespace
|
|
2232
|
+
*
|
|
2233
|
+
* NOTE: `export * from "remote/xxx"` is not supported — Rolldown cannot
|
|
2234
|
+
* statically resolve the set of exported names from a federated remote at
|
|
2235
|
+
* build time. Use explicit named re-exports instead.
|
|
2236
|
+
*/
|
|
2237
|
+
const JS_EXTENSIONS_RE = /\.(?:[mc]?[jt]sx?|vue|svelte)(?:\?|$)/;
|
|
2238
|
+
function wrapDynamicImport(original) {
|
|
2239
|
+
return `${original}.then(function(__mf_m__) {\n if (!__mf_m__ || !__mf_m__.__moduleExports) return __mf_m__;\n var __mf_ns__ = Object.create(null);\n Object.defineProperty(__mf_ns__, Symbol.toStringTag, { value: "Module" });\n var __mf_e__ = __mf_m__.__moduleExports;\n Object.keys(__mf_e__).forEach(function(k) { if (k !== "__esModule") __mf_ns__[k] = __mf_e__[k] });\n if ("default" in __mf_m__) __mf_ns__.default = __mf_m__.default;\n return __mf_ns__;\n})`;
|
|
2240
|
+
}
|
|
2241
|
+
function applyRewrites(code, imports, id) {
|
|
2242
|
+
if (imports.length === 0) return;
|
|
2243
|
+
const ms = new MagicString(code);
|
|
2244
|
+
let changed = false;
|
|
2245
|
+
let counter = 0;
|
|
2246
|
+
for (const imp of imports) switch (imp.kind) {
|
|
2247
|
+
case "static": {
|
|
2248
|
+
const src = JSON.stringify(imp.source);
|
|
2249
|
+
if (imp.namespaceLocal && !imp.defaultLocal && imp.named.length === 0) ms.overwrite(imp.start, imp.end, `import { __moduleExports as ${imp.namespaceLocal} } from ${src};`);
|
|
2250
|
+
else {
|
|
2251
|
+
const nsId = `__mf_ns_${counter++}`;
|
|
2252
|
+
const importParts = [];
|
|
2253
|
+
if (imp.defaultLocal) importParts.push(`default as ${imp.defaultLocal}`);
|
|
2254
|
+
importParts.push(`__moduleExports as ${nsId}`);
|
|
2255
|
+
const destructParts = imp.named.map((s) => s.imported === s.local ? s.local : `${s.imported}: ${s.local}`);
|
|
2256
|
+
let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
|
|
2257
|
+
if (destructParts.length > 0) rewrite += `\nconst { ${destructParts.join(", ")} } = ${nsId};`;
|
|
2258
|
+
ms.overwrite(imp.start, imp.end, rewrite);
|
|
2259
|
+
}
|
|
2260
|
+
changed = true;
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
case "reexport": {
|
|
2264
|
+
const src = JSON.stringify(imp.source);
|
|
2265
|
+
const nsId = `__mf_ns_${counter++}`;
|
|
2266
|
+
const vars = imp.specifiers.map((s) => {
|
|
2267
|
+
const tmp = `__mf_re_${counter++}`;
|
|
2268
|
+
return {
|
|
2269
|
+
...s,
|
|
2270
|
+
tmp
|
|
2271
|
+
};
|
|
2272
|
+
});
|
|
2273
|
+
const importLine = `import { __moduleExports as ${nsId} } from ${src};`;
|
|
2274
|
+
const varLines = vars.map((v) => `const ${v.tmp} = ${nsId}[${JSON.stringify(v.local)}];`).join("\n");
|
|
2275
|
+
const exportLine = `export { ${vars.map((v) => `${v.tmp} as ${v.exported}`).join(", ")} };`;
|
|
2276
|
+
ms.overwrite(imp.start, imp.end, `${importLine}\n${varLines}\n${exportLine}`);
|
|
2277
|
+
changed = true;
|
|
2278
|
+
break;
|
|
2279
|
+
}
|
|
2280
|
+
case "export-all":
|
|
2281
|
+
console.warn(`[module-federation] "export * from '${imp.source}'" is not supported with Rolldown — use explicit named re-exports instead. (${id})`);
|
|
2282
|
+
break;
|
|
2283
|
+
case "dynamic":
|
|
2284
|
+
ms.overwrite(imp.start, imp.end, wrapDynamicImport(imp.originalText));
|
|
2285
|
+
changed = true;
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
if (!changed) return;
|
|
2289
|
+
return {
|
|
2290
|
+
code: ms.toString(),
|
|
2291
|
+
map: ms.generateMap({ hires: true })
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
async function collectFromAST(ast, code, isRemoteImport) {
|
|
2295
|
+
const walk = await loadWalk();
|
|
2296
|
+
const result = [];
|
|
2297
|
+
walk(ast, { enter(node) {
|
|
2298
|
+
if (node.type === "ImportDeclaration" && node.source?.value) {
|
|
2299
|
+
if (!isRemoteImport(node.source.value)) return;
|
|
2300
|
+
const specifiers = node.specifiers || [];
|
|
2301
|
+
const named = specifiers.filter((s) => s.type === "ImportSpecifier" && s.importKind !== "type").map((s) => ({
|
|
2302
|
+
imported: s.imported.name ?? s.imported.value,
|
|
2303
|
+
local: s.local.name
|
|
2304
|
+
}));
|
|
2305
|
+
const defaultSpec = specifiers.find((s) => s.type === "ImportDefaultSpecifier");
|
|
2306
|
+
const nsSpec = specifiers.find((s) => s.type === "ImportNamespaceSpecifier");
|
|
2307
|
+
if (named.length === 0 && !nsSpec) return;
|
|
2308
|
+
result.push({
|
|
2309
|
+
kind: "static",
|
|
2310
|
+
source: node.source.value,
|
|
2311
|
+
start: node.start,
|
|
2312
|
+
end: node.end,
|
|
2313
|
+
named,
|
|
2314
|
+
defaultLocal: defaultSpec?.local.name,
|
|
2315
|
+
namespaceLocal: nsSpec?.local.name
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
if (node.type === "ExportNamedDeclaration" && node.source?.value && isRemoteImport(node.source.value)) {
|
|
2319
|
+
const specifiers = (node.specifiers || []).filter((s) => s.exportKind !== "type").map((s) => ({
|
|
2320
|
+
local: s.local.name ?? s.local.value,
|
|
2321
|
+
exported: s.exported.name ?? s.exported.value
|
|
2322
|
+
}));
|
|
2323
|
+
if (specifiers.length === 0) return;
|
|
2324
|
+
result.push({
|
|
2325
|
+
kind: "reexport",
|
|
2326
|
+
source: node.source.value,
|
|
2327
|
+
start: node.start,
|
|
2328
|
+
end: node.end,
|
|
2329
|
+
specifiers
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
if (node.type === "ExportAllDeclaration" && node.source?.value && isRemoteImport(node.source.value)) {
|
|
2333
|
+
this.skip();
|
|
2334
|
+
result.push({
|
|
2335
|
+
kind: "export-all",
|
|
2336
|
+
source: node.source.value,
|
|
2337
|
+
start: node.start,
|
|
2338
|
+
end: node.end
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
if (node.type === "ImportExpression") {
|
|
2342
|
+
const source = node.source;
|
|
2343
|
+
if (source.type !== "Literal" && source.type !== "StringLiteral" && source.type !== "TemplateLiteral") return;
|
|
2344
|
+
const value = source.type === "TemplateLiteral" ? source.quasis?.length === 1 ? source.quasis[0].value?.cooked : void 0 : source.value;
|
|
2345
|
+
if (!value || !isRemoteImport(value)) return;
|
|
2346
|
+
result.push({
|
|
2347
|
+
kind: "dynamic",
|
|
2348
|
+
start: node.start,
|
|
2349
|
+
end: node.end,
|
|
2350
|
+
originalText: code.slice(node.start, node.end)
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
} });
|
|
2354
|
+
return result;
|
|
2355
|
+
}
|
|
2356
|
+
async function collectFromEsLexer(code, isRemoteImport) {
|
|
2357
|
+
await init;
|
|
2358
|
+
let imports;
|
|
2359
|
+
try {
|
|
2360
|
+
[imports] = parse$1(code);
|
|
2361
|
+
} catch {
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
const result = [];
|
|
2365
|
+
for (const imp of imports) {
|
|
2366
|
+
if (imp.d === -2) continue;
|
|
2367
|
+
if (!imp.n || !isRemoteImport(imp.n)) continue;
|
|
2368
|
+
const stmtText = code.slice(imp.ss, imp.se);
|
|
2369
|
+
if (imp.d >= 0) {
|
|
2370
|
+
result.push({
|
|
2371
|
+
kind: "dynamic",
|
|
2372
|
+
start: imp.ss,
|
|
2373
|
+
end: imp.se,
|
|
2374
|
+
originalText: stmtText
|
|
2375
|
+
});
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
if (/^\s*export\s*\*\s/.test(stmtText)) {
|
|
2379
|
+
result.push({
|
|
2380
|
+
kind: "export-all",
|
|
2381
|
+
source: imp.n,
|
|
2382
|
+
start: imp.ss,
|
|
2383
|
+
end: imp.se
|
|
2384
|
+
});
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
if (/^\s*export\s/.test(stmtText)) {
|
|
2388
|
+
const braceMatch = stmtText.match(/\{([^}]*)\}/);
|
|
2389
|
+
if (!braceMatch) continue;
|
|
2390
|
+
const specs = braceMatch[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2391
|
+
if (specs.length === 0) continue;
|
|
2392
|
+
const specifiers = specs.map((s) => {
|
|
2393
|
+
const asMatch = s.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
2394
|
+
return {
|
|
2395
|
+
local: asMatch ? asMatch[1] : s,
|
|
2396
|
+
exported: asMatch ? asMatch[2] : s
|
|
2397
|
+
};
|
|
2398
|
+
});
|
|
2399
|
+
result.push({
|
|
2400
|
+
kind: "reexport",
|
|
2401
|
+
source: imp.n,
|
|
2402
|
+
start: imp.ss,
|
|
2403
|
+
end: imp.se,
|
|
2404
|
+
specifiers
|
|
2405
|
+
});
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
const importMatch = stmtText.match(/^import\s+([\s\S]*?)\s+from\s/);
|
|
2409
|
+
if (!importMatch) continue;
|
|
2410
|
+
const specifiersPart = importMatch[1].trim();
|
|
2411
|
+
if (/^type\s/.test(specifiersPart)) continue;
|
|
2412
|
+
const nsMatch = specifiersPart.match(/^\*\s+as\s+(\w+)$/);
|
|
2413
|
+
if (nsMatch) {
|
|
2414
|
+
result.push({
|
|
2415
|
+
kind: "static",
|
|
2416
|
+
source: imp.n,
|
|
2417
|
+
start: imp.ss,
|
|
2418
|
+
end: imp.se,
|
|
2419
|
+
named: [],
|
|
2420
|
+
namespaceLocal: nsMatch[1]
|
|
2421
|
+
});
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
const braceMatch = specifiersPart.match(/\{([^}]*)\}/);
|
|
2425
|
+
if (!braceMatch) continue;
|
|
2426
|
+
const namedSpecifiers = braceMatch[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("type "));
|
|
2427
|
+
if (namedSpecifiers.length === 0) continue;
|
|
2428
|
+
const defaultMatch = specifiersPart.match(/^(\w+)\s*,/);
|
|
2429
|
+
const named = namedSpecifiers.map((s) => {
|
|
2430
|
+
const asMatch = s.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
2431
|
+
return {
|
|
2432
|
+
imported: asMatch ? asMatch[1] : s,
|
|
2433
|
+
local: asMatch ? asMatch[2] : s
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
result.push({
|
|
2437
|
+
kind: "static",
|
|
2438
|
+
source: imp.n,
|
|
2439
|
+
start: imp.ss,
|
|
2440
|
+
end: imp.se,
|
|
2441
|
+
named,
|
|
2442
|
+
defaultLocal: defaultMatch?.[1]
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
return result;
|
|
2446
|
+
}
|
|
2447
|
+
function pluginRemoteNamedExports(options) {
|
|
2448
|
+
const remoteNames = Object.keys(options.remotes);
|
|
2449
|
+
let rolldown;
|
|
2450
|
+
function isRemoteImport(source) {
|
|
2451
|
+
return remoteNames.some((name) => source === name || source.startsWith(name + "/"));
|
|
2452
|
+
}
|
|
2453
|
+
return {
|
|
2454
|
+
name: "module-federation-remote-named-exports",
|
|
2455
|
+
enforce: "pre",
|
|
2456
|
+
async transform(code, id) {
|
|
2457
|
+
rolldown ??= getIsRolldown(this);
|
|
2458
|
+
if (!rolldown) return;
|
|
2459
|
+
if (remoteNames.length === 0) return;
|
|
2460
|
+
if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
|
|
2461
|
+
if (!JS_EXTENSIONS_RE.test(id)) return;
|
|
2462
|
+
if (!remoteNames.some((name) => code.includes(name))) return;
|
|
2463
|
+
let imports;
|
|
2464
|
+
try {
|
|
2465
|
+
imports = await collectFromAST(this.parse(code), code, isRemoteImport);
|
|
2466
|
+
} catch {
|
|
2467
|
+
imports = await collectFromEsLexer(code, isRemoteImport);
|
|
2468
|
+
}
|
|
2469
|
+
if (!imports) return;
|
|
2470
|
+
return applyRewrites(code, imports, id);
|
|
2471
|
+
}
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
//#endregion
|
|
2183
2475
|
//#region src/utils/PromiseStore.ts
|
|
2184
2476
|
/**
|
|
2185
2477
|
* example:
|
|
@@ -2255,7 +2547,7 @@ function proxySharedModule(options) {
|
|
|
2255
2547
|
if (key.endsWith("/") && source !== key.slice(0, -1)) return;
|
|
2256
2548
|
const loadSharePath = getLoadShareModulePath(source, isRolldown, command);
|
|
2257
2549
|
writeLoadShareModule(source, shared[key], command, isRolldown);
|
|
2258
|
-
writePreBuildLibPath(source, shared[key]);
|
|
2550
|
+
if (shared[key].shareConfig.import !== false) writePreBuildLibPath(source, shared[key]);
|
|
2259
2551
|
addUsedShares(source);
|
|
2260
2552
|
writeLocalSharedImportMap();
|
|
2261
2553
|
return this.resolve(loadSharePath, importer);
|
|
@@ -2286,7 +2578,7 @@ function proxySharedModule(options) {
|
|
|
2286
2578
|
},
|
|
2287
2579
|
configResolved(config) {
|
|
2288
2580
|
_config = config;
|
|
2289
|
-
const isRolldown =
|
|
2581
|
+
const isRolldown = getIsRolldown(this);
|
|
2290
2582
|
Object.keys(shared).forEach((key) => {
|
|
2291
2583
|
if (key.endsWith("/")) return;
|
|
2292
2584
|
if (isVinext && key === "react") {
|
|
@@ -2294,7 +2586,7 @@ function proxySharedModule(options) {
|
|
|
2294
2586
|
return;
|
|
2295
2587
|
}
|
|
2296
2588
|
writeLoadShareModule(key, shared[key], _command, isRolldown);
|
|
2297
|
-
writePreBuildLibPath(key, shared[key]);
|
|
2589
|
+
if (shared[key].shareConfig.import !== false) writePreBuildLibPath(key, shared[key]);
|
|
2298
2590
|
addUsedShares(key);
|
|
2299
2591
|
});
|
|
2300
2592
|
writeLocalSharedImportMap();
|
|
@@ -2522,9 +2814,9 @@ function createEarlyVirtualModulesPlugin(options) {
|
|
|
2522
2814
|
}
|
|
2523
2815
|
getLoadShareModulePath(key, isRolldown);
|
|
2524
2816
|
writeLoadShareModule(key, shareItem, _command, isRolldown);
|
|
2525
|
-
writePreBuildLibPath(key, shareItem);
|
|
2817
|
+
if (shareItem.shareConfig?.import !== false) writePreBuildLibPath(key, shareItem);
|
|
2526
2818
|
addUsedShares(key);
|
|
2527
|
-
if (_command === "serve") {
|
|
2819
|
+
if (_command === "serve" && shareItem.shareConfig?.import !== false) {
|
|
2528
2820
|
if (!isRolldown) config.optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
|
|
2529
2821
|
config.optimizeDeps.include.push(getPreBuildLibImportId(key));
|
|
2530
2822
|
}
|
|
@@ -2597,6 +2889,7 @@ function federation(mfUserOptions) {
|
|
|
2597
2889
|
virtualExposesId
|
|
2598
2890
|
}),
|
|
2599
2891
|
pluginProxyRemotes_default(options),
|
|
2892
|
+
pluginRemoteNamedExports(options),
|
|
2600
2893
|
...pluginModuleParseEnd_default((id) => {
|
|
2601
2894
|
return id.includes(getHostAutoInitImportId()) || id.includes(remoteEntryId) || id.includes(virtualExposesId) || id.includes(getLocalSharedImportMapPath());
|
|
2602
2895
|
}, {
|
|
@@ -2632,19 +2925,19 @@ function federation(mfUserOptions) {
|
|
|
2632
2925
|
warnedAboutCodeSplitting = true;
|
|
2633
2926
|
mfWarn("Ignoring `build.rolldownOptions.output.codeSplitting = false` because module federation requires chunk splitting.");
|
|
2634
2927
|
};
|
|
2928
|
+
let warnedAboutManualChunks = false;
|
|
2635
2929
|
const applyManualChunks = (output) => {
|
|
2636
2930
|
ensureCodeSplitting(output);
|
|
2637
|
-
|
|
2931
|
+
if (output.manualChunks && !warnedAboutManualChunks) {
|
|
2932
|
+
warnedAboutManualChunks = true;
|
|
2933
|
+
mfWarn("Ignoring `build.rollupOptions.output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
|
|
2934
|
+
}
|
|
2638
2935
|
output.manualChunks = function(id) {
|
|
2639
2936
|
if (id.includes(runtimeInitId)) return "runtimeInit";
|
|
2640
2937
|
if (id.includes("__loadShare__")) {
|
|
2641
2938
|
const match = id.match(/([^/\\]+__loadShare__[^/\\]+)/);
|
|
2642
2939
|
return match ? match[1] : "loadShare";
|
|
2643
2940
|
}
|
|
2644
|
-
if (typeof existingManualChunks === "function") return existingManualChunks.apply(this, arguments);
|
|
2645
|
-
if (existingManualChunks && typeof existingManualChunks === "object") {
|
|
2646
|
-
for (const [key, ids] of Object.entries(existingManualChunks)) if (Array.isArray(ids) && ids.some((v) => id.includes(v))) return key;
|
|
2647
|
-
}
|
|
2648
2941
|
};
|
|
2649
2942
|
};
|
|
2650
2943
|
config.build.rollupOptions = config.build.rollupOptions || {};
|
|
@@ -2676,6 +2969,7 @@ function federation(mfUserOptions) {
|
|
|
2676
2969
|
* @see https://rollupjs.org/plugin-development/#synthetic-named-exports
|
|
2677
2970
|
*/
|
|
2678
2971
|
code = code.replace("export default exportModule", "export const __moduleExports = exportModule;\nexport default exportModule.__esModule ? exportModule.default : exportModule");
|
|
2972
|
+
if (getIsRolldown(this)) return { code };
|
|
2679
2973
|
return {
|
|
2680
2974
|
code,
|
|
2681
2975
|
syntheticNamedExports: "__moduleExports"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@module-federation/vite",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.5",
|
|
4
4
|
"description": "Vite plugin for Module Federation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.cjs",
|
|
@@ -30,10 +30,13 @@
|
|
|
30
30
|
"dev-rv": "pnpm clean && pnpm -filter 'examples-rust-vite*' run dev",
|
|
31
31
|
"preview-rv": "pnpm clean && pnpm -filter 'examples-rust-vite*' run preview",
|
|
32
32
|
"dev-vv": "pnpm clean && pnpm -filter 'examples-vite-vite*' run dev",
|
|
33
|
-
"preview-vv": "pnpm clean && pnpm -filter 'examples-vite-vite*' --parallel run preview",
|
|
33
|
+
"preview-vv": "pnpm clean && pnpm run build:shared-lib && pnpm -filter 'examples-vite-vite*' --parallel run preview",
|
|
34
|
+
"preview-vv:ci": "pnpm run build:shared-lib && pnpm -filter 'examples-vite-vite*' --parallel run preview",
|
|
35
|
+
"build:shared-lib": "pnpm --filter @vite-vite/shared-lib run build",
|
|
36
|
+
"multi-example:ci": "pnpm -filter 'multi-example-*' --parallel run start",
|
|
34
37
|
"mixed-vv:1": "pnpm clean && pnpm -filter 'examples-vite-vite*' run mixed:1",
|
|
35
38
|
"mixed-vv:2": "pnpm clean && pnpm -filter 'examples-vite-vite*' run mixed:2",
|
|
36
|
-
"multi-example": "pnpm clean && pnpm --filter
|
|
39
|
+
"multi-example": "pnpm clean && pnpm --filter 'multi-example-*' --parallel run start",
|
|
37
40
|
"test": "vitest run --dir src",
|
|
38
41
|
"test:integration": "vitest run integration",
|
|
39
42
|
"e2e": "playwright test",
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
"@module-federation/sdk": "2.2.3",
|
|
73
76
|
"@rollup/pluginutils": "^5.3.0",
|
|
74
77
|
"defu": "^6.1.4",
|
|
75
|
-
"es-module-lexer": "^
|
|
78
|
+
"es-module-lexer": "^2.0.0",
|
|
76
79
|
"estree-walker": "^3.0.3",
|
|
77
80
|
"magic-string": "^0.30.21",
|
|
78
81
|
"pathe": "^2.0.3"
|