@module-federation/vite 1.14.2 → 1.14.4

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 +235 -87
  2. package/lib/index.mjs +235 -87
  3. package/package.json +4 -4
package/lib/index.cjs CHANGED
@@ -152,6 +152,56 @@ function removePathFromNpmPackage(packageString) {
152
152
  const match = packageString.match(/^(?:@[^/]+\/)?[^/]+/);
153
153
  return match ? match[0] : packageString;
154
154
  }
155
+ function getInstalledPackageJson(pkg, opts) {
156
+ const cwd = opts?.cwd || getPackageDetectionCwd();
157
+ const packageName = opts?.packageName || removePathFromNpmPackage(pkg);
158
+ try {
159
+ const projectRequire = (0, module$1.createRequire)(new URL(`file://${pathe.default.join(cwd, "package.json")}`));
160
+ let resolvedPath;
161
+ try {
162
+ resolvedPath = projectRequire.resolve(pkg);
163
+ } catch {
164
+ resolvedPath = projectRequire.resolve(packageName);
165
+ }
166
+ let currentDir = pathe.default.dirname(resolvedPath);
167
+ const rootDir = pathe.default.parse(currentDir).root;
168
+ while (true) {
169
+ const packageJsonPath = pathe.default.join(currentDir, "package.json");
170
+ if ((0, fs.existsSync)(packageJsonPath)) {
171
+ const packageJsonContent = (0, fs.readFileSync)(packageJsonPath, "utf-8");
172
+ try {
173
+ const packageJson = JSON.parse(packageJsonContent);
174
+ if (packageJson.name === packageName) return {
175
+ path: packageJsonPath,
176
+ dir: currentDir,
177
+ packageJson
178
+ };
179
+ } catch (error) {
180
+ if (!(error instanceof SyntaxError)) throw error;
181
+ }
182
+ }
183
+ if (currentDir === rootDir) break;
184
+ currentDir = pathe.default.dirname(currentDir);
185
+ }
186
+ } catch {
187
+ let currentDir = cwd;
188
+ const rootDir = pathe.default.parse(currentDir).root;
189
+ while (true) {
190
+ const packageJsonPath = pathe.default.join(currentDir, "node_modules", packageName, "package.json");
191
+ if ((0, fs.existsSync)(packageJsonPath)) try {
192
+ return {
193
+ path: packageJsonPath,
194
+ dir: pathe.default.dirname(packageJsonPath),
195
+ packageJson: JSON.parse((0, fs.readFileSync)(packageJsonPath, "utf-8"))
196
+ };
197
+ } catch {
198
+ return;
199
+ }
200
+ if (currentDir === rootDir) break;
201
+ currentDir = pathe.default.dirname(currentDir);
202
+ }
203
+ }
204
+ }
155
205
  /**
156
206
  * Detect whether the current runtime is Vite 8+ by checking for a Vite version flag
157
207
  * on the plugin hook context, with Rolldown metadata kept as a compatibility fallback.
@@ -956,6 +1006,12 @@ function inferVersionFromRequiredVersion(requiredVersion) {
956
1006
  if (!requiredVersion) return void 0;
957
1007
  return requiredVersion.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0];
958
1008
  }
1009
+ function getLitExportSubpathShares(sharedName) {
1010
+ if (sharedName !== "lit") return [];
1011
+ const exportsField = getInstalledPackageJson(sharedName, { packageName: sharedName })?.packageJson.exports;
1012
+ if (!exportsField || typeof exportsField === "string") return [];
1013
+ return Object.keys(exportsField).filter((key) => key.startsWith("./") && key !== "." && !key.includes("*")).map((key) => `${sharedName}/${key.slice(2)}`);
1014
+ }
959
1015
  function normalizeShareItem(key, shareItem) {
960
1016
  let version;
961
1017
  if (!(typeof shareItem === "object" && shareItem.import === false)) try {
@@ -1000,14 +1056,21 @@ function normalizeShareItem(key, shareItem) {
1000
1056
  function normalizeShared(shared) {
1001
1057
  if (!shared) return {};
1002
1058
  const result = {};
1003
- if (Array.isArray(shared)) {
1004
- shared.forEach((key) => {
1005
- result[key] = normalizeShareItem(key, key);
1006
- });
1007
- return result;
1008
- }
1009
- if (typeof shared === "object") Object.keys(shared).forEach((key) => {
1010
- result[key] = normalizeShareItem(key, shared[key]);
1059
+ const sourceEntries = [];
1060
+ if (Array.isArray(shared)) shared.forEach((key) => {
1061
+ result[key] = normalizeShareItem(key, key);
1062
+ sourceEntries.push([key, key]);
1063
+ });
1064
+ else if (typeof shared === "object") Object.keys(shared).forEach((key) => {
1065
+ const value = shared[key];
1066
+ result[key] = normalizeShareItem(key, value);
1067
+ sourceEntries.push([key, value]);
1068
+ });
1069
+ sourceEntries.forEach(([key, value]) => {
1070
+ for (const subpathShare of getLitExportSubpathShares(key)) {
1071
+ if (result[subpathShare]) continue;
1072
+ result[subpathShare] = normalizeShareItem(subpathShare, value);
1073
+ }
1011
1074
  });
1012
1075
  return result;
1013
1076
  }
@@ -1239,6 +1302,16 @@ function generateExposes(options) {
1239
1302
  return `
1240
1303
  const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
1241
1304
  const injectedCssHrefs = new Set();
1305
+ let exposeLoadQueue = Promise.resolve();
1306
+
1307
+ async function importExposedModule(loader) {
1308
+ const currentLoad = exposeLoadQueue.then(loader, loader);
1309
+ exposeLoadQueue = currentLoad.then(
1310
+ () => undefined,
1311
+ () => undefined
1312
+ );
1313
+ return currentLoad;
1314
+ }
1242
1315
 
1243
1316
  async function injectCssAssets(exposeKey) {
1244
1317
  if (typeof document === "undefined") {
@@ -1283,7 +1356,9 @@ function generateExposes(options) {
1283
1356
  return `
1284
1357
  ${JSON.stringify(key)}: async () => {
1285
1358
  await injectCssAssets(${JSON.stringify(key)})
1286
- const importModule = await import(${JSON.stringify(options.exposes[key].import)})
1359
+ const importModule = await importExposedModule(
1360
+ () => import(${JSON.stringify(options.exposes[key].import)})
1361
+ )
1287
1362
  const exportModule = {}
1288
1363
  Object.assign(exportModule, importModule)
1289
1364
  Object.defineProperty(exportModule, "__esModule", {
@@ -1455,52 +1530,6 @@ function resolvePackageEntryFromProjectRoot(pkg) {
1455
1530
  return;
1456
1531
  }
1457
1532
  }
1458
- function getInstalledPackageJsonPath(pkg) {
1459
- try {
1460
- const packageName = removePathFromNpmPackage(pkg);
1461
- const projectRequire = (0, module$1.createRequire)(new URL(`file://${pathe.default.join(getPackageDetectionCwd(), "package.json")}`));
1462
- let resolvedPath;
1463
- try {
1464
- resolvedPath = projectRequire.resolve(pkg);
1465
- } catch {
1466
- resolvedPath = projectRequire.resolve(packageName);
1467
- }
1468
- let currentDir = pathe.default.dirname(resolvedPath);
1469
- const rootDir = pathe.default.parse(currentDir).root;
1470
- while (currentDir !== rootDir) {
1471
- const packageJsonPath = pathe.default.join(currentDir, "package.json");
1472
- if ((0, fs.existsSync)(packageJsonPath)) {
1473
- const packageJsonContent = (0, fs.readFileSync)(packageJsonPath, "utf-8");
1474
- try {
1475
- if (JSON.parse(packageJsonContent).name === packageName) return packageJsonPath;
1476
- } catch (error) {
1477
- if (!(error instanceof SyntaxError)) throw error;
1478
- }
1479
- }
1480
- currentDir = pathe.default.dirname(currentDir);
1481
- }
1482
- const rootPackageJsonPath = pathe.default.join(rootDir, "package.json");
1483
- if ((0, fs.existsSync)(rootPackageJsonPath)) {
1484
- const rootPackageJsonContent = (0, fs.readFileSync)(rootPackageJsonPath, "utf-8");
1485
- try {
1486
- if (JSON.parse(rootPackageJsonContent).name === packageName) return rootPackageJsonPath;
1487
- } catch (error) {
1488
- if (!(error instanceof SyntaxError)) throw error;
1489
- }
1490
- }
1491
- } catch {
1492
- const packageName = removePathFromNpmPackage(pkg);
1493
- let currentDir = getPackageDetectionCwd();
1494
- const rootDir = pathe.default.parse(currentDir).root;
1495
- while (currentDir !== rootDir) {
1496
- const packageJsonPath = pathe.default.join(currentDir, "node_modules", packageName, "package.json");
1497
- if ((0, fs.existsSync)(packageJsonPath)) return packageJsonPath;
1498
- currentDir = pathe.default.dirname(currentDir);
1499
- }
1500
- const rootPackageJsonPath = pathe.default.join(rootDir, "node_modules", packageName, "package.json");
1501
- return (0, fs.existsSync)(rootPackageJsonPath) ? rootPackageJsonPath : void 0;
1502
- }
1503
- }
1504
1533
  function resolveImportTarget(exportsField) {
1505
1534
  if (typeof exportsField === "string") return exportsField;
1506
1535
  if (!exportsField || typeof exportsField !== "object") return void 0;
@@ -1521,14 +1550,14 @@ function resolveImportTarget(exportsField) {
1521
1550
  function getPackageEsmEntryPath(pkg) {
1522
1551
  try {
1523
1552
  const resolvedEntryPath = resolvePackageEntryFromProjectRoot(pkg);
1524
- const packageJsonPath = getInstalledPackageJsonPath(pkg);
1525
- if (!packageJsonPath) return resolvedEntryPath;
1553
+ const installedPackageJson = getInstalledPackageJson(pkg);
1554
+ if (!installedPackageJson) return resolvedEntryPath;
1526
1555
  const packageName = removePathFromNpmPackage(pkg);
1527
- const packageJson = JSON.parse((0, fs.readFileSync)(packageJsonPath, "utf-8"));
1556
+ const packageJson = installedPackageJson.packageJson;
1528
1557
  const subpath = pkg === packageName ? "." : `.${pkg.slice(packageName.length)}`;
1529
1558
  const target = resolveImportTarget(typeof packageJson.exports === "string" ? subpath === "." ? packageJson.exports : void 0 : packageJson.exports?.[subpath] ?? (subpath === "." ? packageJson.exports?.["."] ?? (packageJson.exports && !Object.keys(packageJson.exports).some((key) => key.startsWith(".")) ? packageJson.exports : void 0) : void 0)) || packageJson.module;
1530
1559
  if (!target) return resolvedEntryPath;
1531
- return pathe.default.resolve(pathe.default.dirname(packageJsonPath), target);
1560
+ return pathe.default.resolve(installedPackageJson.dir, target);
1532
1561
  } catch {
1533
1562
  return resolvePackageEntryFromProjectRoot(pkg);
1534
1563
  }
@@ -1672,8 +1701,11 @@ function getSharedImportSource(pkg, shareItem) {
1672
1701
  }
1673
1702
  const LOAD_SHARE_TAG = "__loadShare__";
1674
1703
  const loadShareCacheMap = {};
1704
+ function shouldUseEsmLoadShare(pkg, command, isRolldown) {
1705
+ return command === "build" || !!isRolldown || pkg === "lit" || pkg.startsWith("lit/");
1706
+ }
1675
1707
  function getLoadShareImportId(pkg, isRolldown, command) {
1676
- if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1708
+ if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, shouldUseEsmLoadShare(pkg, command, isRolldown) ? ".mjs" : ".js");
1677
1709
  return loadShareCacheMap[pkg].getImportId();
1678
1710
  }
1679
1711
  function getLoadShareModulePath(pkg, isRolldown, command) {
@@ -1681,8 +1713,8 @@ function getLoadShareModulePath(pkg, isRolldown, command) {
1681
1713
  return loadShareCacheMap[pkg].getPath();
1682
1714
  }
1683
1715
  function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1684
- if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1685
- const useESM = command === "build" || isRolldown;
1716
+ if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, shouldUseEsmLoadShare(pkg, command, isRolldown) ? ".mjs" : ".js");
1717
+ const useESM = shouldUseEsmLoadShare(pkg, command, isRolldown);
1686
1718
  const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1687
1719
  const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1688
1720
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
@@ -1716,6 +1748,7 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1716
1748
  const devImportSource = concreteSharedImportSource || pkg;
1717
1749
  const localProviderPath = getLocalProviderImportPath(pkg);
1718
1750
  const isWorkspacePackage = isWorkspaceFilePath(localProviderPath) || isWorkspaceFilePath(concreteSharedImportSource);
1751
+ const skipServePrebuildWarmup = command !== "build" && (pkg === "lit" || pkg.startsWith("lit/"));
1719
1752
  const providerImportId = localProviderPath || concreteSharedImportSource || sharedImportSource;
1720
1753
  const namedExports = getPackageNamedExports(pkg);
1721
1754
  let exportLine;
@@ -1724,8 +1757,8 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1724
1757
  const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
1725
1758
  exportLine = useESM ? `export default exportModule.default ?? exportModule;\n ${destructure}\n ${namedExportLine}` : `module.exports = exportModule;\n ${destructure}\n Object.assign(module.exports, { ${namedExports.map((name, i) => `"${name}": __mf_${i}`).join(", ")} });`;
1726
1759
  } else exportLine = useESM ? `export default exportModule.default ?? exportModule\n export * from ${escapeGeneratedStringLiteral(sharedImportSource)}` : "module.exports = exportModule";
1727
- const prebuildImportLine = isWorkspacePackage && command !== "build" ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
1728
- const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1760
+ const prebuildImportLine = isWorkspacePackage && command !== "build" || skipServePrebuildWarmup ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
1761
+ const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1729
1762
  loadShareCacheMap[pkg].writeSync(`
1730
1763
  ${prebuildImportLine}
1731
1764
  ${devDynamicImportLine}
@@ -2619,6 +2652,37 @@ var PromiseStore = class {
2619
2652
  function getPrebuildResolutionSource(pkgName, shareItem) {
2620
2653
  return getConcreteSharedImportSource(pkgName, shareItem) || pkgName;
2621
2654
  }
2655
+ /**
2656
+ * Reads the dependencies of an installed package from its package.json.
2657
+ */
2658
+ function getPackageDependencies(pkg) {
2659
+ const packageName = removePathFromNpmPackage(pkg);
2660
+ const cwd = getPackageDetectionCwd();
2661
+ const candidates = [pathe.default.join(cwd, "node_modules", packageName, "package.json")];
2662
+ for (const candidate of candidates) if ((0, fs.existsSync)(candidate)) try {
2663
+ const json = JSON.parse((0, fs.readFileSync)(candidate, "utf-8"));
2664
+ return Object.keys(json.dependencies || {});
2665
+ } catch {}
2666
+ return [];
2667
+ }
2668
+ /**
2669
+ * In dev mode, detects shared packages that are sub-dependencies of other
2670
+ * shared packages and removes them to avoid initialization order issues.
2671
+ * For example, `lit` depends on `lit-html`, `lit-element`, and
2672
+ * `@lit/reactive-element` — sharing them separately causes the child modules
2673
+ * to load before their parent, resulting in `undefined` class extends errors.
2674
+ */
2675
+ function excludeSharedSubDependencies(shared) {
2676
+ const sharedKeys = new Set(Object.keys(shared));
2677
+ for (const parentKey of sharedKeys) {
2678
+ const deps = getPackageDependencies(parentKey);
2679
+ for (const dep of deps) if (sharedKeys.has(dep) && dep !== parentKey) {
2680
+ mfWarn(`"${dep}" is a dependency of shared package "${parentKey}" and is also shared separately. This may cause initialization order issues in dev mode. Consider sharing only "${parentKey}".\n Auto-excluding "${dep}" from shared modules for dev mode.`);
2681
+ delete shared[dep];
2682
+ sharedKeys.delete(dep);
2683
+ }
2684
+ }
2685
+ }
2622
2686
  function proxySharedModule(options) {
2623
2687
  const { shared = {} } = options;
2624
2688
  let _config;
@@ -2644,6 +2708,7 @@ function proxySharedModule(options) {
2644
2708
  const isRolldown = getIsRolldown(this);
2645
2709
  _command = command;
2646
2710
  useDirectReactImport = isVinext || isAstro;
2711
+ if (command === "serve") excludeSharedSubDependencies(shared);
2647
2712
  config.resolve.alias.push(...Object.keys(shared).filter((key) => !(useDirectReactImport && key === "react")).map((key) => {
2648
2713
  const keyBase = key.endsWith("/") ? key.slice(0, -1) : key;
2649
2714
  const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3028,6 +3093,7 @@ function pluginRemoteNamedExports(options) {
3028
3093
  name: "module-federation-remote-named-exports",
3029
3094
  enforce: "post",
3030
3095
  async transform(code, id) {
3096
+ if (!getIsRolldown(this)) return;
3031
3097
  if (remoteNames.length === 0) return;
3032
3098
  if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
3033
3099
  if (!JS_EXTENSIONS_RE.test(id)) return;
@@ -3241,6 +3307,44 @@ function escapeUnsafeJsSourceChars(str) {
3241
3307
  return UNSAFE_JS_SOURCE_CHAR_MAP[char] ?? char;
3242
3308
  });
3243
3309
  }
3310
+ function insertAfterLastTopLevelImport(code, snippet) {
3311
+ let cursor = 0;
3312
+ let lastImportEnd = -1;
3313
+ const skipTrivia = () => {
3314
+ while (cursor < code.length) {
3315
+ if (/\s/.test(code[cursor])) {
3316
+ cursor++;
3317
+ continue;
3318
+ }
3319
+ if (code.startsWith("//", cursor)) {
3320
+ const lineEnd = code.indexOf("\n", cursor);
3321
+ cursor = lineEnd === -1 ? code.length : lineEnd + 1;
3322
+ continue;
3323
+ }
3324
+ if (code.startsWith("/*", cursor)) {
3325
+ const commentEnd = code.indexOf("*/", cursor + 2);
3326
+ cursor = commentEnd === -1 ? code.length : commentEnd + 2;
3327
+ continue;
3328
+ }
3329
+ break;
3330
+ }
3331
+ };
3332
+ while (cursor < code.length) {
3333
+ skipTrivia();
3334
+ if (!code.startsWith("import", cursor) || !/[\s"'*{]/.test(code[cursor + 6] ?? "")) break;
3335
+ const statementEnd = code.indexOf(";", cursor);
3336
+ if (statementEnd !== -1) {
3337
+ lastImportEnd = statementEnd + 1;
3338
+ cursor = statementEnd + 1;
3339
+ continue;
3340
+ }
3341
+ const lineEnd = code.indexOf("\n", cursor);
3342
+ lastImportEnd = lineEnd === -1 ? code.length : lineEnd + 1;
3343
+ cursor = lastImportEnd;
3344
+ }
3345
+ if (lastImportEnd === -1) return;
3346
+ return code.slice(0, lastImportEnd) + snippet + code.slice(lastImportEnd);
3347
+ }
3244
3348
  /**
3245
3349
  * Plugin that runs FIRST to create virtual module files in the config hook.
3246
3350
  * This prevents 504 "Outdated Optimize Dep" errors by ensuring files exist
@@ -3248,6 +3352,7 @@ function escapeUnsafeJsSourceChars(str) {
3248
3352
  */
3249
3353
  function createEarlyVirtualModulesPlugin(options) {
3250
3354
  const { shared, remotes, virtualModuleDir } = options;
3355
+ const isLitShare = (pkg) => pkg === "lit" || pkg.startsWith("lit/");
3251
3356
  return {
3252
3357
  name: "vite:module-federation-early-init",
3253
3358
  enforce: "pre",
@@ -3283,8 +3388,13 @@ function createEarlyVirtualModulesPlugin(options) {
3283
3388
  if (shareItem.shareConfig?.import !== false) writePreBuildLibPath(key, shareItem);
3284
3389
  addUsedShares(key);
3285
3390
  if (_command === "serve" && shareItem.shareConfig?.import !== false) {
3286
- if (!isRolldown) config.optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
3287
- config.optimizeDeps.include.push(getPreBuildLibImportId(key));
3391
+ const optimizeDeps = config.optimizeDeps ??= {};
3392
+ optimizeDeps.include ??= [];
3393
+ optimizeDeps.exclude ??= [];
3394
+ const shouldBypassOptimizeDep = isLitShare(key);
3395
+ if (shouldBypassOptimizeDep) optimizeDeps.exclude.push(key);
3396
+ if (!isRolldown && !shouldBypassOptimizeDep) optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
3397
+ optimizeDeps.include.push(getPreBuildLibImportId(key));
3288
3398
  }
3289
3399
  }
3290
3400
  writeLocalSharedImportMap();
@@ -3302,6 +3412,7 @@ function federation(mfUserOptions) {
3302
3412
  const virtualExposesId = getVirtualExposesId(options);
3303
3413
  let command;
3304
3414
  let depsDir = "/node_modules/.vite/deps/";
3415
+ let desiredRolldownOutput;
3305
3416
  return [
3306
3417
  createEarlyVirtualModulesPlugin(options),
3307
3418
  ...isVinext ? [{
@@ -3392,12 +3503,22 @@ function federation(mfUserOptions) {
3392
3503
  };
3393
3504
  }
3394
3505
  let warnedAboutCodeSplitting = false;
3506
+ let warnedAboutCodeSplittingGroups = false;
3395
3507
  const ensureCodeSplitting = (output) => {
3396
- if (output?.codeSplitting !== false) return;
3397
- delete output.codeSplitting;
3398
- if (warnedAboutCodeSplitting) return;
3399
- warnedAboutCodeSplitting = true;
3400
- mfWarn("Ignoring `build.rolldownOptions.output.codeSplitting = false` because module federation requires chunk splitting.");
3508
+ if (output?.codeSplitting === false) {
3509
+ delete output.codeSplitting;
3510
+ if (warnedAboutCodeSplitting) return;
3511
+ warnedAboutCodeSplitting = true;
3512
+ mfWarn("Ignoring `output.codeSplitting = false` because module federation requires chunk splitting.");
3513
+ return;
3514
+ }
3515
+ if (!output?.codeSplitting || typeof output.codeSplitting !== "object") return;
3516
+ if (!("groups" in output.codeSplitting)) return;
3517
+ delete output.codeSplitting.groups;
3518
+ if (Object.keys(output.codeSplitting).length === 0) delete output.codeSplitting;
3519
+ if (warnedAboutCodeSplittingGroups) return;
3520
+ warnedAboutCodeSplittingGroups = true;
3521
+ mfWarn("Ignoring `output.codeSplitting.groups` because it conflicts with module federation. Grouping shared dependency init wrappers with their dependent modules can break runtime init order and cause standalone remotes to fail before mount.");
3401
3522
  };
3402
3523
  let warnedAboutManualChunks = false;
3403
3524
  const applyManualChunks = (output) => {
@@ -3405,7 +3526,7 @@ function federation(mfUserOptions) {
3405
3526
  const isPatchedByPlugin = !!(output.manualChunks && patchedManualChunks.has(output.manualChunks));
3406
3527
  if (output.manualChunks && !isPatchedByPlugin && !warnedAboutManualChunks) {
3407
3528
  warnedAboutManualChunks = true;
3408
- mfWarn("Ignoring `build.rollupOptions.output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
3529
+ mfWarn("Ignoring `output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
3409
3530
  }
3410
3531
  const mfManualChunks = function(id) {
3411
3532
  if (id.includes(runtimeInitId)) return "runtimeInit";
@@ -3418,10 +3539,46 @@ function federation(mfUserOptions) {
3418
3539
  output.manualChunks = mfManualChunks;
3419
3540
  };
3420
3541
  config.build.rollupOptions = config.build.rollupOptions || {};
3421
- if (!Array.isArray(config.build.rollupOptions.output)) applyManualChunks(config.build.rollupOptions.output ||= {});
3542
+ const rollupOutput = config.build.rollupOptions.output;
3543
+ if (Array.isArray(rollupOutput)) rollupOutput.forEach((output) => applyManualChunks(output));
3544
+ else applyManualChunks(config.build.rollupOptions.output ||= {});
3422
3545
  const buildWithRolldown = config.build;
3423
3546
  buildWithRolldown.rolldownOptions = buildWithRolldown.rolldownOptions || {};
3424
- if (!Array.isArray(buildWithRolldown.rolldownOptions.output)) applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
3547
+ const rolldownOutput = buildWithRolldown.rolldownOptions.output;
3548
+ const snapshotRolldownOutput = (output) => ({
3549
+ entryFileNames: output.entryFileNames,
3550
+ chunkFileNames: output.chunkFileNames,
3551
+ assetFileNames: output.assetFileNames
3552
+ });
3553
+ if (Array.isArray(rolldownOutput)) {
3554
+ rolldownOutput.forEach((output) => applyManualChunks(output));
3555
+ desiredRolldownOutput = rolldownOutput.map((output) => snapshotRolldownOutput(output));
3556
+ } else {
3557
+ applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
3558
+ desiredRolldownOutput = [snapshotRolldownOutput(buildWithRolldown.rolldownOptions.output)];
3559
+ }
3560
+ },
3561
+ async buildApp(builder) {
3562
+ if (!desiredRolldownOutput) return;
3563
+ const applyRolldownOutput = (output, restoredOutput) => {
3564
+ if (!output || !restoredOutput) return;
3565
+ if (restoredOutput.entryFileNames !== void 0) output.entryFileNames = restoredOutput.entryFileNames;
3566
+ if (restoredOutput.chunkFileNames !== void 0) output.chunkFileNames = restoredOutput.chunkFileNames;
3567
+ if (restoredOutput.assetFileNames !== void 0) output.assetFileNames = restoredOutput.assetFileNames;
3568
+ };
3569
+ for (const environment of Object.values(builder.environments)) {
3570
+ const getRolldownOptions = environment?.getRolldownOptions;
3571
+ if (typeof getRolldownOptions !== "function") continue;
3572
+ environment.getRolldownOptions = async () => {
3573
+ const rolldownOptions = await getRolldownOptions.call(environment);
3574
+ if (Array.isArray(rolldownOptions.output)) rolldownOptions.output.forEach((output, index) => applyRolldownOutput(output, desiredRolldownOutput?.[index]));
3575
+ else {
3576
+ rolldownOptions.output ||= {};
3577
+ applyRolldownOutput(rolldownOptions.output, desiredRolldownOutput[0]);
3578
+ }
3579
+ return rolldownOptions;
3580
+ };
3581
+ }
3425
3582
  },
3426
3583
  load(id) {
3427
3584
  if (id.startsWith("\0")) return;
@@ -3475,11 +3632,9 @@ function federation(mfUserOptions) {
3475
3632
  for (const v of importedFromLoadShare) if (new RegExp("\\(" + v + "\\(\\)\\s*,\\s*\\w+\\(\\w+\\)\\)").test(code)) allInits.push(v);
3476
3633
  if (allInits.length === 0) continue;
3477
3634
  const awaits = allInits.map((v) => `await ${v}();`).join("");
3478
- const lastFromRegex = /\bfrom\s*["'][^"']*["']\s*;?/g;
3479
- let lastFromEnd = -1;
3480
- while ((m = lastFromRegex.exec(code)) !== null) lastFromEnd = m.index + m[0].length;
3481
- if (lastFromEnd !== -1) {
3482
- chunk.code = code.slice(0, lastFromEnd) + awaits + code.slice(lastFromEnd);
3635
+ const codeWithAwaits = insertAfterLastTopLevelImport(code, awaits);
3636
+ if (codeWithAwaits) {
3637
+ chunk.code = codeWithAwaits;
3483
3638
  continue;
3484
3639
  }
3485
3640
  const exportIdx = code.search(/\bexport\s*[{d]/);
@@ -3610,14 +3765,7 @@ function federation(mfUserOptions) {
3610
3765
  })) return;
3611
3766
  if (/await\s+init_\w+__loadShare__/.test(code)) return;
3612
3767
  if (code.includes("__esmMin")) return;
3613
- const awaits = [...initFns].map((fn) => `await ${fn}();`).join("\n");
3614
- const topLevelImportRe = /^import\s/gm;
3615
- let lastImportIdx = -1;
3616
- let importMatch;
3617
- while ((importMatch = topLevelImportRe.exec(code)) !== null) lastImportIdx = importMatch.index;
3618
- if (lastImportIdx === -1) return;
3619
- const lineEnd = code.indexOf("\n", lastImportIdx);
3620
- return code.slice(0, lineEnd + 1) + awaits + "\n" + code.slice(lineEnd + 1);
3768
+ return insertAfterLastTopLevelImport(code, [...initFns].map((fn) => `await ${fn}();`).join("\n") + "\n");
3621
3769
  }
3622
3770
  },
3623
3771
  PluginDevProxyModuleTopLevelAwait(),
package/lib/index.mjs CHANGED
@@ -130,6 +130,56 @@ function removePathFromNpmPackage(packageString) {
130
130
  const match = packageString.match(/^(?:@[^/]+\/)?[^/]+/);
131
131
  return match ? match[0] : packageString;
132
132
  }
133
+ function getInstalledPackageJson(pkg, opts) {
134
+ const cwd = opts?.cwd || getPackageDetectionCwd();
135
+ const packageName = opts?.packageName || removePathFromNpmPackage(pkg);
136
+ try {
137
+ const projectRequire = createRequire$1(new URL(`file://${path.join(cwd, "package.json")}`));
138
+ let resolvedPath;
139
+ try {
140
+ resolvedPath = projectRequire.resolve(pkg);
141
+ } catch {
142
+ resolvedPath = projectRequire.resolve(packageName);
143
+ }
144
+ let currentDir = path.dirname(resolvedPath);
145
+ const rootDir = path.parse(currentDir).root;
146
+ while (true) {
147
+ const packageJsonPath = path.join(currentDir, "package.json");
148
+ if (existsSync(packageJsonPath)) {
149
+ const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
150
+ try {
151
+ const packageJson = JSON.parse(packageJsonContent);
152
+ if (packageJson.name === packageName) return {
153
+ path: packageJsonPath,
154
+ dir: currentDir,
155
+ packageJson
156
+ };
157
+ } catch (error) {
158
+ if (!(error instanceof SyntaxError)) throw error;
159
+ }
160
+ }
161
+ if (currentDir === rootDir) break;
162
+ currentDir = path.dirname(currentDir);
163
+ }
164
+ } catch {
165
+ let currentDir = cwd;
166
+ const rootDir = path.parse(currentDir).root;
167
+ while (true) {
168
+ const packageJsonPath = path.join(currentDir, "node_modules", packageName, "package.json");
169
+ if (existsSync(packageJsonPath)) try {
170
+ return {
171
+ path: packageJsonPath,
172
+ dir: path.dirname(packageJsonPath),
173
+ packageJson: JSON.parse(readFileSync(packageJsonPath, "utf-8"))
174
+ };
175
+ } catch {
176
+ return;
177
+ }
178
+ if (currentDir === rootDir) break;
179
+ currentDir = path.dirname(currentDir);
180
+ }
181
+ }
182
+ }
133
183
  /**
134
184
  * Detect whether the current runtime is Vite 8+ by checking for a Vite version flag
135
185
  * on the plugin hook context, with Rolldown metadata kept as a compatibility fallback.
@@ -934,6 +984,12 @@ function inferVersionFromRequiredVersion(requiredVersion) {
934
984
  if (!requiredVersion) return void 0;
935
985
  return requiredVersion.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0];
936
986
  }
987
+ function getLitExportSubpathShares(sharedName) {
988
+ if (sharedName !== "lit") return [];
989
+ const exportsField = getInstalledPackageJson(sharedName, { packageName: sharedName })?.packageJson.exports;
990
+ if (!exportsField || typeof exportsField === "string") return [];
991
+ return Object.keys(exportsField).filter((key) => key.startsWith("./") && key !== "." && !key.includes("*")).map((key) => `${sharedName}/${key.slice(2)}`);
992
+ }
937
993
  function normalizeShareItem(key, shareItem) {
938
994
  let version;
939
995
  if (!(typeof shareItem === "object" && shareItem.import === false)) try {
@@ -977,14 +1033,21 @@ function normalizeShareItem(key, shareItem) {
977
1033
  function normalizeShared(shared) {
978
1034
  if (!shared) return {};
979
1035
  const result = {};
980
- if (Array.isArray(shared)) {
981
- shared.forEach((key) => {
982
- result[key] = normalizeShareItem(key, key);
983
- });
984
- return result;
985
- }
986
- if (typeof shared === "object") Object.keys(shared).forEach((key) => {
987
- result[key] = normalizeShareItem(key, shared[key]);
1036
+ const sourceEntries = [];
1037
+ if (Array.isArray(shared)) shared.forEach((key) => {
1038
+ result[key] = normalizeShareItem(key, key);
1039
+ sourceEntries.push([key, key]);
1040
+ });
1041
+ else if (typeof shared === "object") Object.keys(shared).forEach((key) => {
1042
+ const value = shared[key];
1043
+ result[key] = normalizeShareItem(key, value);
1044
+ sourceEntries.push([key, value]);
1045
+ });
1046
+ sourceEntries.forEach(([key, value]) => {
1047
+ for (const subpathShare of getLitExportSubpathShares(key)) {
1048
+ if (result[subpathShare]) continue;
1049
+ result[subpathShare] = normalizeShareItem(subpathShare, value);
1050
+ }
988
1051
  });
989
1052
  return result;
990
1053
  }
@@ -1216,6 +1279,16 @@ function generateExposes(options) {
1216
1279
  return `
1217
1280
  const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
1218
1281
  const injectedCssHrefs = new Set();
1282
+ let exposeLoadQueue = Promise.resolve();
1283
+
1284
+ async function importExposedModule(loader) {
1285
+ const currentLoad = exposeLoadQueue.then(loader, loader);
1286
+ exposeLoadQueue = currentLoad.then(
1287
+ () => undefined,
1288
+ () => undefined
1289
+ );
1290
+ return currentLoad;
1291
+ }
1219
1292
 
1220
1293
  async function injectCssAssets(exposeKey) {
1221
1294
  if (typeof document === "undefined") {
@@ -1260,7 +1333,9 @@ function generateExposes(options) {
1260
1333
  return `
1261
1334
  ${JSON.stringify(key)}: async () => {
1262
1335
  await injectCssAssets(${JSON.stringify(key)})
1263
- const importModule = await import(${JSON.stringify(options.exposes[key].import)})
1336
+ const importModule = await importExposedModule(
1337
+ () => import(${JSON.stringify(options.exposes[key].import)})
1338
+ )
1264
1339
  const exportModule = {}
1265
1340
  Object.assign(exportModule, importModule)
1266
1341
  Object.defineProperty(exportModule, "__esModule", {
@@ -1432,52 +1507,6 @@ function resolvePackageEntryFromProjectRoot(pkg) {
1432
1507
  return;
1433
1508
  }
1434
1509
  }
1435
- function getInstalledPackageJsonPath(pkg) {
1436
- try {
1437
- const packageName = removePathFromNpmPackage(pkg);
1438
- const projectRequire = createRequire$1(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`));
1439
- let resolvedPath;
1440
- try {
1441
- resolvedPath = projectRequire.resolve(pkg);
1442
- } catch {
1443
- resolvedPath = projectRequire.resolve(packageName);
1444
- }
1445
- let currentDir = path.dirname(resolvedPath);
1446
- const rootDir = path.parse(currentDir).root;
1447
- while (currentDir !== rootDir) {
1448
- const packageJsonPath = path.join(currentDir, "package.json");
1449
- if (existsSync(packageJsonPath)) {
1450
- const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
1451
- try {
1452
- if (JSON.parse(packageJsonContent).name === packageName) return packageJsonPath;
1453
- } catch (error) {
1454
- if (!(error instanceof SyntaxError)) throw error;
1455
- }
1456
- }
1457
- currentDir = path.dirname(currentDir);
1458
- }
1459
- const rootPackageJsonPath = path.join(rootDir, "package.json");
1460
- if (existsSync(rootPackageJsonPath)) {
1461
- const rootPackageJsonContent = readFileSync(rootPackageJsonPath, "utf-8");
1462
- try {
1463
- if (JSON.parse(rootPackageJsonContent).name === packageName) return rootPackageJsonPath;
1464
- } catch (error) {
1465
- if (!(error instanceof SyntaxError)) throw error;
1466
- }
1467
- }
1468
- } catch {
1469
- const packageName = removePathFromNpmPackage(pkg);
1470
- let currentDir = getPackageDetectionCwd();
1471
- const rootDir = path.parse(currentDir).root;
1472
- while (currentDir !== rootDir) {
1473
- const packageJsonPath = path.join(currentDir, "node_modules", packageName, "package.json");
1474
- if (existsSync(packageJsonPath)) return packageJsonPath;
1475
- currentDir = path.dirname(currentDir);
1476
- }
1477
- const rootPackageJsonPath = path.join(rootDir, "node_modules", packageName, "package.json");
1478
- return existsSync(rootPackageJsonPath) ? rootPackageJsonPath : void 0;
1479
- }
1480
- }
1481
1510
  function resolveImportTarget(exportsField) {
1482
1511
  if (typeof exportsField === "string") return exportsField;
1483
1512
  if (!exportsField || typeof exportsField !== "object") return void 0;
@@ -1498,14 +1527,14 @@ function resolveImportTarget(exportsField) {
1498
1527
  function getPackageEsmEntryPath(pkg) {
1499
1528
  try {
1500
1529
  const resolvedEntryPath = resolvePackageEntryFromProjectRoot(pkg);
1501
- const packageJsonPath = getInstalledPackageJsonPath(pkg);
1502
- if (!packageJsonPath) return resolvedEntryPath;
1530
+ const installedPackageJson = getInstalledPackageJson(pkg);
1531
+ if (!installedPackageJson) return resolvedEntryPath;
1503
1532
  const packageName = removePathFromNpmPackage(pkg);
1504
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1533
+ const packageJson = installedPackageJson.packageJson;
1505
1534
  const subpath = pkg === packageName ? "." : `.${pkg.slice(packageName.length)}`;
1506
1535
  const target = resolveImportTarget(typeof packageJson.exports === "string" ? subpath === "." ? packageJson.exports : void 0 : packageJson.exports?.[subpath] ?? (subpath === "." ? packageJson.exports?.["."] ?? (packageJson.exports && !Object.keys(packageJson.exports).some((key) => key.startsWith(".")) ? packageJson.exports : void 0) : void 0)) || packageJson.module;
1507
1536
  if (!target) return resolvedEntryPath;
1508
- return path.resolve(path.dirname(packageJsonPath), target);
1537
+ return path.resolve(installedPackageJson.dir, target);
1509
1538
  } catch {
1510
1539
  return resolvePackageEntryFromProjectRoot(pkg);
1511
1540
  }
@@ -1649,8 +1678,11 @@ function getSharedImportSource(pkg, shareItem) {
1649
1678
  }
1650
1679
  const LOAD_SHARE_TAG = "__loadShare__";
1651
1680
  const loadShareCacheMap = {};
1681
+ function shouldUseEsmLoadShare(pkg, command, isRolldown) {
1682
+ return command === "build" || !!isRolldown || pkg === "lit" || pkg.startsWith("lit/");
1683
+ }
1652
1684
  function getLoadShareImportId(pkg, isRolldown, command) {
1653
- if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1685
+ if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, shouldUseEsmLoadShare(pkg, command, isRolldown) ? ".mjs" : ".js");
1654
1686
  return loadShareCacheMap[pkg].getImportId();
1655
1687
  }
1656
1688
  function getLoadShareModulePath(pkg, isRolldown, command) {
@@ -1658,8 +1690,8 @@ function getLoadShareModulePath(pkg, isRolldown, command) {
1658
1690
  return loadShareCacheMap[pkg].getPath();
1659
1691
  }
1660
1692
  function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1661
- if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, isRolldown || command === "build" ? ".mjs" : ".js");
1662
- const useESM = command === "build" || isRolldown;
1693
+ if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, shouldUseEsmLoadShare(pkg, command, isRolldown) ? ".mjs" : ".js");
1694
+ const useESM = shouldUseEsmLoadShare(pkg, command, isRolldown);
1663
1695
  const importLine = command === "build" ? getRuntimeInitPromiseBootstrapCode() : useESM ? `${getRuntimeInitBootstrapCode()}
1664
1696
  const { initPromise } = globalThis[globalKey];` : `const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")`;
1665
1697
  const awaitOrPlaceholder = useESM ? "await " : "/*mf top-level-await placeholder replacement mf*/";
@@ -1693,6 +1725,7 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1693
1725
  const devImportSource = concreteSharedImportSource || pkg;
1694
1726
  const localProviderPath = getLocalProviderImportPath(pkg);
1695
1727
  const isWorkspacePackage = isWorkspaceFilePath(localProviderPath) || isWorkspaceFilePath(concreteSharedImportSource);
1728
+ const skipServePrebuildWarmup = command !== "build" && (pkg === "lit" || pkg.startsWith("lit/"));
1696
1729
  const providerImportId = localProviderPath || concreteSharedImportSource || sharedImportSource;
1697
1730
  const namedExports = getPackageNamedExports(pkg);
1698
1731
  let exportLine;
@@ -1701,8 +1734,8 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
1701
1734
  const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
1702
1735
  exportLine = useESM ? `export default exportModule.default ?? exportModule;\n ${destructure}\n ${namedExportLine}` : `module.exports = exportModule;\n ${destructure}\n Object.assign(module.exports, { ${namedExports.map((name, i) => `"${name}": __mf_${i}`).join(", ")} });`;
1703
1736
  } else exportLine = useESM ? `export default exportModule.default ?? exportModule\n export * from ${escapeGeneratedStringLiteral(sharedImportSource)}` : "module.exports = exportModule";
1704
- const prebuildImportLine = isWorkspacePackage && command !== "build" ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
1705
- const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1737
+ const prebuildImportLine = isWorkspacePackage && command !== "build" || skipServePrebuildWarmup ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
1738
+ const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
1706
1739
  loadShareCacheMap[pkg].writeSync(`
1707
1740
  ${prebuildImportLine}
1708
1741
  ${devDynamicImportLine}
@@ -2596,6 +2629,37 @@ var PromiseStore = class {
2596
2629
  function getPrebuildResolutionSource(pkgName, shareItem) {
2597
2630
  return getConcreteSharedImportSource(pkgName, shareItem) || pkgName;
2598
2631
  }
2632
+ /**
2633
+ * Reads the dependencies of an installed package from its package.json.
2634
+ */
2635
+ function getPackageDependencies(pkg) {
2636
+ const packageName = removePathFromNpmPackage(pkg);
2637
+ const cwd = getPackageDetectionCwd();
2638
+ const candidates = [path.join(cwd, "node_modules", packageName, "package.json")];
2639
+ for (const candidate of candidates) if (existsSync(candidate)) try {
2640
+ const json = JSON.parse(readFileSync(candidate, "utf-8"));
2641
+ return Object.keys(json.dependencies || {});
2642
+ } catch {}
2643
+ return [];
2644
+ }
2645
+ /**
2646
+ * In dev mode, detects shared packages that are sub-dependencies of other
2647
+ * shared packages and removes them to avoid initialization order issues.
2648
+ * For example, `lit` depends on `lit-html`, `lit-element`, and
2649
+ * `@lit/reactive-element` — sharing them separately causes the child modules
2650
+ * to load before their parent, resulting in `undefined` class extends errors.
2651
+ */
2652
+ function excludeSharedSubDependencies(shared) {
2653
+ const sharedKeys = new Set(Object.keys(shared));
2654
+ for (const parentKey of sharedKeys) {
2655
+ const deps = getPackageDependencies(parentKey);
2656
+ for (const dep of deps) if (sharedKeys.has(dep) && dep !== parentKey) {
2657
+ mfWarn(`"${dep}" is a dependency of shared package "${parentKey}" and is also shared separately. This may cause initialization order issues in dev mode. Consider sharing only "${parentKey}".\n Auto-excluding "${dep}" from shared modules for dev mode.`);
2658
+ delete shared[dep];
2659
+ sharedKeys.delete(dep);
2660
+ }
2661
+ }
2662
+ }
2599
2663
  function proxySharedModule(options) {
2600
2664
  const { shared = {} } = options;
2601
2665
  let _config;
@@ -2621,6 +2685,7 @@ function proxySharedModule(options) {
2621
2685
  const isRolldown = getIsRolldown(this);
2622
2686
  _command = command;
2623
2687
  useDirectReactImport = isVinext || isAstro;
2688
+ if (command === "serve") excludeSharedSubDependencies(shared);
2624
2689
  config.resolve.alias.push(...Object.keys(shared).filter((key) => !(useDirectReactImport && key === "react")).map((key) => {
2625
2690
  const keyBase = key.endsWith("/") ? key.slice(0, -1) : key;
2626
2691
  const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3005,6 +3070,7 @@ function pluginRemoteNamedExports(options) {
3005
3070
  name: "module-federation-remote-named-exports",
3006
3071
  enforce: "post",
3007
3072
  async transform(code, id) {
3073
+ if (!getIsRolldown(this)) return;
3008
3074
  if (remoteNames.length === 0) return;
3009
3075
  if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
3010
3076
  if (!JS_EXTENSIONS_RE.test(id)) return;
@@ -3218,6 +3284,44 @@ function escapeUnsafeJsSourceChars(str) {
3218
3284
  return UNSAFE_JS_SOURCE_CHAR_MAP[char] ?? char;
3219
3285
  });
3220
3286
  }
3287
+ function insertAfterLastTopLevelImport(code, snippet) {
3288
+ let cursor = 0;
3289
+ let lastImportEnd = -1;
3290
+ const skipTrivia = () => {
3291
+ while (cursor < code.length) {
3292
+ if (/\s/.test(code[cursor])) {
3293
+ cursor++;
3294
+ continue;
3295
+ }
3296
+ if (code.startsWith("//", cursor)) {
3297
+ const lineEnd = code.indexOf("\n", cursor);
3298
+ cursor = lineEnd === -1 ? code.length : lineEnd + 1;
3299
+ continue;
3300
+ }
3301
+ if (code.startsWith("/*", cursor)) {
3302
+ const commentEnd = code.indexOf("*/", cursor + 2);
3303
+ cursor = commentEnd === -1 ? code.length : commentEnd + 2;
3304
+ continue;
3305
+ }
3306
+ break;
3307
+ }
3308
+ };
3309
+ while (cursor < code.length) {
3310
+ skipTrivia();
3311
+ if (!code.startsWith("import", cursor) || !/[\s"'*{]/.test(code[cursor + 6] ?? "")) break;
3312
+ const statementEnd = code.indexOf(";", cursor);
3313
+ if (statementEnd !== -1) {
3314
+ lastImportEnd = statementEnd + 1;
3315
+ cursor = statementEnd + 1;
3316
+ continue;
3317
+ }
3318
+ const lineEnd = code.indexOf("\n", cursor);
3319
+ lastImportEnd = lineEnd === -1 ? code.length : lineEnd + 1;
3320
+ cursor = lastImportEnd;
3321
+ }
3322
+ if (lastImportEnd === -1) return;
3323
+ return code.slice(0, lastImportEnd) + snippet + code.slice(lastImportEnd);
3324
+ }
3221
3325
  /**
3222
3326
  * Plugin that runs FIRST to create virtual module files in the config hook.
3223
3327
  * This prevents 504 "Outdated Optimize Dep" errors by ensuring files exist
@@ -3225,6 +3329,7 @@ function escapeUnsafeJsSourceChars(str) {
3225
3329
  */
3226
3330
  function createEarlyVirtualModulesPlugin(options) {
3227
3331
  const { shared, remotes, virtualModuleDir } = options;
3332
+ const isLitShare = (pkg) => pkg === "lit" || pkg.startsWith("lit/");
3228
3333
  return {
3229
3334
  name: "vite:module-federation-early-init",
3230
3335
  enforce: "pre",
@@ -3260,8 +3365,13 @@ function createEarlyVirtualModulesPlugin(options) {
3260
3365
  if (shareItem.shareConfig?.import !== false) writePreBuildLibPath(key, shareItem);
3261
3366
  addUsedShares(key);
3262
3367
  if (_command === "serve" && shareItem.shareConfig?.import !== false) {
3263
- if (!isRolldown) config.optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
3264
- config.optimizeDeps.include.push(getPreBuildLibImportId(key));
3368
+ const optimizeDeps = config.optimizeDeps ??= {};
3369
+ optimizeDeps.include ??= [];
3370
+ optimizeDeps.exclude ??= [];
3371
+ const shouldBypassOptimizeDep = isLitShare(key);
3372
+ if (shouldBypassOptimizeDep) optimizeDeps.exclude.push(key);
3373
+ if (!isRolldown && !shouldBypassOptimizeDep) optimizeDeps.include.push(getLoadShareImportId(key, isRolldown, _command));
3374
+ optimizeDeps.include.push(getPreBuildLibImportId(key));
3265
3375
  }
3266
3376
  }
3267
3377
  writeLocalSharedImportMap();
@@ -3279,6 +3389,7 @@ function federation(mfUserOptions) {
3279
3389
  const virtualExposesId = getVirtualExposesId(options);
3280
3390
  let command;
3281
3391
  let depsDir = "/node_modules/.vite/deps/";
3392
+ let desiredRolldownOutput;
3282
3393
  return [
3283
3394
  createEarlyVirtualModulesPlugin(options),
3284
3395
  ...isVinext ? [{
@@ -3369,12 +3480,22 @@ function federation(mfUserOptions) {
3369
3480
  };
3370
3481
  }
3371
3482
  let warnedAboutCodeSplitting = false;
3483
+ let warnedAboutCodeSplittingGroups = false;
3372
3484
  const ensureCodeSplitting = (output) => {
3373
- if (output?.codeSplitting !== false) return;
3374
- delete output.codeSplitting;
3375
- if (warnedAboutCodeSplitting) return;
3376
- warnedAboutCodeSplitting = true;
3377
- mfWarn("Ignoring `build.rolldownOptions.output.codeSplitting = false` because module federation requires chunk splitting.");
3485
+ if (output?.codeSplitting === false) {
3486
+ delete output.codeSplitting;
3487
+ if (warnedAboutCodeSplitting) return;
3488
+ warnedAboutCodeSplitting = true;
3489
+ mfWarn("Ignoring `output.codeSplitting = false` because module federation requires chunk splitting.");
3490
+ return;
3491
+ }
3492
+ if (!output?.codeSplitting || typeof output.codeSplitting !== "object") return;
3493
+ if (!("groups" in output.codeSplitting)) return;
3494
+ delete output.codeSplitting.groups;
3495
+ if (Object.keys(output.codeSplitting).length === 0) delete output.codeSplitting;
3496
+ if (warnedAboutCodeSplittingGroups) return;
3497
+ warnedAboutCodeSplittingGroups = true;
3498
+ mfWarn("Ignoring `output.codeSplitting.groups` because it conflicts with module federation. Grouping shared dependency init wrappers with their dependent modules can break runtime init order and cause standalone remotes to fail before mount.");
3378
3499
  };
3379
3500
  let warnedAboutManualChunks = false;
3380
3501
  const applyManualChunks = (output) => {
@@ -3382,7 +3503,7 @@ function federation(mfUserOptions) {
3382
3503
  const isPatchedByPlugin = !!(output.manualChunks && patchedManualChunks.has(output.manualChunks));
3383
3504
  if (output.manualChunks && !isPatchedByPlugin && !warnedAboutManualChunks) {
3384
3505
  warnedAboutManualChunks = true;
3385
- mfWarn("Ignoring `build.rollupOptions.output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
3506
+ mfWarn("Ignoring `output.manualChunks` because it conflicts with module federation. Module federation transforms shared dependency imports with top-level await, and grouping these transformed modules into a single chunk creates circular async dependencies that cause the application to silently hang.");
3386
3507
  }
3387
3508
  const mfManualChunks = function(id) {
3388
3509
  if (id.includes(runtimeInitId)) return "runtimeInit";
@@ -3395,10 +3516,46 @@ function federation(mfUserOptions) {
3395
3516
  output.manualChunks = mfManualChunks;
3396
3517
  };
3397
3518
  config.build.rollupOptions = config.build.rollupOptions || {};
3398
- if (!Array.isArray(config.build.rollupOptions.output)) applyManualChunks(config.build.rollupOptions.output ||= {});
3519
+ const rollupOutput = config.build.rollupOptions.output;
3520
+ if (Array.isArray(rollupOutput)) rollupOutput.forEach((output) => applyManualChunks(output));
3521
+ else applyManualChunks(config.build.rollupOptions.output ||= {});
3399
3522
  const buildWithRolldown = config.build;
3400
3523
  buildWithRolldown.rolldownOptions = buildWithRolldown.rolldownOptions || {};
3401
- if (!Array.isArray(buildWithRolldown.rolldownOptions.output)) applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
3524
+ const rolldownOutput = buildWithRolldown.rolldownOptions.output;
3525
+ const snapshotRolldownOutput = (output) => ({
3526
+ entryFileNames: output.entryFileNames,
3527
+ chunkFileNames: output.chunkFileNames,
3528
+ assetFileNames: output.assetFileNames
3529
+ });
3530
+ if (Array.isArray(rolldownOutput)) {
3531
+ rolldownOutput.forEach((output) => applyManualChunks(output));
3532
+ desiredRolldownOutput = rolldownOutput.map((output) => snapshotRolldownOutput(output));
3533
+ } else {
3534
+ applyManualChunks(buildWithRolldown.rolldownOptions.output ||= {});
3535
+ desiredRolldownOutput = [snapshotRolldownOutput(buildWithRolldown.rolldownOptions.output)];
3536
+ }
3537
+ },
3538
+ async buildApp(builder) {
3539
+ if (!desiredRolldownOutput) return;
3540
+ const applyRolldownOutput = (output, restoredOutput) => {
3541
+ if (!output || !restoredOutput) return;
3542
+ if (restoredOutput.entryFileNames !== void 0) output.entryFileNames = restoredOutput.entryFileNames;
3543
+ if (restoredOutput.chunkFileNames !== void 0) output.chunkFileNames = restoredOutput.chunkFileNames;
3544
+ if (restoredOutput.assetFileNames !== void 0) output.assetFileNames = restoredOutput.assetFileNames;
3545
+ };
3546
+ for (const environment of Object.values(builder.environments)) {
3547
+ const getRolldownOptions = environment?.getRolldownOptions;
3548
+ if (typeof getRolldownOptions !== "function") continue;
3549
+ environment.getRolldownOptions = async () => {
3550
+ const rolldownOptions = await getRolldownOptions.call(environment);
3551
+ if (Array.isArray(rolldownOptions.output)) rolldownOptions.output.forEach((output, index) => applyRolldownOutput(output, desiredRolldownOutput?.[index]));
3552
+ else {
3553
+ rolldownOptions.output ||= {};
3554
+ applyRolldownOutput(rolldownOptions.output, desiredRolldownOutput[0]);
3555
+ }
3556
+ return rolldownOptions;
3557
+ };
3558
+ }
3402
3559
  },
3403
3560
  load(id) {
3404
3561
  if (id.startsWith("\0")) return;
@@ -3452,11 +3609,9 @@ function federation(mfUserOptions) {
3452
3609
  for (const v of importedFromLoadShare) if (new RegExp("\\(" + v + "\\(\\)\\s*,\\s*\\w+\\(\\w+\\)\\)").test(code)) allInits.push(v);
3453
3610
  if (allInits.length === 0) continue;
3454
3611
  const awaits = allInits.map((v) => `await ${v}();`).join("");
3455
- const lastFromRegex = /\bfrom\s*["'][^"']*["']\s*;?/g;
3456
- let lastFromEnd = -1;
3457
- while ((m = lastFromRegex.exec(code)) !== null) lastFromEnd = m.index + m[0].length;
3458
- if (lastFromEnd !== -1) {
3459
- chunk.code = code.slice(0, lastFromEnd) + awaits + code.slice(lastFromEnd);
3612
+ const codeWithAwaits = insertAfterLastTopLevelImport(code, awaits);
3613
+ if (codeWithAwaits) {
3614
+ chunk.code = codeWithAwaits;
3460
3615
  continue;
3461
3616
  }
3462
3617
  const exportIdx = code.search(/\bexport\s*[{d]/);
@@ -3587,14 +3742,7 @@ function federation(mfUserOptions) {
3587
3742
  })) return;
3588
3743
  if (/await\s+init_\w+__loadShare__/.test(code)) return;
3589
3744
  if (code.includes("__esmMin")) return;
3590
- const awaits = [...initFns].map((fn) => `await ${fn}();`).join("\n");
3591
- const topLevelImportRe = /^import\s/gm;
3592
- let lastImportIdx = -1;
3593
- let importMatch;
3594
- while ((importMatch = topLevelImportRe.exec(code)) !== null) lastImportIdx = importMatch.index;
3595
- if (lastImportIdx === -1) return;
3596
- const lineEnd = code.indexOf("\n", lastImportIdx);
3597
- return code.slice(0, lineEnd + 1) + awaits + "\n" + code.slice(lineEnd + 1);
3745
+ return insertAfterLastTopLevelImport(code, [...initFns].map((fn) => `await ${fn}();`).join("\n") + "\n");
3598
3746
  }
3599
3747
  },
3600
3748
  PluginDevProxyModuleTopLevelAwait(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/vite",
3
- "version": "1.14.2",
3
+ "version": "1.14.4",
4
4
  "description": "Vite plugin for Module Federation",
5
5
  "type": "module",
6
6
  "main": "./lib/index.cjs",
@@ -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.3.1",
74
- "@module-federation/runtime": "2.3.1",
75
- "@module-federation/sdk": "2.3.1",
73
+ "@module-federation/dts-plugin": "2.3.3",
74
+ "@module-federation/runtime": "2.3.3",
75
+ "@module-federation/sdk": "2.3.3",
76
76
  "@rollup/pluginutils": "^5.3.0",
77
77
  "defu": "^6.1.4",
78
78
  "es-module-lexer": "^2.0.0",