@module-federation/vite 1.16.12 → 1.16.14

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 CHANGED
@@ -6,22 +6,15 @@
6
6
 
7
7
  [Read the announcement](https://www.linkedin.com/posts/voidzero_github-module-federationvite-vite-plugin-activity-7449452398202241024-JyAL).
8
8
 
9
- <div align="center">
10
- <h2>Gold Sponsor</h2>
11
- <a href="https://www.zephyr-cloud.io/">
12
- <img src="./docs/sponsors/zephyr-logo.png" alt="Zephyr Cloud" width="360" />
13
- </a>
14
-
15
- <h3>Sponsors</h3>
16
- <p>
17
- <a href="https://github.com/thecoder93">
18
- <img src="https://github.com/thecoder93.png?size=96" alt="thecoder93" width="64" height="64" />
19
- </a>
20
- <a href="https://github.com/stephanelgrg">
21
- <img src="https://github.com/stephanelgrg.png?size=96" alt="stephanelgrg" width="64" height="64" />
22
- </a>
23
- </p>
24
- </div>
9
+ <br />
10
+
11
+ <a href="https://github.com/sponsors/gioboa">
12
+ <img src="./docs/sponsors.png" alt="Sponsors" />
13
+ </a>
14
+
15
+ ## Become a sponsor
16
+
17
+ [Support this project on GitHub Sponsors](https://github.com/sponsors/gioboa)
25
18
 
26
19
  ## Reason why 🤔
27
20
 
package/lib/index.js CHANGED
@@ -380,7 +380,9 @@ function normalizeShared(shared) {
380
380
  if (Array.isArray(shared)) shared.forEach((key) => {
381
381
  if (isModuleFederationRuntimePackage(key)) return;
382
382
  const normalizedKey = normalizeSharedKey(key);
383
+ const hadConfiguredPackageSubpath = (result[normalizedKey]?.shareConfig)?.__mfConfiguredPackageSubpath === true;
383
384
  result[normalizedKey] = normalizeShareItem(normalizedKey, normalizedKey);
385
+ if (key.endsWith("/") || hadConfiguredPackageSubpath) result[normalizedKey].shareConfig.__mfConfiguredPackageSubpath = true;
384
386
  explicitSharedKeys.add(normalizedKey);
385
387
  sourceEntries.push([normalizedKey, normalizedKey]);
386
388
  });
@@ -388,7 +390,9 @@ function normalizeShared(shared) {
388
390
  if (isModuleFederationRuntimePackage(key)) return;
389
391
  const normalizedKey = normalizeSharedKey(key);
390
392
  const value = shared[key];
393
+ const hadConfiguredPackageSubpath = (result[normalizedKey]?.shareConfig)?.__mfConfiguredPackageSubpath === true;
391
394
  result[normalizedKey] = normalizeShareItem(normalizedKey, value);
395
+ if (key.endsWith("/") || hadConfiguredPackageSubpath) result[normalizedKey].shareConfig.__mfConfiguredPackageSubpath = true;
392
396
  explicitSharedKeys.add(normalizedKey);
393
397
  sourceEntries.push([normalizedKey, value]);
394
398
  });
@@ -650,7 +654,7 @@ function getExposesCssMapPlaceholder() {
650
654
  function getVirtualExposesId(options) {
651
655
  return `virtual:mf-exposes:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
652
656
  }
653
- function generateExposes(options) {
657
+ function generateExposes(options, remoteDependencyMap = {}, command = "build") {
654
658
  return `
655
659
  const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
656
660
  const injectedCssHrefs = new Set();
@@ -683,8 +687,12 @@ function generateExposes(options) {
683
687
  }
684
688
  injectedCssHrefs.add(href);
685
689
 
690
+ // Check for any existing stylesheet with the same href, not just
691
+ // MF-injected ones. This prevents duplicate <link> tags when Vite's
692
+ // own CSS module injection or MF runtime's createLink has already
693
+ // created a <link rel="stylesheet"> for the same URL.
686
694
  const existingLink = document.querySelector(
687
- \`link[rel="stylesheet"][data-mf-href="\${href}"]\`
695
+ \`link[rel="stylesheet"][href="\${href}"]\`
688
696
  );
689
697
  if (existingLink) {
690
698
  return Promise.resolve();
@@ -694,7 +702,6 @@ function generateExposes(options) {
694
702
  const link = document.createElement("link");
695
703
  link.rel = "stylesheet";
696
704
  link.href = href;
697
- link.setAttribute("data-mf-href", href);
698
705
  link.onload = () => resolve();
699
706
  link.onerror = () => reject(new Error(\`[Module Federation] Failed to load CSS asset: \${href}\`));
700
707
  document.head.appendChild(link);
@@ -705,12 +712,22 @@ function generateExposes(options) {
705
712
 
706
713
  export default {
707
714
  ${Object.keys(options.exposes).map((key) => {
715
+ const remoteDependencyPreloads = (remoteDependencyMap[key] ?? []).map((remoteId) => {
716
+ const virtualRemote = getRemoteVirtualModule(remoteId, command);
717
+ return `import(${JSON.stringify(virtualRemote.getImportId())})
718
+ .then((mod) => mod.__mf_remote_pending)`;
719
+ }).join(",");
708
720
  return `
709
721
  ${JSON.stringify(key)}: async () => {
710
722
  await injectCssAssets(${JSON.stringify(key)})
723
+ await Promise.all([${remoteDependencyPreloads}])
711
724
  const importModule = await importExposedModule(
712
725
  () => import(${JSON.stringify(options.exposes[key].import)})
713
726
  )
727
+ const dependencyPending = importModule && importModule.__mf_remote_dependency_pending;
728
+ if (dependencyPending && typeof dependencyPending.then === "function") {
729
+ await dependencyPending;
730
+ }
714
731
  const exportModule = {}
715
732
  Object.assign(exportModule, importModule)
716
733
  Object.defineProperty(exportModule, "__esModule", {
@@ -1129,6 +1146,15 @@ function getWorkspacePackageJson(pkg) {
1129
1146
  fromResolvedEntry: resolved
1130
1147
  })?.packageJson;
1131
1148
  }
1149
+ function getSharedDependencyGraphPackageJson(pkg) {
1150
+ const installedPackageJson = getInstalledPackageJson(pkg, { packageName: getPackageName(pkg) })?.packageJson;
1151
+ if (installedPackageJson) return installedPackageJson;
1152
+ try {
1153
+ const packageJsonPath = createRequire$1(pathToFileURL(path$1.join(getPackageDetectionCwd(), "package.json"))).resolve(`${getPackageName(pkg)}/package.json`);
1154
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1155
+ } catch {}
1156
+ return getWorkspacePackageJson(pkg);
1157
+ }
1132
1158
  function getDependencyNames(packageJson) {
1133
1159
  if (!packageJson) return [];
1134
1160
  const names = /* @__PURE__ */ new Set();
@@ -1143,7 +1169,7 @@ function getDependencyNames(packageJson) {
1143
1169
  }
1144
1170
  return Array.from(names);
1145
1171
  }
1146
- function isWorkspaceSingletonConsumedByPeer(pkg) {
1172
+ function isSharedSingletonConsumedByPeer(pkg) {
1147
1173
  const shared = getNormalizeModuleFederationOptions()?.shared || {};
1148
1174
  const sharedKeyByPackageName = /* @__PURE__ */ new Map();
1149
1175
  Object.entries(shared).filter(([, item]) => item.shareConfig.singleton === true).forEach(([key]) => {
@@ -1151,7 +1177,7 @@ function isWorkspaceSingletonConsumedByPeer(pkg) {
1151
1177
  if (!sharedKeyByPackageName.get(packageName) || key === packageName) sharedKeyByPackageName.set(packageName, key);
1152
1178
  });
1153
1179
  const reachesPkg = (current, seen) => {
1154
- const packageJson = getWorkspacePackageJson(current);
1180
+ const packageJson = getSharedDependencyGraphPackageJson(current);
1155
1181
  for (const dependency of getDependencyNames(packageJson)) {
1156
1182
  const sharedDependency = sharedKeyByPackageName.get(dependency);
1157
1183
  if (!sharedDependency) continue;
@@ -1164,6 +1190,10 @@ function isWorkspaceSingletonConsumedByPeer(pkg) {
1164
1190
  };
1165
1191
  return Array.from(sharedKeyByPackageName.values()).some((sharedPkg) => sharedPkg !== pkg && reachesPkg(sharedPkg, new Set([sharedPkg])));
1166
1192
  }
1193
+ function isRemoteOnlyContainer() {
1194
+ const options = getNormalizeModuleFederationOptions();
1195
+ return Object.keys(options.exposes || {}).length > 0 && Object.keys(options.remotes || {}).length === 0;
1196
+ }
1167
1197
  function tryResolveImportFromPackageRoot(pkg, root) {
1168
1198
  try {
1169
1199
  return resolveWorkspaceEsmEntry(pkg, createRequire$1(pathToFileURL(path$1.join(root, "package.json"))).resolve(pkg), root);
@@ -1245,7 +1275,7 @@ function writePreBuildLibPath(pkg, shareItem) {
1245
1275
  const __mfPrebuildExports = __mfPrebuildNamespace;
1246
1276
  ${declarations}
1247
1277
  ${namedExportLine}
1248
- export default __mfPrebuildExports;
1278
+ export default __mfPrebuildNamespace.default ?? __mfPrebuildNamespace;
1249
1279
  `, true);
1250
1280
  return;
1251
1281
  }
@@ -1317,37 +1347,34 @@ function generateEagerWorkspaceSingletonExports(namedExports, importSource, cach
1317
1347
  const __mf_default = exportModule.default ?? exportModule;${namedExportAssignments}
1318
1348
  export { __mf_default as default };${namedExportLine}`;
1319
1349
  }
1320
- function generateLazyWorkspaceSingletonExports(namedExports, importSource, cacheDescriptor, eagerLocalFallback) {
1350
+ function generateLazyWorkspaceSingletonExports(namedExports, importSource, cacheDescriptor) {
1321
1351
  const namedExportVars = namedExports.map((_name, i) => `__mf_${i}`);
1322
1352
  const declarations = namedExports.length > 0 ? ["let __mf_default;", ...namedExportVars.map((name) => `let ${name};`)].join("\n ") : "let __mf_default;";
1323
1353
  const assignments = namedExports.length > 0 ? [...namedExports.map((name, i) => `${namedExportVars[i]} = mod[${escapeGeneratedStringLiteral(name)}];`), "__mf_default = mod.default ?? mod;"].join("\n ") : "__mf_default = mod.default ?? mod;";
1324
1354
  const namedExportLine = namedExports.length > 0 ? `\n export { ${namedExports.map((name, i) => `${namedExportVars[i]} as ${name}`).join(", ")} };` : "";
1325
- const applyLocalFallback = `exportModule = __mfNormalizeShareModule(__mfLocalShare);
1326
- __mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
1327
- __mfApplyLazyShareExports(exportModule);`;
1328
- const body = `${declarations}
1355
+ return `${declarations}
1329
1356
  const __mfApplyLazyShareExports = (mod) => {
1330
1357
  ${assignments}
1331
1358
  };
1332
1359
  let exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
1333
1360
  if (exportModule === undefined) {
1334
- ${eagerLocalFallback ? applyLocalFallback : `if (import.meta.env.SSR) {
1335
- ${applyLocalFallback}
1361
+ if (import.meta.env.SSR) {
1362
+ ${`exportModule = __mfNormalizeShareModule(__mfLocalShare);
1363
+ __mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
1364
+ __mfApplyLazyShareExports(exportModule);`}
1336
1365
  } else {
1337
- initPromise.then(() =>
1366
+ (__mfModuleCache.pendingShareLoads ||= []).push(initPromise.then(() =>
1338
1367
  import(${escapeGeneratedStringLiteral(importSource)}).then((mod) => {
1339
1368
  exportModule = __mfNormalizeShareModule(mod);
1340
1369
  __mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
1341
1370
  __mfApplyLazyShareExports(exportModule);
1342
1371
  })
1343
- );
1344
- }`}
1372
+ ));
1373
+ }
1345
1374
  } else {
1346
1375
  __mfApplyLazyShareExports(exportModule);
1347
1376
  }
1348
1377
  export { __mf_default as default };${namedExportLine}`;
1349
- return eagerLocalFallback ? `import * as __mfLocalShare from ${escapeGeneratedStringLiteral(importSource)};
1350
- ${body}` : body;
1351
1378
  }
1352
1379
  const WORKSPACE_SINGLETON_SSR_LOCAL_SHARE = "__mfNormalizeShareModule(__mfLocalShare)";
1353
1380
  function prependWorkspaceSingletonSsrImport(code) {
@@ -1367,13 +1394,13 @@ function generateDeferredHostProvidedExports(namedExports, pkg, cacheDescriptor)
1367
1394
  };
1368
1395
  let exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
1369
1396
  if (exportModule === undefined) {
1370
- initPromise.then(() => {
1397
+ (__mfModuleCache.pendingShareLoads ||= []).push(initPromise.then(() => {
1371
1398
  exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
1372
1399
  if (exportModule === undefined) {
1373
1400
  throw new Error("[Module Federation] Shared module ${pkg} was imported before federation bootstrap finished.");
1374
1401
  }
1375
1402
  __mfApplyHostProvidedExports(exportModule);
1376
- });
1403
+ }));
1377
1404
  } else {
1378
1405
  __mfApplyHostProvidedExports(exportModule);
1379
1406
  }
@@ -1421,17 +1448,21 @@ function writeLoadShareModule(pkg, shareItem, command, _isRolldown) {
1421
1448
  const devImportSource = concreteSharedImportSource || pkg;
1422
1449
  const localProviderPath = getLocalProviderImportPath(pkg);
1423
1450
  const isWorkspacePackage = isWorkspacePackageEntry(pkg, localProviderPath) || isWorkspacePackageEntry(pkg, concreteSharedImportSource);
1424
- const lazyLocalFallbackSource = concreteSharedImportSource || localProviderPath || sharedImportSource;
1451
+ const lazyLocalFallbackSource = command !== "build" ? concreteSharedImportSource || localProviderPath || devImportSource : concreteSharedImportSource || localProviderPath || sharedImportSource;
1425
1452
  const skipServePrebuildWarmup = command !== "build" && (pkg === "lit" || pkg.startsWith("lit/"));
1426
1453
  const isWorkspaceSingleton = isWorkspacePackage && shareItem.shareConfig.singleton === true;
1427
- const usesEagerWorkspaceFallback = isWorkspaceSingleton && isWorkspaceSingletonConsumedByPeer(pkg);
1454
+ const isDefaultShareScope = shareItem.scope === void 0 || shareItem.scope === "default" || Array.isArray(shareItem.scope) && shareItem.scope[0] === "default";
1455
+ const usesDeferredSingletonFallback = isWorkspaceSingleton || command !== "build" && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true || command === "build" && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true && !isDefaultShareScope;
1456
+ const isConsumedByPeerSingleton = isSharedSingletonConsumedByPeer(pkg);
1457
+ const usesEntryInjectedRemoteFallback = command !== "build" && !isWorkspaceSingleton && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true && getNormalizeModuleFederationOptions().hostInitInjectLocation === "entry" && isConsumedByPeerSingleton;
1458
+ const usesEagerWorkspaceFallback = isWorkspaceSingleton && isConsumedByPeerSingleton;
1428
1459
  const namedExports = getSharedNamedExports(pkg, shareItem);
1429
1460
  let exportLine;
1430
1461
  let initBlock = "";
1431
- if (usesEagerWorkspaceFallback) exportLine = generateEagerWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
1432
- else if (isWorkspaceSingleton) {
1462
+ if (usesEagerWorkspaceFallback || usesEntryInjectedRemoteFallback) exportLine = generateEagerWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
1463
+ else if (usesDeferredSingletonFallback) {
1433
1464
  importLine = `${getRuntimeInitPromiseBootstrapCode()}\n ${importLine}`;
1434
- exportLine = generateLazyWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor, command !== "build");
1465
+ exportLine = generateLazyWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
1435
1466
  } else if (namedExports.length > 0) {
1436
1467
  const destructure = `const { ${namedExports.map((name, i) => `${name}: __mf_${i}`).join(", ")} } = exportModule;`;
1437
1468
  const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
@@ -1452,9 +1483,9 @@ function writeLoadShareModule(pkg, shareItem, command, _isRolldown) {
1452
1483
  initBlock = `exportModule = __mfNormalizeShareModule(__mfLocalShare);
1453
1484
  __mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);`;
1454
1485
  }
1455
- const prebuildImportLine = isWorkspaceSingleton || isWorkspacePackage && command !== "build" ? "" : `import * as __mfLocalShare from ${escapeGeneratedStringLiteral(skipServePrebuildWarmup ? devImportSource : sharedImportSource)};`;
1456
- const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1457
- const moduleBody = isWorkspaceSingleton ? `
1486
+ const prebuildImportLine = usesDeferredSingletonFallback || isWorkspacePackage && command !== "build" ? "" : `import * as __mfLocalShare from ${escapeGeneratedStringLiteral(skipServePrebuildWarmup ? devImportSource : sharedImportSource)};`;
1487
+ const devDynamicImportLine = isWorkspacePackage ? "" : usesDeferredSingletonFallback ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1488
+ const moduleBody = usesDeferredSingletonFallback ? `
1458
1489
  ${prebuildImportLine}
1459
1490
  ${devDynamicImportLine}
1460
1491
  ${importLine}
@@ -1587,29 +1618,14 @@ function generateLocalSharedImportMap() {
1587
1618
  }
1588
1619
  `;
1589
1620
  }
1590
- function generateUsedSharedPreloadConfig() {
1591
- return `{
1592
- ${getOrderedUsedShares().map((pkg) => {
1593
- const shareItem = getShareItemForPreload(pkg);
1594
- if (!shareItem) return null;
1595
- return `${JSON.stringify(pkg)}: {
1596
- version: ${JSON.stringify(shareItem.version)},
1597
- scope: ${JSON.stringify(shareItem.scope)},
1598
- shareConfig: {
1599
- singleton: ${shareItem.shareConfig.singleton},
1600
- requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)},
1601
- strictVersion: ${shareItem.shareConfig.strictVersion},
1602
- ${shareItem.shareConfig.import === false ? "import: false," : ""}
1603
- }
1604
- }`;
1605
- }).filter((item) => item !== null).join(",\n")}
1606
- }`;
1607
- }
1608
1621
  function getOrderedUsedShares() {
1609
1622
  const shares = new Set(getUsedShares());
1610
1623
  try {
1611
1624
  Object.keys(getNormalizeModuleFederationOptions().shared).forEach((pkg) => {
1612
- if (!pkg.endsWith("/")) shares.add(pkg);
1625
+ if (!pkg.endsWith("/")) {
1626
+ shares.add(pkg);
1627
+ return;
1628
+ }
1613
1629
  });
1614
1630
  } catch {}
1615
1631
  return orderSharedDependenciesFirst(Array.from(shares).sort((a, b) => {
@@ -1712,12 +1728,31 @@ const sharedProviderSelectionHelperCode = `const __mfOriginalProviderKey = Symbo
1712
1728
  function hasImportFalseShared$1(options) {
1713
1729
  return Object.values(options.shared ?? {}).some((share) => share?.shareConfig?.import === false);
1714
1730
  }
1715
- function generateDirectSharedCacheSeedCode(command = "build") {
1716
- return getOrderedUsedShares().map((pkg) => {
1717
- const shareItem = getShareItemForPreload(pkg);
1718
- if (!shareItem || shareItem.shareConfig.import === false) return null;
1719
- return generateSharedCacheSeedItem(pkg, shareItem, command === "serve" ? getLocalSharedPackagePath(pkg, shareItem) : getDirectSharedCacheSeedImportPath(pkg, shareItem));
1720
- }).filter((item) => item !== null).join("\n");
1731
+ function generateRuntimeSharedCacheSeedCode() {
1732
+ return `
1733
+ for (const [pkg, share] of Object.entries(usedShared)) {
1734
+ const cacheDescriptor = __mfGetSharedCacheDescriptor(pkg, share.shareConfig?.singleton, share.version, share.scope);
1735
+ if (share.shareConfig?.import === false || __mfReadSharedCache(__mfModuleCache.share, cacheDescriptor) !== undefined) {
1736
+ continue;
1737
+ }
1738
+ const singletonCacheDescriptor = __mfGetSharedCacheDescriptor(pkg, true, share.version, share.scope);
1739
+ const singletonModule = __mfReadSharedCache(__mfModuleCache.share, singletonCacheDescriptor);
1740
+ if (singletonModule !== undefined) {
1741
+ __mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, singletonModule);
1742
+ continue;
1743
+ }
1744
+ const factory = await share.get();
1745
+ const mod = typeof factory === "function" ? factory() : factory;
1746
+ const resolved = await Promise.resolve(mod);
1747
+ ${normalizeRuntimeShareCode}
1748
+ const normalizedModule = __mfNormalizeRuntimeShare(resolved);
1749
+ const exportModule = normalizedModule === resolved ? {...resolved} : normalizedModule;
1750
+ Object.defineProperty(exportModule, "__esModule", {
1751
+ value: true,
1752
+ enumerable: false
1753
+ });
1754
+ __mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, exportModule);
1755
+ }`;
1721
1756
  }
1722
1757
  function getBrowserImportPath(importPath) {
1723
1758
  if (/^(?:[a-zA-Z]:[\\/]|\/)/.test(importPath) && !importPath.startsWith("/@")) return `/@fs/${importPath}`;
@@ -1867,7 +1902,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1867
1902
  __mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, singletonModule);
1868
1903
  }
1869
1904
  }
1870
- ${generateDirectSharedCacheSeedCode(command)}
1905
+ ${generateRuntimeSharedCacheSeedCode()}
1871
1906
  const __browserPlugins = [${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => `${item[0]}(${item[2]})`).join(", ")}];
1872
1907
  const __ssrPlugins = typeof globalThis.window === 'undefined'
1873
1908
  ? await Promise.all([${pluginImportNames.filter((item) => isSsrOnlyPlugin(item[1])).map((item) => {
@@ -1890,7 +1925,6 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1890
1925
  if (initScope.indexOf(initToken) >= 0) return;
1891
1926
  initScope.push(initToken);
1892
1927
  initRes.initShareScopeMap('${options.shareScope}', shared);
1893
- initResolve(initRes)
1894
1928
  try {
1895
1929
  await retrySharedInit(async () => {
1896
1930
  await Promise.all(await initRes.initializeSharing('${options.shareScope}', {
@@ -1914,6 +1948,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1914
1948
  const resolved = await Promise.resolve(mod);
1915
1949
  __mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, __mfNormalizeRuntimeShare(resolved));
1916
1950
  }
1951
+ initResolve(initRes)
1917
1952
  return initRes
1918
1953
  }
1919
1954
 
@@ -1943,7 +1978,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
1943
1978
  ${generateHostAutoInitSharedCacheSeedCode(_command)}
1944
1979
  const remoteEntry = await import(${remoteEntryImport});
1945
1980
  const runtime = await remoteEntry.init();
1946
- const usedShared = ${generateUsedSharedPreloadConfig()};
1981
+ const {usedShared} = await import("${getLocalSharedImportMapPath()}");
1947
1982
  ${normalizeRuntimeShareCode}
1948
1983
  ${shouldPreloadShares ? `
1949
1984
  for (const [pkg, share] of Object.entries(usedShared)) {
@@ -2272,10 +2307,10 @@ function injectHostInitPreloads(html, bundle, resolvePath) {
2272
2307
  function getFirstHtmlEntryFile(entryFiles) {
2273
2308
  return entryFiles.find((file) => file.endsWith(".html"));
2274
2309
  }
2275
- function stripQueryAndHash(file) {
2310
+ function stripQueryAndHash$1(file) {
2276
2311
  return file.split(/[?#]/)[0];
2277
2312
  }
2278
- function resolveDevHashEntryFileName(fileName) {
2313
+ function resolveDevHashEntryFileName$1(fileName) {
2279
2314
  if (!fileName.includes("[hash")) return fileName;
2280
2315
  const normalized = fileName.replace(/(?:[._-]?\[hash(?::\d+)?\])/g, "");
2281
2316
  const baseName = path$1.basename(normalized);
@@ -2419,6 +2454,9 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
2419
2454
  await __mfHostInit.__tla;
2420
2455
  const { initHost } = __mfHostInit;
2421
2456
  ${preloadBlock}
2457
+ if (__mfModuleCache.pendingShareLoads) {
2458
+ await Promise.all(__mfModuleCache.pendingShareLoads);
2459
+ }
2422
2460
  })().then(() => ${importExpression(entrySrc)});
2423
2461
  `;
2424
2462
  return [
@@ -2461,7 +2499,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
2461
2499
  const scriptRegex = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>/gi;
2462
2500
  let match;
2463
2501
  while ((match = scriptRegex.exec(htmlContent)) !== null) {
2464
- const scriptSrc = stripQueryAndHash(match[1]);
2502
+ const scriptSrc = stripQueryAndHash$1(match[1]);
2465
2503
  if (/^(?:[a-z]+:)?\/\//i.test(scriptSrc)) continue;
2466
2504
  addEntryFile(scriptSrc);
2467
2505
  addEntryFile(scriptSrc.startsWith("/") ? path$1.resolve(viteConfig.root, scriptSrc.slice(1)) : path$1.resolve(path$1.dirname(htmlPath), scriptSrc));
@@ -2504,7 +2542,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
2504
2542
  next();
2505
2543
  return;
2506
2544
  }
2507
- const devFileName = resolveDevHashEntryFileName(fileName);
2545
+ const devFileName = resolveDevHashEntryFileName$1(fileName);
2508
2546
  if (devFileName !== fileName && req.url?.startsWith((viteConfig.base + devFileName).replace(/^\/?/, "/"))) req.url = req.url.replace(devFileName, fileName);
2509
2547
  if (req.url && req.url.startsWith((viteConfig.base + fileName).replace(/^\/?/, "/"))) req.url = devEntryPath;
2510
2548
  next();
@@ -4088,8 +4126,60 @@ function pluginModuleParseEnd_default(excludeFn, options) {
4088
4126
  }
4089
4127
  //#endregion
4090
4128
  //#region src/plugins/pluginProxyRemoteEntry.ts
4129
+ function resolveDevHashEntryFileName(fileName) {
4130
+ if (!fileName.includes("[hash")) return fileName;
4131
+ const normalized = fileName.replace(/(?:[._-]?\[hash(?::\d+)?\])/g, "");
4132
+ const baseName = path$1.basename(normalized);
4133
+ return path$1.extname(baseName) ? normalized : `${normalized}.js`;
4134
+ }
4091
4135
  function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposesId }) {
4092
4136
  let viteConfig, _command, root;
4137
+ let exposeRemoteDependencies = {};
4138
+ function isRemoteImport(source) {
4139
+ return Object.keys(options.remotes).some((name) => source === name || source.startsWith(name + "/"));
4140
+ }
4141
+ function collectImportSources(code) {
4142
+ const sources = /* @__PURE__ */ new Set();
4143
+ for (const match of code.matchAll(/(?:^|[;\n\r])\s*import\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g)) {
4144
+ const source = match[1] || match[2];
4145
+ if (source) sources.add(source);
4146
+ }
4147
+ return Array.from(sources).sort();
4148
+ }
4149
+ function shouldScanResolvedImport(id) {
4150
+ if (!id || id.includes("\0")) return false;
4151
+ if (id.includes("/node_modules/") || id.includes("\\node_modules\\")) return false;
4152
+ return /\.(?:[cm]?[jt]sx?|vue|svelte)(?:\?|$)/.test(id);
4153
+ }
4154
+ async function collectRemoteDependencies(ctx, id, seen = /* @__PURE__ */ new Set()) {
4155
+ if (seen.has(id) || !shouldScanResolvedImport(id)) return [];
4156
+ seen.add(id);
4157
+ let code;
4158
+ try {
4159
+ code = readFileSync$1(id, "utf8");
4160
+ } catch {
4161
+ return [];
4162
+ }
4163
+ const dependencies = /* @__PURE__ */ new Set();
4164
+ for (const source of collectImportSources(code)) {
4165
+ if (isRemoteImport(source)) {
4166
+ dependencies.add(source);
4167
+ continue;
4168
+ }
4169
+ const resolved = await ctx.resolve(source, id);
4170
+ if (!resolved?.id || !shouldScanResolvedImport(resolved.id)) continue;
4171
+ for (const dependency of await collectRemoteDependencies(ctx, resolved.id, seen)) dependencies.add(dependency);
4172
+ }
4173
+ return Array.from(dependencies).sort();
4174
+ }
4175
+ async function refreshExposeRemoteDependencies(ctx) {
4176
+ const next = {};
4177
+ for (const [exposeKey, expose] of Object.entries(options.exposes)) {
4178
+ const resolved = await ctx.resolve(expose.import);
4179
+ next[exposeKey] = resolved?.id ? await collectRemoteDependencies(ctx, resolved.id) : [];
4180
+ }
4181
+ exposeRemoteDependencies = next;
4182
+ }
4093
4183
  return {
4094
4184
  name: "proxyRemoteEntry",
4095
4185
  enforce: "post",
@@ -4101,6 +4191,7 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
4101
4191
  _command = command;
4102
4192
  },
4103
4193
  async buildStart() {
4194
+ await refreshExposeRemoteDependencies(this);
4104
4195
  if (_command !== "build") return;
4105
4196
  for (const expose of Object.values(options.exposes)) {
4106
4197
  const resolved = await this.resolve(expose.import);
@@ -4122,19 +4213,19 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
4122
4213
  },
4123
4214
  load(id) {
4124
4215
  if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
4125
- if (id === virtualExposesId) return generateExposes(options);
4216
+ if (id === virtualExposesId) return generateExposes(options, exposeRemoteDependencies, _command);
4126
4217
  if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
4127
4218
  },
4128
4219
  transform(code, id) {
4129
4220
  return mapCodeToCodeWithSourcemap((() => {
4130
4221
  if (!filterId(id)) return;
4131
4222
  if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
4132
- if (id === virtualExposesId) return generateExposes(options);
4223
+ if (id === virtualExposesId) return generateExposes(options, exposeRemoteDependencies, _command);
4133
4224
  if (id.includes(getHostAutoInitPath())) {
4134
4225
  if (_command === "serve") {
4135
4226
  const host = typeof viteConfig.server?.host === "string" && viteConfig.server.host !== "0.0.0.0" ? viteConfig.server.host : "localhost";
4136
4227
  const resolvedPublicPath = resolvePublicPath(options, viteConfig.base);
4137
- const publicPath = JSON.stringify((resolvedPublicPath === "auto" ? "/" : resolvedPublicPath) + options.filename);
4228
+ const publicPath = JSON.stringify((resolvedPublicPath === "auto" ? "/" : resolvedPublicPath) + resolveDevHashEntryFileName(options.filename));
4138
4229
  const fallbackOrigin = `//${host}:${viteConfig.server?.port}`;
4139
4230
  const ssrRemoteEntry = "data:text/javascript," + encodeURIComponent("export async function init(){return {loadRemote:async()=>({}),loadShare:async()=>({})}}");
4140
4231
  return `
@@ -4573,26 +4664,7 @@ function applyRewrites(code, imports, id) {
4573
4664
  const ms = new CodeRewriter(code);
4574
4665
  let changed = false;
4575
4666
  let counter = 0;
4576
- let namedProxyHelperDeclared = false;
4577
4667
  const dependencyPendingIds = [];
4578
- const namedProxyHelper = `function __mfCreateNamedRemoteProxy(ns, key) {
4579
- const target = function (...args) {
4580
- const value = ns[key];
4581
- return typeof value === "function" ? value.apply(this, args) : value;
4582
- };
4583
- return new Proxy(target, {
4584
- get(_target, prop) {
4585
- if (prop === "then") return undefined;
4586
- const value = ns[key];
4587
- if (prop === Symbol.toPrimitive) return () => value;
4588
- const item = value == null ? undefined : value[prop];
4589
- return typeof item === "function" ? item.bind(value) : item;
4590
- },
4591
- apply(target, thisArg, args) {
4592
- return target.apply(thisArg, args);
4593
- }
4594
- });
4595
- }`;
4596
4668
  for (const imp of imports) switch (imp.kind) {
4597
4669
  case "static": {
4598
4670
  const src = JSON.stringify(imp.source);
@@ -4610,20 +4682,8 @@ function applyRewrites(code, imports, id) {
4610
4682
  importParts.push(`__mf_remote_pending as ${pendingId}`);
4611
4683
  let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
4612
4684
  if (imp.named.length > 0) {
4613
- const isProxyId = `__mf_is_proxy_${counter++}`;
4614
- const tempNames = imp.named.map((_s) => `__mf_named_${counter++}`);
4615
- const destructParts = imp.named.map((s, index) => `${s.imported}: ${tempNames[index]}`);
4616
- const bindingLines = imp.named.map((s, index) => {
4617
- const temp = tempNames[index];
4618
- return `const ${s.local} = ${isProxyId} ? __mfCreateNamedRemoteProxy(${nsId}, ${JSON.stringify(s.imported)}) : ${temp};`;
4619
- });
4620
- if (!namedProxyHelperDeclared) {
4621
- rewrite += `\n${namedProxyHelper}`;
4622
- namedProxyHelperDeclared = true;
4623
- }
4624
- rewrite += `\nconst ${isProxyId} = ${nsId} && ${nsId}.__mf_is_remote_proxy;`;
4625
- rewrite += `\nconst { ${destructParts.join(", ")} } = ${isProxyId} ? {} : ${nsId};`;
4626
- rewrite += `\n${bindingLines.join("\n")}`;
4685
+ const destructParts = imp.named.map((s) => `${s.imported}: ${s.local}`);
4686
+ rewrite += `\nconst { ${destructParts.join(", ")} } = ${nsId};`;
4627
4687
  }
4628
4688
  ms.overwrite(imp.start, imp.end, rewrite);
4629
4689
  }
@@ -4880,6 +4940,104 @@ function pluginRemoteNamedExports(options) {
4880
4940
  }
4881
4941
  //#endregion
4882
4942
  //#region src/plugins/pluginSSRRemoteEntry.ts
4943
+ const MAX_RUNNER_BODY_BYTES = 1024 * 1024;
4944
+ const ALLOWED_RUNNER_INVOKE_NAMES = new Set(["fetchModule", "getBuiltins"]);
4945
+ const VITE_FS_PREFIX = "/@fs/";
4946
+ function isPlainObject(value) {
4947
+ return !!value && typeof value === "object" && !Array.isArray(value);
4948
+ }
4949
+ function stripQueryAndHash(id) {
4950
+ const queryIndex = id.indexOf("?");
4951
+ const hashIndex = id.indexOf("#");
4952
+ const endIndex = queryIndex === -1 ? hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
4953
+ return endIndex === -1 ? id : id.slice(0, endIndex);
4954
+ }
4955
+ function decodeRunnerFilePath(filePath) {
4956
+ try {
4957
+ return decodeURIComponent(filePath);
4958
+ } catch {
4959
+ return;
4960
+ }
4961
+ }
4962
+ function hasRelativeTraversal(id) {
4963
+ return id.split(/[\\/]+/).includes("..");
4964
+ }
4965
+ function getRealPathIfExists(filePath) {
4966
+ try {
4967
+ return fs$1.realpathSync.native(filePath);
4968
+ } catch {
4969
+ return;
4970
+ }
4971
+ }
4972
+ function isPathWithinDirectory(filePath, directory) {
4973
+ const realFilePath = getRealPathIfExists(filePath) ?? path$1.resolve(filePath);
4974
+ const realDirectory = getRealPathIfExists(directory) ?? path$1.resolve(directory);
4975
+ const relative = path$1.relative(realDirectory, realFilePath);
4976
+ return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
4977
+ }
4978
+ function getRunnerAllowedDirectories(config) {
4979
+ return [config.root, ...config.server?.fs?.allow ?? []].map((directory) => path$1.resolve(directory));
4980
+ }
4981
+ function isPathWithinAllowedDirectories(filePath, allowedDirectories) {
4982
+ return allowedDirectories.some((directory) => isPathWithinDirectory(filePath, directory));
4983
+ }
4984
+ function isSafeRunnerFetchModuleId(id, config) {
4985
+ if (typeof id !== "string" || !id || id.includes("\0")) return false;
4986
+ const decoded = decodeViteId(id).replace(/^\0+/, "");
4987
+ if (!decoded || decoded.startsWith("virtual:")) return !!decoded;
4988
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(decoded) || decoded.startsWith("//")) return false;
4989
+ const cleanId = decodeRunnerFilePath(stripQueryAndHash(decoded));
4990
+ if (!cleanId || hasRelativeTraversal(cleanId)) return false;
4991
+ const allowedDirectories = getRunnerAllowedDirectories(config);
4992
+ if (cleanId.startsWith(VITE_FS_PREFIX)) {
4993
+ const fsPath = cleanId.slice(5);
4994
+ return path$1.isAbsolute(fsPath) && isPathWithinAllowedDirectories(fsPath, allowedDirectories);
4995
+ }
4996
+ if (path$1.isAbsolute(cleanId)) {
4997
+ if (isPathWithinAllowedDirectories(cleanId, allowedDirectories)) return true;
4998
+ return !fs$1.existsSync(cleanId);
4999
+ }
5000
+ return true;
5001
+ }
5002
+ function isRunnerInvokePayload(payload, config) {
5003
+ if (!payload || typeof payload !== "object") return false;
5004
+ if (payload.type !== "custom" || payload.event !== "vite:invoke") return false;
5005
+ const data = payload.data;
5006
+ if (!data || typeof data !== "object") return false;
5007
+ const name = data.name;
5008
+ const args = data.data;
5009
+ if (typeof name !== "string" || !ALLOWED_RUNNER_INVOKE_NAMES.has(name) || !Array.isArray(args)) return false;
5010
+ if (name === "getBuiltins") return args.length === 0;
5011
+ if (args.length < 1 || args.length > 3) return false;
5012
+ const [id, importer, opts] = args;
5013
+ return isSafeRunnerFetchModuleId(id, config) && (importer === void 0 || importer === null || isSafeRunnerFetchModuleId(importer, config)) && (opts === void 0 || isPlainObject(opts));
5014
+ }
5015
+ function readBoundedRunnerBody(req, res) {
5016
+ return new Promise((resolve) => {
5017
+ const chunks = [];
5018
+ let size = 0;
5019
+ let done = false;
5020
+ const fail = (statusCode, message) => {
5021
+ if (done) return;
5022
+ done = true;
5023
+ res.statusCode = statusCode;
5024
+ res.end(message);
5025
+ resolve(void 0);
5026
+ };
5027
+ req.on("data", (chunk) => {
5028
+ if (done) return;
5029
+ size += chunk.length;
5030
+ if (size > MAX_RUNNER_BODY_BYTES) return fail(413, "Payload too large");
5031
+ chunks.push(chunk);
5032
+ });
5033
+ req.on("end", () => {
5034
+ if (done) return;
5035
+ done = true;
5036
+ resolve(Buffer.concat(chunks));
5037
+ });
5038
+ req.on("error", () => fail(400, "Bad request"));
5039
+ });
5040
+ }
4883
5041
  /**
4884
5042
  * Emits a Node-compatible SSR remote entry alongside the browser entry.
4885
5043
  *
@@ -4966,7 +5124,8 @@ function pluginSSRRemoteEntry(options) {
4966
5124
  });
4967
5125
  const ssrEnv = server.environments?.ssr;
4968
5126
  const clientEnv = server.environments?.client;
4969
- if (typeof (ssrEnv?.fetchModule ?? clientEnv?.fetchModule) === "function") server.middlewares.use("/__mf_runner__", async (req, res) => {
5127
+ const runnerEnv = typeof ssrEnv?.hot?.handleInvoke === "function" ? ssrEnv : typeof clientEnv?.hot?.handleInvoke === "function" ? clientEnv : void 0;
5128
+ if (typeof (ssrEnv?.fetchModule ?? clientEnv?.fetchModule) === "function" && runnerEnv) server.middlewares.use("/__mf_runner__", async (req, res) => {
4970
5129
  res.setHeader("Access-Control-Allow-Origin", "*");
4971
5130
  if (req.method === "OPTIONS") {
4972
5131
  res.setHeader("Access-Control-Allow-Methods", "POST");
@@ -4981,46 +5140,37 @@ function pluginSSRRemoteEntry(options) {
4981
5140
  return;
4982
5141
  }
4983
5142
  try {
4984
- const chunks = [];
4985
- await new Promise((resolve, reject) => {
4986
- req.on("data", (chunk) => chunks.push(chunk));
4987
- req.on("end", resolve);
4988
- req.on("error", reject);
4989
- });
4990
- const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
4991
- if (body.name === "getBuiltins") {
4992
- const builtins = (clientEnv ?? ssrEnv)?.config?.resolve?.builtins ?? [];
4993
- res.setHeader("Content-Type", "application/json");
4994
- res.end(JSON.stringify({ result: builtins }));
5143
+ const rawBody = await readBoundedRunnerBody(req, res);
5144
+ if (!rawBody) return;
5145
+ let body;
5146
+ try {
5147
+ body = JSON.parse(rawBody.toString("utf8"));
5148
+ } catch {
5149
+ res.statusCode = 400;
5150
+ res.end(JSON.stringify({ error: { message: "Invalid JSON" } }));
4995
5151
  return;
4996
5152
  }
4997
- if (body.name !== "fetchModule") {
5153
+ if (!isRunnerInvokePayload(body, server.config)) {
4998
5154
  res.statusCode = 400;
4999
- res.end(JSON.stringify({ error: { message: `Unsupported invoke: ${body.name}` } }));
5155
+ res.end(JSON.stringify({ error: { message: "Invalid runner invoke" } }));
5000
5156
  return;
5001
5157
  }
5002
- const [id, importer, opts] = body.data;
5003
- const fetchEnv = ssrEnv ?? clientEnv;
5004
- const fetchFn = fetchEnv.fetchModule.bind(fetchEnv);
5005
- let result;
5006
- try {
5007
- result = await fetchFn(id, importer, opts);
5008
- } catch (fetchErr) {
5009
- const bareId = decodeViteId(id);
5010
- try {
5158
+ let result = await runnerEnv.hot.handleInvoke(body);
5159
+ if ("error" in result && body.data.name === "fetchModule") {
5160
+ const id = body.data.data[0];
5161
+ const bareId = typeof id === "string" ? decodeViteId(id).replace(/^\0/, "") : "";
5162
+ if (bareId && !bareId.startsWith(".") && !bareId.startsWith("/") && !bareId.startsWith("file:") && !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(bareId)) try {
5011
5163
  const { createRequire } = await import("module");
5012
5164
  const path = await import("path");
5013
5165
  const { pathToFileURL } = await import("url");
5014
- result = {
5015
- externalize: pathToFileURL(createRequire(pathToFileURL(path.join(server.config.root, "package.json"))).resolve(bareId.replace(/^\0/, ""))).href,
5166
+ result = { result: {
5167
+ externalize: pathToFileURL(createRequire(pathToFileURL(path.join(server.config.root, "package.json"))).resolve(bareId)).href,
5016
5168
  type: "module"
5017
- };
5018
- } catch {
5019
- throw fetchErr;
5020
- }
5169
+ } };
5170
+ } catch {}
5021
5171
  }
5022
5172
  res.setHeader("Content-Type", "application/json");
5023
- res.end(JSON.stringify({ result }));
5173
+ res.end(JSON.stringify(result));
5024
5174
  } catch (e) {
5025
5175
  res.setHeader("Content-Type", "application/json");
5026
5176
  res.end(JSON.stringify({ error: { message: String(e instanceof Error ? e.message : e) } }));
@@ -5864,7 +6014,9 @@ function federation(mfUserOptions) {
5864
6014
  },
5865
6015
  load(id) {
5866
6016
  if (id.includes("__loadShare__") || id.includes("__loadRemote__")) {
5867
- let code = VirtualModule.findById(id)?.code ?? readFileSync(id, "utf-8");
6017
+ const virtualModule = VirtualModule.findById(id);
6018
+ if (!virtualModule?.code) return null;
6019
+ let code = virtualModule.code;
5868
6020
  const environmentName = this.environment?.name;
5869
6021
  if (environmentName && environmentName !== "client" || !environmentName && isSsrBuild) code = prependWorkspaceSingletonSsrImport(code);
5870
6022
  code = code.replace(/import\s+["'][^"']*__prebuild__[^"']*["']\s*;?/g, "");
@@ -5948,7 +6100,15 @@ function federation(mfUserOptions) {
5948
6100
  options.runtimePlugins.forEach((p) => {
5949
6101
  const pluginPath = typeof p === "string" ? p : p[0];
5950
6102
  if (SSR_ONLY_PLUGINS.has(pluginPath)) return;
5951
- if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:")) config.optimizeDeps.include.push(pluginPath);
6103
+ if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:")) {
6104
+ let optimizeDep = pluginPath;
6105
+ if (pluginPath === "@module-federation/dts-plugin/dynamic-remote-type-hints-plugin") try {
6106
+ optimizeDep = normalizePathForImport(resolveImportPath(pluginPath));
6107
+ } catch {
6108
+ optimizeDep = pluginPath;
6109
+ }
6110
+ config.optimizeDeps.include.push(optimizeDep);
6111
+ }
5952
6112
  });
5953
6113
  if (isRolldown) {
5954
6114
  config.build ??= {};
@@ -0,0 +1,152 @@
1
+ import { SsrEntryHttpError, neutralizeBrowserPreloadHelpers } from "./utils/ssrEntryLoader.js";
2
+ //#region src/utils/ssrVmStrategy.ts
3
+ /**
4
+ * vm.SourceTextModule strategy for loading remote SSR entries.
5
+ *
6
+ * Unlike the temp-file strategy (which rewrites bare shared imports to
7
+ * host-resolved file:// paths at fetch time), this strategy evaluates the
8
+ * remote's ESM graph with `vm.SourceTextModule` in the current context and
9
+ * resolves bare imports through a linker, in order:
10
+ *
11
+ * 1. The host's federation share scope — `instance.loadShare(name)` on the
12
+ * global `__FEDERATION__` instances. This restores real share-scope
13
+ * semantics (version negotiation, loaded-first reuse) on the server.
14
+ * 2. The build-time `resolvedShared` file map (same source as the temp-file
15
+ * strategy) as a fallback when no instance shares the package.
16
+ * 3. Plain host `import(specifier)` for everything else (node builtins,
17
+ * packages the remote expects the host to provide).
18
+ *
19
+ * Requires Node with `--experimental-vm-modules`; callers must check
20
+ * `isVmStrategyAvailable()` and fall back to the temp-file strategy when the
21
+ * API is missing.
22
+ */
23
+ let vmApiPromise;
24
+ async function getVmApi() {
25
+ if (!vmApiPromise) vmApiPromise = (async () => {
26
+ try {
27
+ const vm = await import(
28
+ /* @vite-ignore */
29
+ "vm"
30
+ );
31
+ if (typeof vm.SourceTextModule !== "function" || typeof vm.SyntheticModule !== "function") return null;
32
+ return vm;
33
+ } catch {
34
+ return null;
35
+ }
36
+ })();
37
+ return vmApiPromise;
38
+ }
39
+ async function isVmStrategyAvailable() {
40
+ return await getVmApi() !== null;
41
+ }
42
+ function getFederationInstances() {
43
+ return globalThis.__FEDERATION__?.__INSTANCES__ ?? [];
44
+ }
45
+ /**
46
+ * Resolve a bare specifier to a module namespace: share scope first, then the
47
+ * build-time resolvedShared file map, then plain host import.
48
+ */
49
+ async function loadBareModule(specifier, options) {
50
+ for (const instance of getFederationInstances()) {
51
+ if (typeof instance?.loadShare !== "function") continue;
52
+ if (!instance.options?.shared || !(specifier in instance.options.shared)) continue;
53
+ try {
54
+ const factory = await instance.loadShare(specifier);
55
+ if (typeof factory === "function") {
56
+ const shared = factory();
57
+ if (shared) return shared;
58
+ }
59
+ } catch {}
60
+ }
61
+ const resolvedPath = options.resolvedShared[specifier];
62
+ if (resolvedPath) return import(
63
+ /* @vite-ignore */
64
+ `file://${resolvedPath}`
65
+ );
66
+ return import(
67
+ /* @vite-ignore */
68
+ specifier
69
+ );
70
+ }
71
+ function createSyntheticModule(vm, specifier, namespace) {
72
+ const source = namespace && typeof namespace === "object" ? namespace : { default: namespace };
73
+ const exportNames = new Set(Object.keys(source));
74
+ exportNames.add("default");
75
+ const syntheticModule = new vm.SyntheticModule([...exportNames], () => {
76
+ for (const exportName of exportNames) if (exportName === "default") syntheticModule.setExport("default", source.default !== void 0 ? source.default : namespace);
77
+ else syntheticModule.setExport(exportName, source[exportName]);
78
+ }, { identifier: `mf-shared:${specifier}` });
79
+ return syntheticModule;
80
+ }
81
+ const httpModuleCache = /* @__PURE__ */ new Map();
82
+ const namespaceCache = /* @__PURE__ */ new Map();
83
+ function getBodyPreview(body) {
84
+ return body.slice(0, 240).replace(/\s+/g, " ").trim();
85
+ }
86
+ async function fetchModuleSource(url) {
87
+ const res = await fetch(url);
88
+ const text = await res.text();
89
+ if (!res.ok) throw new SsrEntryHttpError(url, res.status, res.statusText, getBodyPreview(text));
90
+ return neutralizeBrowserPreloadHelpers(text);
91
+ }
92
+ function isHttpUrl(value) {
93
+ return value.startsWith("http://") || value.startsWith("https://");
94
+ }
95
+ /** Resolve a specifier against the referencing module's URL; null for bare specifiers. */
96
+ function resolveSpecifierUrl(specifier, referencerUrl) {
97
+ if (isHttpUrl(specifier)) return specifier;
98
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) return new URL(specifier, referencerUrl).href;
99
+ return null;
100
+ }
101
+ function getHttpModule(vm, url, options) {
102
+ const cacheKey = `${options.versionKey}::${url}`;
103
+ if (!httpModuleCache.has(cacheKey)) httpModuleCache.set(cacheKey, (async () => {
104
+ const code = await fetchModuleSource(url);
105
+ return new vm.SourceTextModule(code, {
106
+ identifier: url,
107
+ initializeImportMeta(meta) {
108
+ meta.url = url;
109
+ },
110
+ importModuleDynamically: (specifier, referencingModule) => importDynamically(vm, specifier, referencingModule, options)
111
+ });
112
+ })().catch((error) => {
113
+ httpModuleCache.delete(cacheKey);
114
+ throw error;
115
+ }));
116
+ return httpModuleCache.get(cacheKey);
117
+ }
118
+ async function linkModule(vm, specifier, referencingModule, options) {
119
+ const url = resolveSpecifierUrl(specifier, referencingModule.identifier);
120
+ if (url) return getHttpModule(vm, url, options);
121
+ return createSyntheticModule(vm, specifier, await loadBareModule(specifier, options));
122
+ }
123
+ async function importDynamically(vm, specifier, referencingModule, options) {
124
+ const linker = (spec, referencer) => linkModule(vm, spec, referencer, options);
125
+ const module = await linker(specifier, referencingModule);
126
+ if (module.status === "unlinked") await module.link(linker);
127
+ if (module.status === "linked") await module.evaluate();
128
+ return module;
129
+ }
130
+ /**
131
+ * Load and evaluate a remote SSR entry as a `vm.SourceTextModule` graph and
132
+ * return its namespace (the federation container with `init`/`get`).
133
+ * Returns null when the vm module APIs are unavailable.
134
+ */
135
+ async function loadViaVmStrategy(entryUrl, options) {
136
+ const vm = await getVmApi();
137
+ if (!vm) return null;
138
+ const cacheKey = `${options.versionKey}::${entryUrl}`;
139
+ if (!namespaceCache.has(cacheKey)) namespaceCache.set(cacheKey, (async () => {
140
+ const entryModule = await getHttpModule(vm, entryUrl, options);
141
+ const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule, options);
142
+ if (entryModule.status === "unlinked") await entryModule.link(linker);
143
+ if (entryModule.status === "linked") await entryModule.evaluate();
144
+ return entryModule.namespace;
145
+ })().catch((error) => {
146
+ namespaceCache.delete(cacheKey);
147
+ throw error;
148
+ }));
149
+ return namespaceCache.get(cacheKey);
150
+ }
151
+ //#endregion
152
+ export { isVmStrategyAvailable, loadViaVmStrategy };
@@ -32,6 +32,29 @@ interface RemoteInfo {
32
32
  type?: string;
33
33
  entryGlobalName?: string;
34
34
  }
35
+ declare class SsrEntryHttpError extends Error {
36
+ readonly url: string;
37
+ readonly status: number;
38
+ readonly statusText: string;
39
+ readonly bodyPreview: string;
40
+ constructor(url: string, status: number, statusText: string, bodyPreview: string);
41
+ }
42
+ /**
43
+ * Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
44
+ * remote SSR entries. Pass a remote entry URL to scope the invalidation to one
45
+ * remote; call with no arguments to invalidate everything.
46
+ *
47
+ * Note: the MF runtime keeps its own container/module caches per federation
48
+ * instance. This function best-effort clears the module caches of all global
49
+ * federation instances so re-renders load fresh remote modules, but hosts that
50
+ * hold direct references to previously loaded modules keep those references.
51
+ */
52
+ declare function revalidate(remoteEntryUrl?: string): void;
53
+ /**
54
+ * Neutralize browser-only preload machinery in Vite/Rolldown output so the
55
+ * code can evaluate in Node. Shared by the temp-file and vm strategies.
56
+ */
57
+ declare function neutralizeBrowserPreloadHelpers(code: string): string;
35
58
  /**
36
59
  * MF runtime plugin factory.
37
60
  *
@@ -50,6 +73,33 @@ interface SsrEntryLoaderOptions {
50
73
  * in remote SSR entry temp files — no runtime createRequire walk-up needed.
51
74
  */
52
75
  resolvedShared?: Record<string, string>;
76
+ /**
77
+ * How to evaluate remote SSR entries on the server.
78
+ *
79
+ * - `'temp-file'` (default): fetch the ESM graph, rewrite specifiers, write
80
+ * temp files and `import()` them. Works on stock Node; shared packages are
81
+ * pinned to the host's copies via `resolvedShared` (no version negotiation).
82
+ * - `'vm'`: evaluate the graph with `vm.SourceTextModule` and link bare
83
+ * shared imports through the host's federation share scope (`loadShare`),
84
+ * restoring version negotiation. Requires `--experimental-vm-modules`;
85
+ * falls back to `'temp-file'` when unavailable.
86
+ */
87
+ strategy?: 'temp-file' | 'vm';
88
+ /**
89
+ * Share scope consulted by the `'vm'` strategy when linking bare imports.
90
+ * Defaults to `'default'`.
91
+ */
92
+ shareScopeName?: string;
93
+ /**
94
+ * Re-check each remote's manifest when the cached SSR entry resolution is
95
+ * older than this many milliseconds. When the manifest's version changes
96
+ * (remote redeployed at the same URL), the loader drops its caches for that
97
+ * remote so subsequent loads use the new build. Omit to cache until process
98
+ * exit or an explicit `revalidate()` call. Only manifest-resolved entries
99
+ * can be revalidated this way — convention-resolved entries have no version
100
+ * source.
101
+ */
102
+ maxAgeMs?: number;
53
103
  }
54
104
  declare function ssrEntryLoaderPlugin(options?: SsrEntryLoaderOptions): {
55
105
  name: string;
@@ -63,4 +113,4 @@ declare function ssrEntryLoaderPlugin(options?: SsrEntryLoaderOptions): {
63
113
  } | undefined>;
64
114
  };
65
115
  //#endregion
66
- export { ssrEntryLoaderPlugin as default };
116
+ export { SsrEntryHttpError, ssrEntryLoaderPlugin as default, neutralizeBrowserPreloadHelpers, revalidate };
@@ -70,10 +70,7 @@ async function getOrCreateRunner(remoteOrigin) {
70
70
  return await (await fetch(runnerEndpoint, {
71
71
  method: "POST",
72
72
  headers: { "Content-Type": "application/json" },
73
- body: JSON.stringify({
74
- name: payload.data.name,
75
- data: payload.data.data
76
- })
73
+ body: JSON.stringify(payload)
77
74
  })).json();
78
75
  } }
79
76
  }, new ESModulesEvaluator());
@@ -88,6 +85,27 @@ const _path = () => nodeImport("path");
88
85
  const _fs = () => nodeImport("fs");
89
86
  const _crypto = () => nodeImport("crypto");
90
87
  const _module = () => nodeImport("module");
88
+ /**
89
+ * Version key for a resolved SSR entry. Derived from the remote's manifest
90
+ * content so a redeploy at the same URL produces a different key, which in
91
+ * turn produces different temp-file names — busting both our caches and
92
+ * Node's ESM module cache. Convention-resolved entries (no manifest) get a
93
+ * stable placeholder key and cannot be revalidated automatically.
94
+ */
95
+ const UNVERSIONED = "unversioned";
96
+ function hashString(value) {
97
+ let hash = 2166136261;
98
+ for (let i = 0; i < value.length; i++) {
99
+ hash ^= value.charCodeAt(i);
100
+ hash = Math.imul(hash, 16777619);
101
+ }
102
+ return (hash >>> 0).toString(16).padStart(8, "0");
103
+ }
104
+ function computeManifestVersionKey(manifest) {
105
+ const buildVersion = manifest.metaData?.buildInfo?.buildVersion;
106
+ const contentHash = hashString(JSON.stringify(manifest));
107
+ return buildVersion ? `${buildVersion}-${contentHash}` : contentHash;
108
+ }
91
109
  const ssrEntryCache = /* @__PURE__ */ new Map();
92
110
  const manifestFetchCache = /* @__PURE__ */ new Map();
93
111
  var SsrEntryHttpError = class extends Error {
@@ -149,7 +167,8 @@ function resolveSSREntryUrl(manifest, manifestUrl) {
149
167
  const entryPath = (meta.ssrRemoteEntry.path || "") + meta.ssrRemoteEntry.name;
150
168
  return {
151
169
  url: new URL(entryPath, base).href,
152
- type: meta.ssrRemoteEntry.type || "module"
170
+ type: meta.ssrRemoteEntry.type || "module",
171
+ versionKey: computeManifestVersionKey(manifest)
153
172
  };
154
173
  }
155
174
  /**
@@ -191,14 +210,17 @@ function buildSsrEntryCandidates(ctx, options = {}) {
191
210
  const candidates = [];
192
211
  if (!options.skipServerBuild) candidates.push({
193
212
  url: `${remoteOrigin}/__mf_server__/${filename}.ssr.js`,
194
- type: "module"
213
+ type: "module",
214
+ versionKey: UNVERSIONED
195
215
  });
196
216
  candidates.push({
197
217
  url: `${base}.ssr.js`,
198
- type: "module"
218
+ type: "module",
219
+ versionKey: UNVERSIONED
199
220
  }, {
200
221
  url: `${remoteOrigin}/__mf_ssr__/${filename}.ssr.js`,
201
- type: "module"
222
+ type: "module",
223
+ versionKey: UNVERSIONED
202
224
  });
203
225
  return candidates;
204
226
  }
@@ -212,13 +234,15 @@ async function resolveFirstReachableCandidate(candidates) {
212
234
  async function resolveSSREntryImpl(remoteEntryUrl) {
213
235
  if (isSsrEntry(remoteEntryUrl)) return {
214
236
  url: remoteEntryUrl,
215
- type: "module"
237
+ type: "module",
238
+ versionKey: UNVERSIONED
216
239
  };
217
240
  if (!isManifestEntry(remoteEntryUrl)) {
218
241
  const filename = getEntryFilename(remoteEntryUrl);
219
242
  const fromServerBuild = await headCheckSsrEntry({
220
243
  url: `${remoteEntryUrl.replace(/\/[^/]+$/, "")}/__mf_server__/${filename}.ssr.js`,
221
- type: "module"
244
+ type: "module",
245
+ versionKey: UNVERSIONED
222
246
  });
223
247
  if (fromServerBuild) return fromServerBuild;
224
248
  }
@@ -229,9 +253,63 @@ async function resolveSSREntryImpl(remoteEntryUrl) {
229
253
  }
230
254
  return resolveFirstReachableCandidate(buildSsrEntryCandidates(ctx, { skipServerBuild: !isManifestEntry(remoteEntryUrl) }));
231
255
  }
232
- async function getSSREntry(remoteEntryUrl) {
233
- if (!ssrEntryCache.has(remoteEntryUrl)) ssrEntryCache.set(remoteEntryUrl, resolveSSREntryImpl(remoteEntryUrl));
234
- return ssrEntryCache.get(remoteEntryUrl);
256
+ function setSsrEntryCache(remoteEntryUrl) {
257
+ const record = {
258
+ promise: resolveSSREntryImpl(remoteEntryUrl),
259
+ resolvedAt: Date.now()
260
+ };
261
+ ssrEntryCache.set(remoteEntryUrl, record);
262
+ return record;
263
+ }
264
+ async function getSSREntry(remoteEntryUrl, maxAgeMs) {
265
+ const cached = ssrEntryCache.get(remoteEntryUrl);
266
+ if (!cached) return setSsrEntryCache(remoteEntryUrl).promise;
267
+ if (!(typeof maxAgeMs === "number" && maxAgeMs >= 0 && Date.now() - cached.resolvedAt >= maxAgeMs)) return cached.promise;
268
+ const previous = await cached.promise.catch(() => null);
269
+ manifestFetchCache.delete(getManifestUrl(remoteEntryUrl));
270
+ const record = setSsrEntryCache(remoteEntryUrl);
271
+ const next = await record.promise.catch(() => null);
272
+ if (previous && next && previous.versionKey !== next.versionKey) dropRemoteCaches(remoteEntryUrl);
273
+ return record.promise;
274
+ }
275
+ /**
276
+ * Drop per-remote caches after a version change so old artifacts stop being
277
+ * reused. Temp-file cache keys hold SSR entry/chunk URLs (not the browser
278
+ * entry URL), so scope the invalidation by origin.
279
+ */
280
+ function dropRemoteCaches(remoteEntryUrl) {
281
+ let origin;
282
+ try {
283
+ origin = new URL(remoteEntryUrl).origin;
284
+ } catch {
285
+ return;
286
+ }
287
+ for (const key of tempFileCache.keys()) if (key.slice(key.indexOf("::") + 2).startsWith(origin)) tempFileCache.delete(key);
288
+ }
289
+ /**
290
+ * Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
291
+ * remote SSR entries. Pass a remote entry URL to scope the invalidation to one
292
+ * remote; call with no arguments to invalidate everything.
293
+ *
294
+ * Note: the MF runtime keeps its own container/module caches per federation
295
+ * instance. This function best-effort clears the module caches of all global
296
+ * federation instances so re-renders load fresh remote modules, but hosts that
297
+ * hold direct references to previously loaded modules keep those references.
298
+ */
299
+ function revalidate(remoteEntryUrl) {
300
+ if (remoteEntryUrl) {
301
+ ssrEntryCache.delete(remoteEntryUrl);
302
+ manifestFetchCache.delete(getManifestUrl(remoteEntryUrl));
303
+ dropRemoteCaches(remoteEntryUrl);
304
+ } else {
305
+ ssrEntryCache.clear();
306
+ manifestFetchCache.clear();
307
+ tempFileCache.clear();
308
+ }
309
+ const federation = globalThis.__FEDERATION__;
310
+ for (const instance of federation?.__INSTANCES__ ?? []) try {
311
+ instance?.moduleCache?.clear?.();
312
+ } catch {}
235
313
  }
236
314
  const tempFileCache = /* @__PURE__ */ new Map();
237
315
  let ssrCacheDirPromise;
@@ -252,14 +330,11 @@ async function getSSRCacheDir() {
252
330
  })();
253
331
  return ssrCacheDirPromise;
254
332
  }
255
- function transformSsrCode(code, base, sharedPkgMap) {
256
- code = code.replace(/((?:from|export\s*\*\s*from)\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
257
- code = code.replace(/(import\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
258
- code = code.replace(/(import\s*\(\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`](\s*\))/g, (_m, prefix, _q, specifier, suffix) => `${prefix}"${new URL(specifier, base).href}"${suffix}`);
259
- if (sharedPkgMap && sharedPkgMap.size > 0) code = code.replace(/(?:from|import\s*\()\s*(["'`])([^"'`./][^"'`]*)["'`]/g, (m, _q, specifier) => {
260
- const resolved = sharedPkgMap.get(specifier);
261
- return resolved ? m.replace(specifier, `file://${resolved}`) : m;
262
- });
333
+ /**
334
+ * Neutralize browser-only preload machinery in Vite/Rolldown output so the
335
+ * code can evaluate in Node. Shared by the temp-file and vm strategies.
336
+ */
337
+ function neutralizeBrowserPreloadHelpers(code) {
263
338
  code = code.replace(/import\s*\{([^}]*)\}\s*from\s*["'][^"']*preload-helper[^"']*["'];?/g, (_m, bindings) => {
264
339
  return bindings.split(",").map((b) => {
265
340
  const parts = b.trim().split(/\s+as\s+/);
@@ -270,6 +345,16 @@ function transformSsrCode(code, base, sharedPkgMap) {
270
345
  code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
271
346
  return code;
272
347
  }
348
+ function transformSsrCode(code, base, sharedPkgMap) {
349
+ code = code.replace(/((?:from|export\s*\*\s*from)\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
350
+ code = code.replace(/(import\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
351
+ code = code.replace(/(import\s*\(\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`](\s*\))/g, (_m, prefix, _q, specifier, suffix) => `${prefix}"${new URL(specifier, base).href}"${suffix}`);
352
+ if (sharedPkgMap && sharedPkgMap.size > 0) code = code.replace(/(?:from|import\s*\()\s*(["'`])([^"'`./][^"'`]*)["'`]/g, (m, _q, specifier) => {
353
+ const resolved = sharedPkgMap.get(specifier);
354
+ return resolved ? m.replace(specifier, `file://${resolved}`) : m;
355
+ });
356
+ return neutralizeBrowserPreloadHelpers(code);
357
+ }
273
358
  function isVitePreloadHelperSpecifier(specifier) {
274
359
  return specifier.includes("preload-helper");
275
360
  }
@@ -277,10 +362,15 @@ function isVitePreloadHelperSpecifier(specifier) {
277
362
  * Fetch an HTTP ESM module, transform it, write it to a temp .js file and
278
363
  * return the file path. Recursively does the same for HTTP transitive imports
279
364
  * so that `import('file:///...temp.js')` can resolve them.
365
+ *
366
+ * `versionKey` participates in both the cache key and the temp file name, so
367
+ * a remote redeploy (new manifest → new key) produces new files and bypasses
368
+ * Node's ESM module cache instead of serving the stale build.
280
369
  */
281
- async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
370
+ async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap, versionKey = UNVERSIONED) {
371
+ const cacheKey = `${versionKey}::${url}`;
282
372
  if (visited.has(url)) return visited.get(url);
283
- if (tempFileCache.has(url)) return tempFileCache.get(url);
373
+ if (tempFileCache.has(cacheKey)) return tempFileCache.get(cacheKey);
284
374
  const promise = (async () => {
285
375
  const res = await fetch(url);
286
376
  let code = await res.text();
@@ -292,7 +382,7 @@ async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
292
382
  while ((m = relRegex.exec(code)) !== null) if ((m[1].startsWith("./") || m[1].startsWith("../")) && !isVitePreloadHelperSpecifier(m[1])) relImports.push(new URL(m[1], base).href);
293
383
  const subMap = /* @__PURE__ */ new Map();
294
384
  await Promise.all([...new Set(relImports)].filter((u) => u.startsWith("http://") || u.startsWith("https://")).map(async (u) => {
295
- const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, sharedPkgMap);
385
+ const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, sharedPkgMap, versionKey);
296
386
  subMap.set(u, `file://${tmpPath}`);
297
387
  }));
298
388
  code = transformSsrCode(code, base, sharedPkgMap);
@@ -300,22 +390,36 @@ async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
300
390
  const { createHash } = await _crypto();
301
391
  const { join } = await _path();
302
392
  const { writeFileSync } = await _fs();
303
- const tmpFile = join(tmpDir, `${createHash("sha1").update(url).digest("hex").slice(0, 12)}.js`);
393
+ const tmpFile = join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}.js`);
304
394
  writeFileSync(tmpFile, code, "utf8");
305
395
  visited.set(url, tmpFile);
306
396
  return tmpFile;
307
397
  })();
308
- tempFileCache.set(url, promise);
398
+ tempFileCache.set(cacheKey, promise);
309
399
  return promise;
310
400
  }
311
- async function importTempModule(filePath) {
312
- return await import(
313
- /* @vite-ignore */
314
- filePath
315
- );
401
+ async function importTempModule(filePath, versionKey) {
402
+ return await import(`${filePath}?v=${encodeURIComponent(versionKey)}`);
403
+ }
404
+ let warnedVmUnavailable = false;
405
+ async function tryVmStrategy(ssrEntry, options) {
406
+ const { loadViaVmStrategy, isVmStrategyAvailable } = await import("../ssrVmStrategy-DtpfkCw1.js");
407
+ if (!await isVmStrategyAvailable()) {
408
+ if (!warnedVmUnavailable) {
409
+ warnedVmUnavailable = true;
410
+ console.warn("[mf-vite:ssr-entry-loader] strategy \"vm\" requires vm.SourceTextModule (run Node with --experimental-vm-modules); falling back to the temp-file strategy.");
411
+ }
412
+ return null;
413
+ }
414
+ return await loadViaVmStrategy(ssrEntry.url, {
415
+ resolvedShared: options.resolvedShared,
416
+ shareScopeName: options.shareScopeName,
417
+ versionKey: ssrEntry.versionKey
418
+ });
316
419
  }
317
- async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
318
- const { url, type } = ssrEntry;
420
+ async function loadSSRRemoteEntry(ssrEntry, options) {
421
+ const { url, type, versionKey } = ssrEntry;
422
+ const { resolvedShared } = options;
319
423
  if (type === "commonjs-module" || type === "commonjs") {
320
424
  const { createRequire } = await _module();
321
425
  const req = createRequire(import.meta.url);
@@ -338,12 +442,18 @@ async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
338
442
  if (process.env.NODE_ENV !== "production") return null;
339
443
  }
340
444
  }
445
+ if (options.strategy === "vm") try {
446
+ const fromVm = await tryVmStrategy(ssrEntry, options);
447
+ if (fromVm) return fromVm;
448
+ } catch (error) {
449
+ if (isSsrEntryHttpError(error)) throw error;
450
+ }
341
451
  const { mkdirSync } = await _fs();
342
452
  const cacheDir = await getSSRCacheDir();
343
453
  mkdirSync(cacheDir, { recursive: true });
344
454
  const sharedPkgMap = new Map(Object.entries(resolvedShared));
345
455
  try {
346
- return await importTempModule(await fetchEsmToTempFile(url, cacheDir, /* @__PURE__ */ new Map(), sharedPkgMap));
456
+ return await importTempModule(await fetchEsmToTempFile(url, cacheDir, /* @__PURE__ */ new Map(), sharedPkgMap, versionKey), versionKey);
347
457
  } catch (error) {
348
458
  if (isSsrEntryHttpError(error)) throw error;
349
459
  return null;
@@ -359,18 +469,23 @@ async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
359
469
  }
360
470
  }
361
471
  function ssrEntryLoaderPlugin(options = {}) {
362
- const resolvedShared = options.resolvedShared ?? {};
472
+ const resolved = {
473
+ resolvedShared: options.resolvedShared ?? {},
474
+ strategy: options.strategy ?? "temp-file",
475
+ shareScopeName: options.shareScopeName ?? "default",
476
+ maxAgeMs: options.maxAgeMs
477
+ };
363
478
  return {
364
479
  name: "mf-vite:ssr-entry-loader",
365
480
  async loadEntry({ remoteInfo }) {
366
481
  if (!isNodeServer()) return;
367
- const ssrEntry = await getSSREntry(remoteInfo.entry);
482
+ const ssrEntry = await getSSREntry(remoteInfo.entry, resolved.maxAgeMs);
368
483
  if (!ssrEntry) return;
369
- const mod = await loadSSRRemoteEntry(ssrEntry, resolvedShared);
484
+ const mod = await loadSSRRemoteEntry(ssrEntry, resolved);
370
485
  if (!mod) return;
371
486
  return mod;
372
487
  }
373
488
  };
374
489
  }
375
490
  //#endregion
376
- export { ssrEntryLoaderPlugin as default };
491
+ export { SsrEntryHttpError, ssrEntryLoaderPlugin as default, neutralizeBrowserPreloadHelpers, revalidate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/vite",
3
- "version": "1.16.12",
3
+ "version": "1.16.14",
4
4
  "description": "Vite plugin for Module Federation",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -70,9 +70,9 @@
70
70
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
71
71
  },
72
72
  "dependencies": {
73
- "@module-federation/dts-plugin": "2.6.0",
74
- "@module-federation/runtime": "2.6.0",
75
- "@module-federation/sdk": "2.6.0"
73
+ "@module-federation/dts-plugin": "2.7.0",
74
+ "@module-federation/runtime": "2.7.0",
75
+ "@module-federation/sdk": "2.7.0"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@playwright/test": "1.58.2",
@@ -86,4 +86,4 @@
86
86
  "vite": "8.1.0",
87
87
  "vitest": "4.0.18"
88
88
  }
89
- }
89
+ }