@module-federation/vite 1.13.0 → 1.13.1

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.
Files changed (3) hide show
  1. package/lib/index.cjs +228 -42
  2. package/lib/index.mjs +228 -42
  3. package/package.json +5 -5
package/lib/index.cjs CHANGED
@@ -206,6 +206,12 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
206
206
  else if (Array.isArray(inputOptions)) entryFiles = inputOptions;
207
207
  else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions);
208
208
  if (entryFiles && entryFiles.length > 0) htmlFilePath = getFirstHtmlEntryFile(entryFiles);
209
+ if (_command === "serve" && htmlFilePath && fs.existsSync(htmlFilePath)) {
210
+ const htmlContent = fs.readFileSync(htmlFilePath, "utf-8");
211
+ const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/gi;
212
+ let match;
213
+ while ((match = scriptRegex.exec(htmlContent)) !== null) entryFiles.push(match[1]);
214
+ }
209
215
  },
210
216
  buildStart() {
211
217
  if (_command === "serve") return;
@@ -674,6 +680,10 @@ function searchPackageVersion(sharedName) {
674
680
  }
675
681
  } catch (_) {}
676
682
  }
683
+ function inferVersionFromRequiredVersion(requiredVersion) {
684
+ if (!requiredVersion) return void 0;
685
+ return requiredVersion.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0];
686
+ }
677
687
  function normalizeShareItem(key, shareItem) {
678
688
  let version;
679
689
  try {
@@ -705,7 +715,7 @@ function normalizeShareItem(key, shareItem) {
705
715
  return {
706
716
  name: key,
707
717
  from: "",
708
- version: shareItem.version || version,
718
+ version: shareItem.version || inferVersionFromRequiredVersion(shareItem.requiredVersion) || version,
709
719
  scope: shareItem.shareScope || "default",
710
720
  shareConfig: {
711
721
  import: typeof shareItem === "object" ? shareItem.import : void 0,
@@ -972,12 +982,12 @@ function generateExposes(options) {
972
982
  //#endregion
973
983
  //#region src/virtualModules/virtualRuntimeInitStatus.ts
974
984
  const virtualRuntimeInitStatus = new VirtualModule("runtimeInit");
975
- function writeRuntimeInitStatus(command) {
976
- const globalKey = `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
977
- const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject } = globalThis[globalKey];
978
- export { initPromise, initResolve, initReject };` : `module.exports = globalThis[globalKey];`;
979
- virtualRuntimeInitStatus.writeSync(`
980
- const globalKey = ${JSON.stringify(globalKey)};
985
+ function getRuntimeInitGlobalKey() {
986
+ return `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
987
+ }
988
+ function getRuntimeInitBootstrapCode() {
989
+ return `
990
+ const globalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
981
991
  if (!globalThis[globalKey]) {
982
992
  let initResolve, initReject;
983
993
  const initPromise = new Promise((re, rj) => {
@@ -989,8 +999,6 @@ if (!globalThis[globalKey]) {
989
999
  initResolve,
990
1000
  initReject,
991
1001
  };
992
- // In SSR (no window), resolve immediately with a stub runtime
993
- // so modules don't hang waiting for browser-only init
994
1002
  if (typeof window === 'undefined') {
995
1003
  initResolve({
996
1004
  loadRemote: function() { return Promise.resolve(undefined); },
@@ -998,6 +1006,64 @@ if (!globalThis[globalKey]) {
998
1006
  });
999
1007
  }
1000
1008
  }
1009
+ `;
1010
+ }
1011
+ function getRuntimeInitPromiseBootstrapCode() {
1012
+ return `
1013
+ const __mfPromiseGlobalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
1014
+ let __mfPromiseState = globalThis[__mfPromiseGlobalKey];
1015
+ if (!__mfPromiseState) {
1016
+ let initResolve, initReject;
1017
+ const initPromise = new Promise((re, rj) => {
1018
+ initResolve = re;
1019
+ initReject = rj;
1020
+ });
1021
+ __mfPromiseState = globalThis[__mfPromiseGlobalKey] = {
1022
+ initPromise,
1023
+ initResolve,
1024
+ initReject,
1025
+ };
1026
+ if (typeof window === 'undefined') {
1027
+ initResolve({
1028
+ loadRemote: function() { return Promise.resolve(undefined); },
1029
+ loadShare: function() { return Promise.resolve(undefined); },
1030
+ });
1031
+ }
1032
+ }
1033
+ const initPromise = __mfPromiseState.initPromise;
1034
+ `;
1035
+ }
1036
+ function getRuntimeInitResolveBootstrapCode() {
1037
+ return `
1038
+ const __mfResolveGlobalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
1039
+ let __mfResolveState = globalThis[__mfResolveGlobalKey];
1040
+ if (!__mfResolveState) {
1041
+ let initResolve, initReject;
1042
+ const initPromise = new Promise((re, rj) => {
1043
+ initResolve = re;
1044
+ initReject = rj;
1045
+ });
1046
+ __mfResolveState = globalThis[__mfResolveGlobalKey] = {
1047
+ initPromise,
1048
+ initResolve,
1049
+ initReject,
1050
+ };
1051
+ if (typeof window === 'undefined') {
1052
+ initResolve({
1053
+ loadRemote: function() { return Promise.resolve(undefined); },
1054
+ loadShare: function() { return Promise.resolve(undefined); },
1055
+ });
1056
+ }
1057
+ }
1058
+ const initResolve = __mfResolveState.initResolve;
1059
+ `;
1060
+ }
1061
+ function writeRuntimeInitStatus(command) {
1062
+ getRuntimeInitGlobalKey();
1063
+ const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject } = globalThis[globalKey];
1064
+ export { initPromise, initResolve, initReject };` : `module.exports = globalThis[globalKey];`;
1065
+ virtualRuntimeInitStatus.writeSync(`
1066
+ ${getRuntimeInitBootstrapCode()}
1001
1067
  ${exportStatement}
1002
1068
  `);
1003
1069
  }
@@ -1022,7 +1088,8 @@ function getUsedRemotesMap() {
1022
1088
  }
1023
1089
  function generateRemotes(id, command, isRolldown) {
1024
1090
  const useESM = command === "build" || isRolldown;
1025
- const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1091
+ const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1092
+ const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1026
1093
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
1027
1094
  const exportLine = command === "serve" && useESM ? "export default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
1028
1095
  return `
@@ -1079,7 +1146,8 @@ function getLoadShareModulePath(pkg, isRolldown, command) {
1079
1146
  function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1080
1147
  if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1081
1148
  const useESM = command === "build" || isRolldown;
1082
- const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1149
+ const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1150
+ const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1083
1151
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
1084
1152
  const useSsrProviderFallback = hasPackageDependency("vinext") && command === "build" && pkg === "react";
1085
1153
  const providerImportId = getLocalProviderImportPath(pkg) || getPreBuildLibImportId(pkg);
@@ -1123,12 +1191,12 @@ new VirtualModule("localSharedImportMap");
1123
1191
  function getLocalSharedImportMapPath() {
1124
1192
  return getLocalSharedImportMapPath_temp();
1125
1193
  }
1126
- let prevSharedCount;
1194
+ let prevLocalSharedImportMapContent;
1127
1195
  function writeLocalSharedImportMap() {
1128
- const sharedCount = getUsedShares().size;
1129
- if (prevSharedCount !== sharedCount) {
1130
- prevSharedCount = sharedCount;
1131
- writeLocalSharedImportMap_temp(generateLocalSharedImportMap());
1196
+ const nextContent = generateLocalSharedImportMap();
1197
+ if (prevLocalSharedImportMapContent !== nextContent) {
1198
+ prevLocalSharedImportMapContent = nextContent;
1199
+ writeLocalSharedImportMap_temp(nextContent);
1132
1200
  }
1133
1201
  }
1134
1202
  function generateLocalSharedImportMap() {
@@ -1211,7 +1279,7 @@ const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
1211
1279
  function getRemoteEntryId(options) {
1212
1280
  return `${REMOTE_ENTRY_ID}:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1213
1281
  }
1214
- function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options)) {
1282
+ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
1215
1283
  const pluginImportNames = options.runtimePlugins.map((p, i) => {
1216
1284
  if (typeof p === "string") return [
1217
1285
  `$runtimePlugin_${i}`,
@@ -1227,15 +1295,25 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1227
1295
  return `
1228
1296
  import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
1229
1297
  ${pluginImportNames.map((item) => item[1]).join("\n")}
1230
- import exposesMap from "${virtualExposesId}"
1231
- import {usedShared, usedRemotes} from "${getLocalSharedImportMapPath()}"
1232
- import {
1233
- initResolve
1234
- } from "${virtualRuntimeInitStatus.getImportId()}"
1298
+ ${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
1235
1299
  const initTokens = {}
1236
1300
  const shareScopeName = ${JSON.stringify(options.shareScope)}
1237
1301
  const mfName = ${JSON.stringify(options.name)}
1302
+ let localSharedImportMapPromise
1303
+ let exposesMapPromise
1304
+
1305
+ async function getLocalSharedImportMap() {
1306
+ localSharedImportMapPromise ??= import("${getLocalSharedImportMapPath()}")
1307
+ return localSharedImportMapPromise
1308
+ }
1309
+
1310
+ async function getExposesMap() {
1311
+ exposesMapPromise ??= import("${virtualExposesId}").then((mod) => mod.default ?? mod)
1312
+ return exposesMapPromise
1313
+ }
1314
+
1238
1315
  async function init(shared = {}, initScope = []) {
1316
+ const {usedShared, usedRemotes} = await getLocalSharedImportMap()
1239
1317
  const initRes = runtimeInit({
1240
1318
  name: mfName,
1241
1319
  remotes: usedRemotes,
@@ -1263,7 +1341,8 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1263
1341
  return initRes
1264
1342
  }
1265
1343
 
1266
- function getExposes(moduleName) {
1344
+ async function getExposes(moduleName) {
1345
+ const exposesMap = await getExposesMap()
1267
1346
  if (!(moduleName in exposesMap)) throw new Error(\`Module \${moduleName} does not exist in container.\`)
1268
1347
  return (exposesMap[moduleName])().then(res => () => res)
1269
1348
  }
@@ -1276,13 +1355,8 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1276
1355
  const hostAutoInitModule = new VirtualModule("hostAutoInit", "__H_A_I__");
1277
1356
  function writeHostAutoInit(remoteEntryId = REMOTE_ENTRY_ID) {
1278
1357
  hostAutoInitModule.writeSync(`
1279
- const remoteEntryPromise = import("${remoteEntryId}")
1280
- Promise.resolve(remoteEntryPromise)
1281
- .then(remoteEntry => {
1282
- return Promise.resolve(remoteEntry.__tla)
1283
- .then(remoteEntry.init)
1284
- .catch(remoteEntry.init)
1285
- })
1358
+ const remoteEntry = await import("${remoteEntryId}");
1359
+ await remoteEntry.init();
1286
1360
  `);
1287
1361
  }
1288
1362
  function getHostAutoInitImportId() {
@@ -1305,13 +1379,21 @@ function initVirtualModules(command, remoteEntryId) {
1305
1379
  * If Rollup's deconflict renamed the alias but didn't update references
1306
1380
  * in the code body, fall back to proxyLocal so they stay in sync.
1307
1381
  */
1308
- function resolveProxyAlias(binding, proxyLocal, code, fullImport) {
1382
+ function resolveProxyAlias(binding, proxyLocal, code, fullImport, claimedLocals = /* @__PURE__ */ new Set()) {
1309
1383
  const codeWithoutImport = code.replace(fullImport, "");
1310
1384
  const escapedLocal = binding.local.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1311
1385
  const localUsedInCode = new RegExp(`\\b${escapedLocal}\\b`).test(codeWithoutImport);
1386
+ const claimedImportLocals = /* @__PURE__ */ new Set();
1387
+ const importRe = /import\s*\{([^}]+)\}\s*from\s*["'][^"']+["']\s*;?/g;
1388
+ let match;
1389
+ while ((match = importRe.exec(codeWithoutImport)) !== null) for (const spec of match[1].split(",")) {
1390
+ const parts = spec.trim().split(/\s+as\s+/);
1391
+ claimedImportLocals.add((parts[1] || parts[0]).trim());
1392
+ }
1393
+ const local = !localUsedInCode && !claimedLocals.has(proxyLocal) && !claimedImportLocals.has(proxyLocal) ? proxyLocal : binding.local;
1312
1394
  return {
1313
1395
  imported: binding.imported,
1314
- local: localUsedInCode ? binding.local : proxyLocal
1396
+ local
1315
1397
  };
1316
1398
  }
1317
1399
  function findRemoteEntryFile(filename, bundle) {
@@ -1766,14 +1848,14 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
1766
1848
  }
1767
1849
  },
1768
1850
  load(id) {
1769
- if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
1851
+ if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
1770
1852
  if (id === virtualExposesId) return generateExposes(options);
1771
1853
  if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
1772
1854
  },
1773
1855
  transform(code, id) {
1774
1856
  return mapCodeToCodeWithSourcemap((() => {
1775
1857
  if (!filter(id)) return;
1776
- if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
1858
+ if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
1777
1859
  if (id === virtualExposesId) return generateExposes(options);
1778
1860
  if (id.includes(getHostAutoInitPath())) {
1779
1861
  if (_command === "serve") {
@@ -2032,6 +2114,58 @@ var aliasToArrayPlugin_default = {
2032
2114
  }
2033
2115
  };
2034
2116
  //#endregion
2117
+ //#region src/utils/controlChunkSanitizer.ts
2118
+ const FEDERATION_CONTROL_CHUNK_HINTS = [
2119
+ "hostInit",
2120
+ "virtualExposes",
2121
+ "localSharedImportMap"
2122
+ ];
2123
+ function stripEmptyPreloadCalls(code) {
2124
+ const helperImportRegex = /import\s*\{\s*_\s*as\s*(\w+)\s*\}\s*from\s*["'][^"']+["']\s*;?/g;
2125
+ const helperAliases = [...code.matchAll(helperImportRegex)].map((match) => match[1]);
2126
+ let nextCode = code;
2127
+ for (const alias of helperAliases) {
2128
+ const marker = `${alias}(()=>`;
2129
+ let start = nextCode.indexOf(marker);
2130
+ while (start !== -1) {
2131
+ const exprStart = start + marker.length;
2132
+ let depth = 0;
2133
+ let cursor = exprStart;
2134
+ let replacementEnd = -1;
2135
+ while (cursor < nextCode.length) {
2136
+ const char = nextCode[cursor];
2137
+ if (char === "(") depth++;
2138
+ else if (char === ")") depth--;
2139
+ else if (depth === 0 && nextCode.startsWith(",[],import.meta.url)", cursor)) {
2140
+ replacementEnd = cursor;
2141
+ break;
2142
+ }
2143
+ cursor++;
2144
+ }
2145
+ if (replacementEnd === -1) break;
2146
+ const expression = nextCode.slice(exprStart, replacementEnd);
2147
+ nextCode = nextCode.slice(0, start) + expression + nextCode.slice(replacementEnd + 20);
2148
+ start = nextCode.indexOf(marker, start + expression.length);
2149
+ }
2150
+ }
2151
+ nextCode = nextCode.replace(/import\s*["'][^"']*__loadShare__[^"']*["']\s*;?/g, "");
2152
+ nextCode = nextCode.replace(helperImportRegex, (statement, local) => {
2153
+ return new RegExp(`\\b${local}\\s*\\(`).test(nextCode.replace(statement, "")) ? statement : "";
2154
+ });
2155
+ return nextCode;
2156
+ }
2157
+ function isFederationControlChunk(fileName, filename) {
2158
+ return fileName.includes(filename) || FEDERATION_CONTROL_CHUNK_HINTS.some((hint) => fileName.includes(hint));
2159
+ }
2160
+ function sanitizeFederationControlChunk(code, fileName, filename) {
2161
+ let nextCode = stripEmptyPreloadCalls(code);
2162
+ if (fileName.includes("localSharedImportMap")) {
2163
+ const remoteEntryImportRegex = new RegExp(`import\\s*["'][^"']*${filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']\\s*;?`, "g");
2164
+ nextCode = nextCode.replace(remoteEntryImportRegex, "");
2165
+ }
2166
+ return nextCode;
2167
+ }
2168
+ //#endregion
2035
2169
  //#region src/utils/normalizeOptimizeDeps.ts
2036
2170
  var normalizeOptimizeDeps_default = {
2037
2171
  name: "normalizeOptimizeDeps",
@@ -2169,9 +2303,19 @@ function federation(mfUserOptions) {
2169
2303
  config(config) {
2170
2304
  const runtimeInitId = virtualRuntimeInitStatus.getImportId();
2171
2305
  config.build = config.build || {};
2172
- config.build.rollupOptions = config.build.rollupOptions || {};
2173
- if (!Array.isArray(config.build.rollupOptions.output)) {
2174
- const output = config.build.rollupOptions.output ||= {};
2306
+ if (config.build.modulePreload !== false) {
2307
+ const currentModulePreload = config.build.modulePreload && typeof config.build.modulePreload === "object" ? config.build.modulePreload : {};
2308
+ const existingResolveDependencies = currentModulePreload.resolveDependencies;
2309
+ config.build.modulePreload = {
2310
+ ...currentModulePreload,
2311
+ resolveDependencies(filename, deps, context) {
2312
+ const resolvedDeps = existingResolveDependencies ? existingResolveDependencies(filename, deps, context) : deps;
2313
+ const hostFile = pathe.default.basename(context.hostId);
2314
+ return context.hostType === "js" && (hostFile === options.filename || hostFile.includes("hostInit") || hostFile.includes("virtualExposes") || hostFile.includes("localSharedImportMap")) ? [] : resolvedDeps;
2315
+ }
2316
+ };
2317
+ }
2318
+ const applyManualChunks = (output) => {
2175
2319
  const existingManualChunks = output.manualChunks;
2176
2320
  output.manualChunks = function(id) {
2177
2321
  if (id.includes(runtimeInitId)) return "runtimeInit";
@@ -2184,7 +2328,12 @@ function federation(mfUserOptions) {
2184
2328
  for (const [key, ids] of Object.entries(existingManualChunks)) if (Array.isArray(ids) && ids.some((v) => id.includes(v))) return key;
2185
2329
  }
2186
2330
  };
2187
- }
2331
+ };
2332
+ config.build.rollupOptions = config.build.rollupOptions || {};
2333
+ if (!Array.isArray(config.build.rollupOptions.output)) applyManualChunks(config.build.rollupOptions.output ||= {});
2334
+ const buildWithRolldown = config.build;
2335
+ buildWithRolldown.rolldownOptions = buildWithRolldown.rolldownOptions || {};
2336
+ if (!Array.isArray(buildWithRolldown.rolldownOptions.output)) applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
2188
2337
  },
2189
2338
  load(id) {
2190
2339
  if (id.startsWith("\0")) return;
@@ -2216,6 +2365,11 @@ function federation(mfUserOptions) {
2216
2365
  }
2217
2366
  },
2218
2367
  generateBundle(_, bundle) {
2368
+ for (const [fileName, chunk] of Object.entries(bundle)) {
2369
+ if (chunk.type !== "chunk") continue;
2370
+ if (!isFederationControlChunk(fileName, filename)) continue;
2371
+ chunk.code = sanitizeFederationControlChunk(chunk.code, fileName, filename);
2372
+ }
2219
2373
  for (const [fileName, chunk] of Object.entries(bundle)) {
2220
2374
  if (chunk.type !== "chunk") continue;
2221
2375
  if (fileName.includes("__loadShare__")) continue;
@@ -2253,12 +2407,12 @@ function federation(mfUserOptions) {
2253
2407
  fileName
2254
2408
  });
2255
2409
  }
2256
- if (proxyChunks.size === 0) return;
2257
- for (const [fileName, chunk] of Object.entries(bundle)) {
2410
+ if (proxyChunks.size > 0) for (const [fileName, chunk] of Object.entries(bundle)) {
2258
2411
  if (chunk.type !== "chunk") continue;
2259
2412
  if (fileName.includes("__loadShare__")) continue;
2260
2413
  let code = chunk.code;
2261
2414
  let modified = false;
2415
+ const claimedLocals = /* @__PURE__ */ new Set();
2262
2416
  for (const [proxyFileName, proxyInfo] of proxyChunks) {
2263
2417
  const proxyBaseName = proxyFileName.replace(/^.*\//, "").replace(/\.js$/, "").replace(/-[A-Za-z0-9_-]+$/, "");
2264
2418
  const importMatch = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']([^"']*${proxyBaseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"']*)["']\\s*;?`).exec(code);
@@ -2281,9 +2435,12 @@ function federation(mfUserOptions) {
2281
2435
  }
2282
2436
  const inlineable = [];
2283
2437
  const nonInlineable = [];
2438
+ const pendingLocals = new Set(bindings.map((binding) => binding.local));
2284
2439
  for (const b of bindings) {
2440
+ pendingLocals.delete(b.local);
2285
2441
  const proxyLocal = exportMap[b.imported];
2286
2442
  if (!proxyLocal) {
2443
+ claimedLocals.add(b.local);
2287
2444
  nonInlineable.push(b);
2288
2445
  continue;
2289
2446
  }
@@ -2305,7 +2462,14 @@ function federation(mfUserOptions) {
2305
2462
  local: b.local,
2306
2463
  funcBody: renamedFunc
2307
2464
  });
2308
- } else nonInlineable.push(resolveProxyAlias(b, proxyLocal, code, fullImport));
2465
+ claimedLocals.add(b.local);
2466
+ } else {
2467
+ const unavailableLocals = new Set(claimedLocals);
2468
+ pendingLocals.forEach((local) => unavailableLocals.add(local));
2469
+ const resolvedBinding = resolveProxyAlias(b, proxyLocal, code, fullImport, unavailableLocals);
2470
+ claimedLocals.add(resolvedBinding.local);
2471
+ nonInlineable.push(resolvedBinding);
2472
+ }
2309
2473
  }
2310
2474
  const hasRenamedAlias = nonInlineable.some((b) => bindings.find((ob) => ob.imported === b.imported)?.local !== b.local);
2311
2475
  if (inlineable.length === 0 && !hasRenamedAlias) continue;
@@ -2319,6 +2483,28 @@ function federation(mfUserOptions) {
2319
2483
  }
2320
2484
  }
2321
2485
  },
2486
+ {
2487
+ name: "module-federation-strip-empty-preload-helper",
2488
+ enforce: "post",
2489
+ apply: "build",
2490
+ renderChunk(code, chunk) {
2491
+ if (!isFederationControlChunk(chunk.fileName, filename)) return;
2492
+ const nextCode = sanitizeFederationControlChunk(code, chunk.fileName, filename);
2493
+ return nextCode === code ? null : {
2494
+ code: nextCode,
2495
+ map: null
2496
+ };
2497
+ },
2498
+ writeBundle(outputOptions, bundle) {
2499
+ if (!outputOptions.dir) return;
2500
+ for (const chunk of Object.values(bundle)) {
2501
+ if (chunk.type !== "chunk") continue;
2502
+ if (!isFederationControlChunk(chunk.fileName, filename)) continue;
2503
+ const outputPath = pathe.default.join(outputOptions.dir, chunk.fileName);
2504
+ (0, fs.writeFileSync)(outputPath, sanitizeFederationControlChunk((0, fs.readFileSync)(outputPath, "utf-8"), chunk.fileName, filename));
2505
+ }
2506
+ }
2507
+ },
2322
2508
  {
2323
2509
  name: "module-federation-dev-await-shared-init",
2324
2510
  apply: "serve",
@@ -2353,7 +2539,7 @@ function federation(mfUserOptions) {
2353
2539
  config(config, { command: _command }) {
2354
2540
  const isRolldown = !!this?.meta?.rolldownVersion;
2355
2541
  let implementation = options.implementation;
2356
- if (isRolldown) implementation = implementation.replace(/\.cjs\.cjs$/, ".esm.js");
2542
+ if (isRolldown) implementation = implementation.replace(/\.cjs(\.js)?$/, ".js");
2357
2543
  config.resolve.alias.push({
2358
2544
  find: "@module-federation/runtime",
2359
2545
  replacement: implementation
package/lib/index.mjs CHANGED
@@ -184,6 +184,12 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
184
184
  else if (Array.isArray(inputOptions)) entryFiles = inputOptions;
185
185
  else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions);
186
186
  if (entryFiles && entryFiles.length > 0) htmlFilePath = getFirstHtmlEntryFile(entryFiles);
187
+ if (_command === "serve" && htmlFilePath && fs.existsSync(htmlFilePath)) {
188
+ const htmlContent = fs.readFileSync(htmlFilePath, "utf-8");
189
+ const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/gi;
190
+ let match;
191
+ while ((match = scriptRegex.exec(htmlContent)) !== null) entryFiles.push(match[1]);
192
+ }
187
193
  },
188
194
  buildStart() {
189
195
  if (_command === "serve") return;
@@ -652,6 +658,10 @@ function searchPackageVersion(sharedName) {
652
658
  }
653
659
  } catch (_) {}
654
660
  }
661
+ function inferVersionFromRequiredVersion(requiredVersion) {
662
+ if (!requiredVersion) return void 0;
663
+ return requiredVersion.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0];
664
+ }
655
665
  function normalizeShareItem(key, shareItem) {
656
666
  let version;
657
667
  try {
@@ -682,7 +692,7 @@ function normalizeShareItem(key, shareItem) {
682
692
  return {
683
693
  name: key,
684
694
  from: "",
685
- version: shareItem.version || version,
695
+ version: shareItem.version || inferVersionFromRequiredVersion(shareItem.requiredVersion) || version,
686
696
  scope: shareItem.shareScope || "default",
687
697
  shareConfig: {
688
698
  import: typeof shareItem === "object" ? shareItem.import : void 0,
@@ -949,12 +959,12 @@ function generateExposes(options) {
949
959
  //#endregion
950
960
  //#region src/virtualModules/virtualRuntimeInitStatus.ts
951
961
  const virtualRuntimeInitStatus = new VirtualModule("runtimeInit");
952
- function writeRuntimeInitStatus(command) {
953
- const globalKey = `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
954
- const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject } = globalThis[globalKey];
955
- export { initPromise, initResolve, initReject };` : `module.exports = globalThis[globalKey];`;
956
- virtualRuntimeInitStatus.writeSync(`
957
- const globalKey = ${JSON.stringify(globalKey)};
962
+ function getRuntimeInitGlobalKey() {
963
+ return `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
964
+ }
965
+ function getRuntimeInitBootstrapCode() {
966
+ return `
967
+ const globalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
958
968
  if (!globalThis[globalKey]) {
959
969
  let initResolve, initReject;
960
970
  const initPromise = new Promise((re, rj) => {
@@ -966,8 +976,6 @@ if (!globalThis[globalKey]) {
966
976
  initResolve,
967
977
  initReject,
968
978
  };
969
- // In SSR (no window), resolve immediately with a stub runtime
970
- // so modules don't hang waiting for browser-only init
971
979
  if (typeof window === 'undefined') {
972
980
  initResolve({
973
981
  loadRemote: function() { return Promise.resolve(undefined); },
@@ -975,6 +983,64 @@ if (!globalThis[globalKey]) {
975
983
  });
976
984
  }
977
985
  }
986
+ `;
987
+ }
988
+ function getRuntimeInitPromiseBootstrapCode() {
989
+ return `
990
+ const __mfPromiseGlobalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
991
+ let __mfPromiseState = globalThis[__mfPromiseGlobalKey];
992
+ if (!__mfPromiseState) {
993
+ let initResolve, initReject;
994
+ const initPromise = new Promise((re, rj) => {
995
+ initResolve = re;
996
+ initReject = rj;
997
+ });
998
+ __mfPromiseState = globalThis[__mfPromiseGlobalKey] = {
999
+ initPromise,
1000
+ initResolve,
1001
+ initReject,
1002
+ };
1003
+ if (typeof window === 'undefined') {
1004
+ initResolve({
1005
+ loadRemote: function() { return Promise.resolve(undefined); },
1006
+ loadShare: function() { return Promise.resolve(undefined); },
1007
+ });
1008
+ }
1009
+ }
1010
+ const initPromise = __mfPromiseState.initPromise;
1011
+ `;
1012
+ }
1013
+ function getRuntimeInitResolveBootstrapCode() {
1014
+ return `
1015
+ const __mfResolveGlobalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
1016
+ let __mfResolveState = globalThis[__mfResolveGlobalKey];
1017
+ if (!__mfResolveState) {
1018
+ let initResolve, initReject;
1019
+ const initPromise = new Promise((re, rj) => {
1020
+ initResolve = re;
1021
+ initReject = rj;
1022
+ });
1023
+ __mfResolveState = globalThis[__mfResolveGlobalKey] = {
1024
+ initPromise,
1025
+ initResolve,
1026
+ initReject,
1027
+ };
1028
+ if (typeof window === 'undefined') {
1029
+ initResolve({
1030
+ loadRemote: function() { return Promise.resolve(undefined); },
1031
+ loadShare: function() { return Promise.resolve(undefined); },
1032
+ });
1033
+ }
1034
+ }
1035
+ const initResolve = __mfResolveState.initResolve;
1036
+ `;
1037
+ }
1038
+ function writeRuntimeInitStatus(command) {
1039
+ getRuntimeInitGlobalKey();
1040
+ const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject } = globalThis[globalKey];
1041
+ export { initPromise, initResolve, initReject };` : `module.exports = globalThis[globalKey];`;
1042
+ virtualRuntimeInitStatus.writeSync(`
1043
+ ${getRuntimeInitBootstrapCode()}
978
1044
  ${exportStatement}
979
1045
  `);
980
1046
  }
@@ -999,7 +1065,8 @@ function getUsedRemotesMap() {
999
1065
  }
1000
1066
  function generateRemotes(id, command, isRolldown) {
1001
1067
  const useESM = command === "build" || isRolldown;
1002
- const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1068
+ const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1069
+ const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1003
1070
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
1004
1071
  const exportLine = command === "serve" && useESM ? "export default exportModule.default ?? exportModule" : useESM ? "export default exportModule" : "module.exports = exportModule";
1005
1072
  return `
@@ -1056,7 +1123,8 @@ function getLoadShareModulePath(pkg, isRolldown, command) {
1056
1123
  function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1057
1124
  if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1058
1125
  const useESM = command === "build" || isRolldown;
1059
- const importLine = useESM ? `import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1126
+ const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1127
+ const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1060
1128
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
1061
1129
  const useSsrProviderFallback = hasPackageDependency("vinext") && command === "build" && pkg === "react";
1062
1130
  const providerImportId = getLocalProviderImportPath(pkg) || getPreBuildLibImportId(pkg);
@@ -1100,12 +1168,12 @@ new VirtualModule("localSharedImportMap");
1100
1168
  function getLocalSharedImportMapPath() {
1101
1169
  return getLocalSharedImportMapPath_temp();
1102
1170
  }
1103
- let prevSharedCount;
1171
+ let prevLocalSharedImportMapContent;
1104
1172
  function writeLocalSharedImportMap() {
1105
- const sharedCount = getUsedShares().size;
1106
- if (prevSharedCount !== sharedCount) {
1107
- prevSharedCount = sharedCount;
1108
- writeLocalSharedImportMap_temp(generateLocalSharedImportMap());
1173
+ const nextContent = generateLocalSharedImportMap();
1174
+ if (prevLocalSharedImportMapContent !== nextContent) {
1175
+ prevLocalSharedImportMapContent = nextContent;
1176
+ writeLocalSharedImportMap_temp(nextContent);
1109
1177
  }
1110
1178
  }
1111
1179
  function generateLocalSharedImportMap() {
@@ -1188,7 +1256,7 @@ const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
1188
1256
  function getRemoteEntryId(options) {
1189
1257
  return `${REMOTE_ENTRY_ID}:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1190
1258
  }
1191
- function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options)) {
1259
+ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
1192
1260
  const pluginImportNames = options.runtimePlugins.map((p, i) => {
1193
1261
  if (typeof p === "string") return [
1194
1262
  `$runtimePlugin_${i}`,
@@ -1204,15 +1272,25 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1204
1272
  return `
1205
1273
  import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
1206
1274
  ${pluginImportNames.map((item) => item[1]).join("\n")}
1207
- import exposesMap from "${virtualExposesId}"
1208
- import {usedShared, usedRemotes} from "${getLocalSharedImportMapPath()}"
1209
- import {
1210
- initResolve
1211
- } from "${virtualRuntimeInitStatus.getImportId()}"
1275
+ ${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
1212
1276
  const initTokens = {}
1213
1277
  const shareScopeName = ${JSON.stringify(options.shareScope)}
1214
1278
  const mfName = ${JSON.stringify(options.name)}
1279
+ let localSharedImportMapPromise
1280
+ let exposesMapPromise
1281
+
1282
+ async function getLocalSharedImportMap() {
1283
+ localSharedImportMapPromise ??= import("${getLocalSharedImportMapPath()}")
1284
+ return localSharedImportMapPromise
1285
+ }
1286
+
1287
+ async function getExposesMap() {
1288
+ exposesMapPromise ??= import("${virtualExposesId}").then((mod) => mod.default ?? mod)
1289
+ return exposesMapPromise
1290
+ }
1291
+
1215
1292
  async function init(shared = {}, initScope = []) {
1293
+ const {usedShared, usedRemotes} = await getLocalSharedImportMap()
1216
1294
  const initRes = runtimeInit({
1217
1295
  name: mfName,
1218
1296
  remotes: usedRemotes,
@@ -1240,7 +1318,8 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1240
1318
  return initRes
1241
1319
  }
1242
1320
 
1243
- function getExposes(moduleName) {
1321
+ async function getExposes(moduleName) {
1322
+ const exposesMap = await getExposesMap()
1244
1323
  if (!(moduleName in exposesMap)) throw new Error(\`Module \${moduleName} does not exist in container.\`)
1245
1324
  return (exposesMap[moduleName])().then(res => () => res)
1246
1325
  }
@@ -1253,13 +1332,8 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1253
1332
  const hostAutoInitModule = new VirtualModule("hostAutoInit", "__H_A_I__");
1254
1333
  function writeHostAutoInit(remoteEntryId = REMOTE_ENTRY_ID) {
1255
1334
  hostAutoInitModule.writeSync(`
1256
- const remoteEntryPromise = import("${remoteEntryId}")
1257
- Promise.resolve(remoteEntryPromise)
1258
- .then(remoteEntry => {
1259
- return Promise.resolve(remoteEntry.__tla)
1260
- .then(remoteEntry.init)
1261
- .catch(remoteEntry.init)
1262
- })
1335
+ const remoteEntry = await import("${remoteEntryId}");
1336
+ await remoteEntry.init();
1263
1337
  `);
1264
1338
  }
1265
1339
  function getHostAutoInitImportId() {
@@ -1282,13 +1356,21 @@ function initVirtualModules(command, remoteEntryId) {
1282
1356
  * If Rollup's deconflict renamed the alias but didn't update references
1283
1357
  * in the code body, fall back to proxyLocal so they stay in sync.
1284
1358
  */
1285
- function resolveProxyAlias(binding, proxyLocal, code, fullImport) {
1359
+ function resolveProxyAlias(binding, proxyLocal, code, fullImport, claimedLocals = /* @__PURE__ */ new Set()) {
1286
1360
  const codeWithoutImport = code.replace(fullImport, "");
1287
1361
  const escapedLocal = binding.local.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1288
1362
  const localUsedInCode = new RegExp(`\\b${escapedLocal}\\b`).test(codeWithoutImport);
1363
+ const claimedImportLocals = /* @__PURE__ */ new Set();
1364
+ const importRe = /import\s*\{([^}]+)\}\s*from\s*["'][^"']+["']\s*;?/g;
1365
+ let match;
1366
+ while ((match = importRe.exec(codeWithoutImport)) !== null) for (const spec of match[1].split(",")) {
1367
+ const parts = spec.trim().split(/\s+as\s+/);
1368
+ claimedImportLocals.add((parts[1] || parts[0]).trim());
1369
+ }
1370
+ const local = !localUsedInCode && !claimedLocals.has(proxyLocal) && !claimedImportLocals.has(proxyLocal) ? proxyLocal : binding.local;
1289
1371
  return {
1290
1372
  imported: binding.imported,
1291
- local: localUsedInCode ? binding.local : proxyLocal
1373
+ local
1292
1374
  };
1293
1375
  }
1294
1376
  function findRemoteEntryFile(filename, bundle) {
@@ -1743,14 +1825,14 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
1743
1825
  }
1744
1826
  },
1745
1827
  load(id) {
1746
- if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
1828
+ if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
1747
1829
  if (id === virtualExposesId) return generateExposes(options);
1748
1830
  if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
1749
1831
  },
1750
1832
  transform(code, id) {
1751
1833
  return mapCodeToCodeWithSourcemap((() => {
1752
1834
  if (!filter(id)) return;
1753
- if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId));
1835
+ if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
1754
1836
  if (id === virtualExposesId) return generateExposes(options);
1755
1837
  if (id.includes(getHostAutoInitPath())) {
1756
1838
  if (_command === "serve") {
@@ -2009,6 +2091,58 @@ var aliasToArrayPlugin_default = {
2009
2091
  }
2010
2092
  };
2011
2093
  //#endregion
2094
+ //#region src/utils/controlChunkSanitizer.ts
2095
+ const FEDERATION_CONTROL_CHUNK_HINTS = [
2096
+ "hostInit",
2097
+ "virtualExposes",
2098
+ "localSharedImportMap"
2099
+ ];
2100
+ function stripEmptyPreloadCalls(code) {
2101
+ const helperImportRegex = /import\s*\{\s*_\s*as\s*(\w+)\s*\}\s*from\s*["'][^"']+["']\s*;?/g;
2102
+ const helperAliases = [...code.matchAll(helperImportRegex)].map((match) => match[1]);
2103
+ let nextCode = code;
2104
+ for (const alias of helperAliases) {
2105
+ const marker = `${alias}(()=>`;
2106
+ let start = nextCode.indexOf(marker);
2107
+ while (start !== -1) {
2108
+ const exprStart = start + marker.length;
2109
+ let depth = 0;
2110
+ let cursor = exprStart;
2111
+ let replacementEnd = -1;
2112
+ while (cursor < nextCode.length) {
2113
+ const char = nextCode[cursor];
2114
+ if (char === "(") depth++;
2115
+ else if (char === ")") depth--;
2116
+ else if (depth === 0 && nextCode.startsWith(",[],import.meta.url)", cursor)) {
2117
+ replacementEnd = cursor;
2118
+ break;
2119
+ }
2120
+ cursor++;
2121
+ }
2122
+ if (replacementEnd === -1) break;
2123
+ const expression = nextCode.slice(exprStart, replacementEnd);
2124
+ nextCode = nextCode.slice(0, start) + expression + nextCode.slice(replacementEnd + 20);
2125
+ start = nextCode.indexOf(marker, start + expression.length);
2126
+ }
2127
+ }
2128
+ nextCode = nextCode.replace(/import\s*["'][^"']*__loadShare__[^"']*["']\s*;?/g, "");
2129
+ nextCode = nextCode.replace(helperImportRegex, (statement, local) => {
2130
+ return new RegExp(`\\b${local}\\s*\\(`).test(nextCode.replace(statement, "")) ? statement : "";
2131
+ });
2132
+ return nextCode;
2133
+ }
2134
+ function isFederationControlChunk(fileName, filename) {
2135
+ return fileName.includes(filename) || FEDERATION_CONTROL_CHUNK_HINTS.some((hint) => fileName.includes(hint));
2136
+ }
2137
+ function sanitizeFederationControlChunk(code, fileName, filename) {
2138
+ let nextCode = stripEmptyPreloadCalls(code);
2139
+ if (fileName.includes("localSharedImportMap")) {
2140
+ const remoteEntryImportRegex = new RegExp(`import\\s*["'][^"']*${filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']\\s*;?`, "g");
2141
+ nextCode = nextCode.replace(remoteEntryImportRegex, "");
2142
+ }
2143
+ return nextCode;
2144
+ }
2145
+ //#endregion
2012
2146
  //#region src/utils/normalizeOptimizeDeps.ts
2013
2147
  var normalizeOptimizeDeps_default = {
2014
2148
  name: "normalizeOptimizeDeps",
@@ -2146,9 +2280,19 @@ function federation(mfUserOptions) {
2146
2280
  config(config) {
2147
2281
  const runtimeInitId = virtualRuntimeInitStatus.getImportId();
2148
2282
  config.build = config.build || {};
2149
- config.build.rollupOptions = config.build.rollupOptions || {};
2150
- if (!Array.isArray(config.build.rollupOptions.output)) {
2151
- const output = config.build.rollupOptions.output ||= {};
2283
+ if (config.build.modulePreload !== false) {
2284
+ const currentModulePreload = config.build.modulePreload && typeof config.build.modulePreload === "object" ? config.build.modulePreload : {};
2285
+ const existingResolveDependencies = currentModulePreload.resolveDependencies;
2286
+ config.build.modulePreload = {
2287
+ ...currentModulePreload,
2288
+ resolveDependencies(filename, deps, context) {
2289
+ const resolvedDeps = existingResolveDependencies ? existingResolveDependencies(filename, deps, context) : deps;
2290
+ const hostFile = path.basename(context.hostId);
2291
+ return context.hostType === "js" && (hostFile === options.filename || hostFile.includes("hostInit") || hostFile.includes("virtualExposes") || hostFile.includes("localSharedImportMap")) ? [] : resolvedDeps;
2292
+ }
2293
+ };
2294
+ }
2295
+ const applyManualChunks = (output) => {
2152
2296
  const existingManualChunks = output.manualChunks;
2153
2297
  output.manualChunks = function(id) {
2154
2298
  if (id.includes(runtimeInitId)) return "runtimeInit";
@@ -2161,7 +2305,12 @@ function federation(mfUserOptions) {
2161
2305
  for (const [key, ids] of Object.entries(existingManualChunks)) if (Array.isArray(ids) && ids.some((v) => id.includes(v))) return key;
2162
2306
  }
2163
2307
  };
2164
- }
2308
+ };
2309
+ config.build.rollupOptions = config.build.rollupOptions || {};
2310
+ if (!Array.isArray(config.build.rollupOptions.output)) applyManualChunks(config.build.rollupOptions.output ||= {});
2311
+ const buildWithRolldown = config.build;
2312
+ buildWithRolldown.rolldownOptions = buildWithRolldown.rolldownOptions || {};
2313
+ if (!Array.isArray(buildWithRolldown.rolldownOptions.output)) applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
2165
2314
  },
2166
2315
  load(id) {
2167
2316
  if (id.startsWith("\0")) return;
@@ -2193,6 +2342,11 @@ function federation(mfUserOptions) {
2193
2342
  }
2194
2343
  },
2195
2344
  generateBundle(_, bundle) {
2345
+ for (const [fileName, chunk] of Object.entries(bundle)) {
2346
+ if (chunk.type !== "chunk") continue;
2347
+ if (!isFederationControlChunk(fileName, filename)) continue;
2348
+ chunk.code = sanitizeFederationControlChunk(chunk.code, fileName, filename);
2349
+ }
2196
2350
  for (const [fileName, chunk] of Object.entries(bundle)) {
2197
2351
  if (chunk.type !== "chunk") continue;
2198
2352
  if (fileName.includes("__loadShare__")) continue;
@@ -2230,12 +2384,12 @@ function federation(mfUserOptions) {
2230
2384
  fileName
2231
2385
  });
2232
2386
  }
2233
- if (proxyChunks.size === 0) return;
2234
- for (const [fileName, chunk] of Object.entries(bundle)) {
2387
+ if (proxyChunks.size > 0) for (const [fileName, chunk] of Object.entries(bundle)) {
2235
2388
  if (chunk.type !== "chunk") continue;
2236
2389
  if (fileName.includes("__loadShare__")) continue;
2237
2390
  let code = chunk.code;
2238
2391
  let modified = false;
2392
+ const claimedLocals = /* @__PURE__ */ new Set();
2239
2393
  for (const [proxyFileName, proxyInfo] of proxyChunks) {
2240
2394
  const proxyBaseName = proxyFileName.replace(/^.*\//, "").replace(/\.js$/, "").replace(/-[A-Za-z0-9_-]+$/, "");
2241
2395
  const importMatch = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']([^"']*${proxyBaseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"']*)["']\\s*;?`).exec(code);
@@ -2258,9 +2412,12 @@ function federation(mfUserOptions) {
2258
2412
  }
2259
2413
  const inlineable = [];
2260
2414
  const nonInlineable = [];
2415
+ const pendingLocals = new Set(bindings.map((binding) => binding.local));
2261
2416
  for (const b of bindings) {
2417
+ pendingLocals.delete(b.local);
2262
2418
  const proxyLocal = exportMap[b.imported];
2263
2419
  if (!proxyLocal) {
2420
+ claimedLocals.add(b.local);
2264
2421
  nonInlineable.push(b);
2265
2422
  continue;
2266
2423
  }
@@ -2282,7 +2439,14 @@ function federation(mfUserOptions) {
2282
2439
  local: b.local,
2283
2440
  funcBody: renamedFunc
2284
2441
  });
2285
- } else nonInlineable.push(resolveProxyAlias(b, proxyLocal, code, fullImport));
2442
+ claimedLocals.add(b.local);
2443
+ } else {
2444
+ const unavailableLocals = new Set(claimedLocals);
2445
+ pendingLocals.forEach((local) => unavailableLocals.add(local));
2446
+ const resolvedBinding = resolveProxyAlias(b, proxyLocal, code, fullImport, unavailableLocals);
2447
+ claimedLocals.add(resolvedBinding.local);
2448
+ nonInlineable.push(resolvedBinding);
2449
+ }
2286
2450
  }
2287
2451
  const hasRenamedAlias = nonInlineable.some((b) => bindings.find((ob) => ob.imported === b.imported)?.local !== b.local);
2288
2452
  if (inlineable.length === 0 && !hasRenamedAlias) continue;
@@ -2296,6 +2460,28 @@ function federation(mfUserOptions) {
2296
2460
  }
2297
2461
  }
2298
2462
  },
2463
+ {
2464
+ name: "module-federation-strip-empty-preload-helper",
2465
+ enforce: "post",
2466
+ apply: "build",
2467
+ renderChunk(code, chunk) {
2468
+ if (!isFederationControlChunk(chunk.fileName, filename)) return;
2469
+ const nextCode = sanitizeFederationControlChunk(code, chunk.fileName, filename);
2470
+ return nextCode === code ? null : {
2471
+ code: nextCode,
2472
+ map: null
2473
+ };
2474
+ },
2475
+ writeBundle(outputOptions, bundle) {
2476
+ if (!outputOptions.dir) return;
2477
+ for (const chunk of Object.values(bundle)) {
2478
+ if (chunk.type !== "chunk") continue;
2479
+ if (!isFederationControlChunk(chunk.fileName, filename)) continue;
2480
+ const outputPath = path.join(outputOptions.dir, chunk.fileName);
2481
+ writeFileSync(outputPath, sanitizeFederationControlChunk(readFileSync(outputPath, "utf-8"), chunk.fileName, filename));
2482
+ }
2483
+ }
2484
+ },
2299
2485
  {
2300
2486
  name: "module-federation-dev-await-shared-init",
2301
2487
  apply: "serve",
@@ -2330,7 +2516,7 @@ function federation(mfUserOptions) {
2330
2516
  config(config, { command: _command }) {
2331
2517
  const isRolldown = !!this?.meta?.rolldownVersion;
2332
2518
  let implementation = options.implementation;
2333
- if (isRolldown) implementation = implementation.replace(/\.cjs\.cjs$/, ".esm.js");
2519
+ if (isRolldown) implementation = implementation.replace(/\.cjs(\.js)?$/, ".js");
2334
2520
  config.resolve.alias.push({
2335
2521
  find: "@module-federation/runtime",
2336
2522
  replacement: implementation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/vite",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "Vite plugin for Module Federation",
5
5
  "type": "module",
6
6
  "main": "./lib/index.cjs",
@@ -66,9 +66,9 @@
66
66
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
67
67
  },
68
68
  "dependencies": {
69
- "@module-federation/dts-plugin": "2.2.0",
70
- "@module-federation/runtime": "2.0.1",
71
- "@module-federation/sdk": "2.0.1",
69
+ "@module-federation/dts-plugin": "2.2.2",
70
+ "@module-federation/runtime": "2.2.2",
71
+ "@module-federation/sdk": "2.2.2",
72
72
  "@rollup/pluginutils": "^5.3.0",
73
73
  "defu": "^6.1.4",
74
74
  "estree-walker": "^3.0.3",
@@ -85,7 +85,7 @@
85
85
  "oxfmt": "^0.36.0",
86
86
  "rollup": "^4.47.1",
87
87
  "tsdown": "^0.21.0",
88
- "vite": "^7.3.1",
88
+ "vite": "^8.0.0",
89
89
  "vitest": "^4.0.18"
90
90
  }
91
91
  }