@module-federation/vite 1.14.1 → 1.14.3

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/lib/index.cjs CHANGED
@@ -234,11 +234,11 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
234
234
  if (!injectHtml()) return;
235
235
  clientInjected = true;
236
236
  const base = viteConfig.base.replace(/\/$/, "");
237
- const stripBase = (p) => base && p.startsWith(base) ? p.slice(base.length) : p;
237
+ const stripBase = (p) => base && p.startsWith(base + "/") ? p.slice(base.length) : p;
238
238
  const html = rewriteEntryScripts(c, (originalSrc) => {
239
239
  return `/@id/${DEV_HTML_PROXY_PREFIX}${new URLSearchParams({
240
240
  init: sanitizeDevEntryPath(stripBase(devEntryPath)),
241
- entry: originalSrc
241
+ entry: sanitizeDevEntryPath(stripBase(originalSrc))
242
242
  }).toString()}`;
243
243
  });
244
244
  return html === c ? injectEntryScript(c, stripBase(devEntryPath)) : html;
@@ -253,11 +253,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
253
253
  const initSrc = params.get("init");
254
254
  const entrySrc = params.get("entry");
255
255
  if (!initSrc || !entrySrc) return;
256
- return `
257
- const baseUrl = document.baseURI || window.location.href;
258
- await import(new URL(${JSON.stringify(initSrc)}, baseUrl).href);
259
- await import(new URL(${JSON.stringify(entrySrc)}, baseUrl).href);
260
- `;
256
+ return `import ${JSON.stringify(initSrc)};\nimport ${JSON.stringify(entrySrc)};\n`;
261
257
  },
262
258
  transform(code, id) {
263
259
  if (id.includes("node_modules") || inject !== "html" || htmlFilePath) return;
@@ -1243,6 +1239,16 @@ function generateExposes(options) {
1243
1239
  return `
1244
1240
  const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
1245
1241
  const injectedCssHrefs = new Set();
1242
+ let exposeLoadQueue = Promise.resolve();
1243
+
1244
+ async function importExposedModule(loader) {
1245
+ const currentLoad = exposeLoadQueue.then(loader, loader);
1246
+ exposeLoadQueue = currentLoad.then(
1247
+ () => undefined,
1248
+ () => undefined
1249
+ );
1250
+ return currentLoad;
1251
+ }
1246
1252
 
1247
1253
  async function injectCssAssets(exposeKey) {
1248
1254
  if (typeof document === "undefined") {
@@ -1287,7 +1293,9 @@ function generateExposes(options) {
1287
1293
  return `
1288
1294
  ${JSON.stringify(key)}: async () => {
1289
1295
  await injectCssAssets(${JSON.stringify(key)})
1290
- const importModule = await import(${JSON.stringify(options.exposes[key].import)})
1296
+ const importModule = await importExposedModule(
1297
+ () => import(${JSON.stringify(options.exposes[key].import)})
1298
+ )
1291
1299
  const exportModule = {}
1292
1300
  Object.assign(exportModule, importModule)
1293
1301
  Object.defineProperty(exportModule, "__esModule", {
@@ -2623,6 +2631,37 @@ var PromiseStore = class {
2623
2631
  function getPrebuildResolutionSource(pkgName, shareItem) {
2624
2632
  return getConcreteSharedImportSource(pkgName, shareItem) || pkgName;
2625
2633
  }
2634
+ /**
2635
+ * Reads the dependencies of an installed package from its package.json.
2636
+ */
2637
+ function getPackageDependencies(pkg) {
2638
+ const packageName = removePathFromNpmPackage(pkg);
2639
+ const cwd = getPackageDetectionCwd();
2640
+ const candidates = [pathe.default.join(cwd, "node_modules", packageName, "package.json")];
2641
+ for (const candidate of candidates) if ((0, fs.existsSync)(candidate)) try {
2642
+ const json = JSON.parse((0, fs.readFileSync)(candidate, "utf-8"));
2643
+ return Object.keys(json.dependencies || {});
2644
+ } catch {}
2645
+ return [];
2646
+ }
2647
+ /**
2648
+ * In dev mode, detects shared packages that are sub-dependencies of other
2649
+ * shared packages and removes them to avoid initialization order issues.
2650
+ * For example, `lit` depends on `lit-html`, `lit-element`, and
2651
+ * `@lit/reactive-element` — sharing them separately causes the child modules
2652
+ * to load before their parent, resulting in `undefined` class extends errors.
2653
+ */
2654
+ function excludeSharedSubDependencies(shared) {
2655
+ const sharedKeys = new Set(Object.keys(shared));
2656
+ for (const parentKey of sharedKeys) {
2657
+ const deps = getPackageDependencies(parentKey);
2658
+ for (const dep of deps) if (sharedKeys.has(dep) && dep !== parentKey) {
2659
+ 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.`);
2660
+ delete shared[dep];
2661
+ sharedKeys.delete(dep);
2662
+ }
2663
+ }
2664
+ }
2626
2665
  function proxySharedModule(options) {
2627
2666
  const { shared = {} } = options;
2628
2667
  let _config;
@@ -2648,6 +2687,7 @@ function proxySharedModule(options) {
2648
2687
  const isRolldown = getIsRolldown(this);
2649
2688
  _command = command;
2650
2689
  useDirectReactImport = isVinext || isAstro;
2690
+ if (command === "serve") excludeSharedSubDependencies(shared);
2651
2691
  config.resolve.alias.push(...Object.keys(shared).filter((key) => !(useDirectReactImport && key === "react")).map((key) => {
2652
2692
  const keyBase = key.endsWith("/") ? key.slice(0, -1) : key;
2653
2693
  const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3245,6 +3285,44 @@ function escapeUnsafeJsSourceChars(str) {
3245
3285
  return UNSAFE_JS_SOURCE_CHAR_MAP[char] ?? char;
3246
3286
  });
3247
3287
  }
3288
+ function insertAfterLastTopLevelImport(code, snippet) {
3289
+ let cursor = 0;
3290
+ let lastImportEnd = -1;
3291
+ const skipTrivia = () => {
3292
+ while (cursor < code.length) {
3293
+ if (/\s/.test(code[cursor])) {
3294
+ cursor++;
3295
+ continue;
3296
+ }
3297
+ if (code.startsWith("//", cursor)) {
3298
+ const lineEnd = code.indexOf("\n", cursor);
3299
+ cursor = lineEnd === -1 ? code.length : lineEnd + 1;
3300
+ continue;
3301
+ }
3302
+ if (code.startsWith("/*", cursor)) {
3303
+ const commentEnd = code.indexOf("*/", cursor + 2);
3304
+ cursor = commentEnd === -1 ? code.length : commentEnd + 2;
3305
+ continue;
3306
+ }
3307
+ break;
3308
+ }
3309
+ };
3310
+ while (cursor < code.length) {
3311
+ skipTrivia();
3312
+ if (!code.startsWith("import", cursor) || !/[\s"'*{]/.test(code[cursor + 6] ?? "")) break;
3313
+ const statementEnd = code.indexOf(";", cursor);
3314
+ if (statementEnd !== -1) {
3315
+ lastImportEnd = statementEnd + 1;
3316
+ cursor = statementEnd + 1;
3317
+ continue;
3318
+ }
3319
+ const lineEnd = code.indexOf("\n", cursor);
3320
+ lastImportEnd = lineEnd === -1 ? code.length : lineEnd + 1;
3321
+ cursor = lastImportEnd;
3322
+ }
3323
+ if (lastImportEnd === -1) return;
3324
+ return code.slice(0, lastImportEnd) + snippet + code.slice(lastImportEnd);
3325
+ }
3248
3326
  /**
3249
3327
  * Plugin that runs FIRST to create virtual module files in the config hook.
3250
3328
  * This prevents 504 "Outdated Optimize Dep" errors by ensuring files exist
@@ -3479,11 +3557,9 @@ function federation(mfUserOptions) {
3479
3557
  for (const v of importedFromLoadShare) if (new RegExp("\\(" + v + "\\(\\)\\s*,\\s*\\w+\\(\\w+\\)\\)").test(code)) allInits.push(v);
3480
3558
  if (allInits.length === 0) continue;
3481
3559
  const awaits = allInits.map((v) => `await ${v}();`).join("");
3482
- const lastFromRegex = /\bfrom\s*["'][^"']*["']\s*;?/g;
3483
- let lastFromEnd = -1;
3484
- while ((m = lastFromRegex.exec(code)) !== null) lastFromEnd = m.index + m[0].length;
3485
- if (lastFromEnd !== -1) {
3486
- chunk.code = code.slice(0, lastFromEnd) + awaits + code.slice(lastFromEnd);
3560
+ const codeWithAwaits = insertAfterLastTopLevelImport(code, awaits);
3561
+ if (codeWithAwaits) {
3562
+ chunk.code = codeWithAwaits;
3487
3563
  continue;
3488
3564
  }
3489
3565
  const exportIdx = code.search(/\bexport\s*[{d]/);
@@ -3614,14 +3690,7 @@ function federation(mfUserOptions) {
3614
3690
  })) return;
3615
3691
  if (/await\s+init_\w+__loadShare__/.test(code)) return;
3616
3692
  if (code.includes("__esmMin")) return;
3617
- const awaits = [...initFns].map((fn) => `await ${fn}();`).join("\n");
3618
- const topLevelImportRe = /^import\s/gm;
3619
- let lastImportIdx = -1;
3620
- let importMatch;
3621
- while ((importMatch = topLevelImportRe.exec(code)) !== null) lastImportIdx = importMatch.index;
3622
- if (lastImportIdx === -1) return;
3623
- const lineEnd = code.indexOf("\n", lastImportIdx);
3624
- return code.slice(0, lineEnd + 1) + awaits + "\n" + code.slice(lineEnd + 1);
3693
+ return insertAfterLastTopLevelImport(code, [...initFns].map((fn) => `await ${fn}();`).join("\n") + "\n");
3625
3694
  }
3626
3695
  },
3627
3696
  PluginDevProxyModuleTopLevelAwait(),
package/lib/index.mjs CHANGED
@@ -212,11 +212,11 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
212
212
  if (!injectHtml()) return;
213
213
  clientInjected = true;
214
214
  const base = viteConfig.base.replace(/\/$/, "");
215
- const stripBase = (p) => base && p.startsWith(base) ? p.slice(base.length) : p;
215
+ const stripBase = (p) => base && p.startsWith(base + "/") ? p.slice(base.length) : p;
216
216
  const html = rewriteEntryScripts(c, (originalSrc) => {
217
217
  return `/@id/${DEV_HTML_PROXY_PREFIX}${new URLSearchParams({
218
218
  init: sanitizeDevEntryPath(stripBase(devEntryPath)),
219
- entry: originalSrc
219
+ entry: sanitizeDevEntryPath(stripBase(originalSrc))
220
220
  }).toString()}`;
221
221
  });
222
222
  return html === c ? injectEntryScript(c, stripBase(devEntryPath)) : html;
@@ -231,11 +231,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
231
231
  const initSrc = params.get("init");
232
232
  const entrySrc = params.get("entry");
233
233
  if (!initSrc || !entrySrc) return;
234
- return `
235
- const baseUrl = document.baseURI || window.location.href;
236
- await import(new URL(${JSON.stringify(initSrc)}, baseUrl).href);
237
- await import(new URL(${JSON.stringify(entrySrc)}, baseUrl).href);
238
- `;
234
+ return `import ${JSON.stringify(initSrc)};\nimport ${JSON.stringify(entrySrc)};\n`;
239
235
  },
240
236
  transform(code, id) {
241
237
  if (id.includes("node_modules") || inject !== "html" || htmlFilePath) return;
@@ -1220,6 +1216,16 @@ function generateExposes(options) {
1220
1216
  return `
1221
1217
  const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
1222
1218
  const injectedCssHrefs = new Set();
1219
+ let exposeLoadQueue = Promise.resolve();
1220
+
1221
+ async function importExposedModule(loader) {
1222
+ const currentLoad = exposeLoadQueue.then(loader, loader);
1223
+ exposeLoadQueue = currentLoad.then(
1224
+ () => undefined,
1225
+ () => undefined
1226
+ );
1227
+ return currentLoad;
1228
+ }
1223
1229
 
1224
1230
  async function injectCssAssets(exposeKey) {
1225
1231
  if (typeof document === "undefined") {
@@ -1264,7 +1270,9 @@ function generateExposes(options) {
1264
1270
  return `
1265
1271
  ${JSON.stringify(key)}: async () => {
1266
1272
  await injectCssAssets(${JSON.stringify(key)})
1267
- const importModule = await import(${JSON.stringify(options.exposes[key].import)})
1273
+ const importModule = await importExposedModule(
1274
+ () => import(${JSON.stringify(options.exposes[key].import)})
1275
+ )
1268
1276
  const exportModule = {}
1269
1277
  Object.assign(exportModule, importModule)
1270
1278
  Object.defineProperty(exportModule, "__esModule", {
@@ -2600,6 +2608,37 @@ var PromiseStore = class {
2600
2608
  function getPrebuildResolutionSource(pkgName, shareItem) {
2601
2609
  return getConcreteSharedImportSource(pkgName, shareItem) || pkgName;
2602
2610
  }
2611
+ /**
2612
+ * Reads the dependencies of an installed package from its package.json.
2613
+ */
2614
+ function getPackageDependencies(pkg) {
2615
+ const packageName = removePathFromNpmPackage(pkg);
2616
+ const cwd = getPackageDetectionCwd();
2617
+ const candidates = [path.join(cwd, "node_modules", packageName, "package.json")];
2618
+ for (const candidate of candidates) if (existsSync(candidate)) try {
2619
+ const json = JSON.parse(readFileSync(candidate, "utf-8"));
2620
+ return Object.keys(json.dependencies || {});
2621
+ } catch {}
2622
+ return [];
2623
+ }
2624
+ /**
2625
+ * In dev mode, detects shared packages that are sub-dependencies of other
2626
+ * shared packages and removes them to avoid initialization order issues.
2627
+ * For example, `lit` depends on `lit-html`, `lit-element`, and
2628
+ * `@lit/reactive-element` — sharing them separately causes the child modules
2629
+ * to load before their parent, resulting in `undefined` class extends errors.
2630
+ */
2631
+ function excludeSharedSubDependencies(shared) {
2632
+ const sharedKeys = new Set(Object.keys(shared));
2633
+ for (const parentKey of sharedKeys) {
2634
+ const deps = getPackageDependencies(parentKey);
2635
+ for (const dep of deps) if (sharedKeys.has(dep) && dep !== parentKey) {
2636
+ 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.`);
2637
+ delete shared[dep];
2638
+ sharedKeys.delete(dep);
2639
+ }
2640
+ }
2641
+ }
2603
2642
  function proxySharedModule(options) {
2604
2643
  const { shared = {} } = options;
2605
2644
  let _config;
@@ -2625,6 +2664,7 @@ function proxySharedModule(options) {
2625
2664
  const isRolldown = getIsRolldown(this);
2626
2665
  _command = command;
2627
2666
  useDirectReactImport = isVinext || isAstro;
2667
+ if (command === "serve") excludeSharedSubDependencies(shared);
2628
2668
  config.resolve.alias.push(...Object.keys(shared).filter((key) => !(useDirectReactImport && key === "react")).map((key) => {
2629
2669
  const keyBase = key.endsWith("/") ? key.slice(0, -1) : key;
2630
2670
  const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3222,6 +3262,44 @@ function escapeUnsafeJsSourceChars(str) {
3222
3262
  return UNSAFE_JS_SOURCE_CHAR_MAP[char] ?? char;
3223
3263
  });
3224
3264
  }
3265
+ function insertAfterLastTopLevelImport(code, snippet) {
3266
+ let cursor = 0;
3267
+ let lastImportEnd = -1;
3268
+ const skipTrivia = () => {
3269
+ while (cursor < code.length) {
3270
+ if (/\s/.test(code[cursor])) {
3271
+ cursor++;
3272
+ continue;
3273
+ }
3274
+ if (code.startsWith("//", cursor)) {
3275
+ const lineEnd = code.indexOf("\n", cursor);
3276
+ cursor = lineEnd === -1 ? code.length : lineEnd + 1;
3277
+ continue;
3278
+ }
3279
+ if (code.startsWith("/*", cursor)) {
3280
+ const commentEnd = code.indexOf("*/", cursor + 2);
3281
+ cursor = commentEnd === -1 ? code.length : commentEnd + 2;
3282
+ continue;
3283
+ }
3284
+ break;
3285
+ }
3286
+ };
3287
+ while (cursor < code.length) {
3288
+ skipTrivia();
3289
+ if (!code.startsWith("import", cursor) || !/[\s"'*{]/.test(code[cursor + 6] ?? "")) break;
3290
+ const statementEnd = code.indexOf(";", cursor);
3291
+ if (statementEnd !== -1) {
3292
+ lastImportEnd = statementEnd + 1;
3293
+ cursor = statementEnd + 1;
3294
+ continue;
3295
+ }
3296
+ const lineEnd = code.indexOf("\n", cursor);
3297
+ lastImportEnd = lineEnd === -1 ? code.length : lineEnd + 1;
3298
+ cursor = lastImportEnd;
3299
+ }
3300
+ if (lastImportEnd === -1) return;
3301
+ return code.slice(0, lastImportEnd) + snippet + code.slice(lastImportEnd);
3302
+ }
3225
3303
  /**
3226
3304
  * Plugin that runs FIRST to create virtual module files in the config hook.
3227
3305
  * This prevents 504 "Outdated Optimize Dep" errors by ensuring files exist
@@ -3456,11 +3534,9 @@ function federation(mfUserOptions) {
3456
3534
  for (const v of importedFromLoadShare) if (new RegExp("\\(" + v + "\\(\\)\\s*,\\s*\\w+\\(\\w+\\)\\)").test(code)) allInits.push(v);
3457
3535
  if (allInits.length === 0) continue;
3458
3536
  const awaits = allInits.map((v) => `await ${v}();`).join("");
3459
- const lastFromRegex = /\bfrom\s*["'][^"']*["']\s*;?/g;
3460
- let lastFromEnd = -1;
3461
- while ((m = lastFromRegex.exec(code)) !== null) lastFromEnd = m.index + m[0].length;
3462
- if (lastFromEnd !== -1) {
3463
- chunk.code = code.slice(0, lastFromEnd) + awaits + code.slice(lastFromEnd);
3537
+ const codeWithAwaits = insertAfterLastTopLevelImport(code, awaits);
3538
+ if (codeWithAwaits) {
3539
+ chunk.code = codeWithAwaits;
3464
3540
  continue;
3465
3541
  }
3466
3542
  const exportIdx = code.search(/\bexport\s*[{d]/);
@@ -3591,14 +3667,7 @@ function federation(mfUserOptions) {
3591
3667
  })) return;
3592
3668
  if (/await\s+init_\w+__loadShare__/.test(code)) return;
3593
3669
  if (code.includes("__esmMin")) return;
3594
- const awaits = [...initFns].map((fn) => `await ${fn}();`).join("\n");
3595
- const topLevelImportRe = /^import\s/gm;
3596
- let lastImportIdx = -1;
3597
- let importMatch;
3598
- while ((importMatch = topLevelImportRe.exec(code)) !== null) lastImportIdx = importMatch.index;
3599
- if (lastImportIdx === -1) return;
3600
- const lineEnd = code.indexOf("\n", lastImportIdx);
3601
- return code.slice(0, lineEnd + 1) + awaits + "\n" + code.slice(lineEnd + 1);
3670
+ return insertAfterLastTopLevelImport(code, [...initFns].map((fn) => `await ${fn}();`).join("\n") + "\n");
3602
3671
  }
3603
3672
  },
3604
3673
  PluginDevProxyModuleTopLevelAwait(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/vite",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
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.2",
74
+ "@module-federation/runtime": "2.3.2",
75
+ "@module-federation/sdk": "2.3.2",
76
76
  "@rollup/pluginutils": "^5.3.0",
77
77
  "defu": "^6.1.4",
78
78
  "es-module-lexer": "^2.0.0",