@module-federation/vite 1.14.4 → 1.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -190,15 +190,20 @@ const RemoteMFE = defineAsyncComponent( 👈
190
190
  </template>
191
191
  ```
192
192
 
193
- ## ⚠️ `codeSplitting: false` is not supported
193
+ ## ⚠️ `codeSplitting` settings are controlled by the plugin
194
194
 
195
- Do not set `build.rolldownOptions.output.codeSplitting` to `false` with this plugin — it will be **automatically ignored**.
196
- Module federation requires chunk splitting to isolate shared dependencies and remote entries into separate chunks.
195
+ Do not set either `build.rollupOptions.output.codeSplitting` or
196
+ `build.rolldownOptions.output.codeSplitting` to `false` with this plugin it will be **automatically ignored**.
197
+
198
+ `codeSplitting.groups` is also ignored because grouping shared-runtime chunks can break MF init order.
199
+ Module Federation needs `loadShare` and `runtimeInitStatus` isolated into separate chunks for correct bootstrap behavior.
197
200
 
198
201
  ## ⚠️ `manualChunks` is not supported
199
202
 
200
- Do not use `build.rollupOptions.output.manualChunks` with this plugin — it will be **automatically ignored**.
203
+ Do not use `build.rollupOptions.output.manualChunks` or
204
+ `build.rolldownOptions.output.manualChunks` with this plugin — it will be **automatically ignored**.
201
205
  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.
206
+ The plugin injects its own split so `runtimeInitStatus` and `loadShare` are kept isolated.
202
207
 
203
208
  ### So far so good 🎉
204
209
 
package/lib/index.cjs CHANGED
@@ -117,6 +117,15 @@ function setPackageDetectionCwd(cwd) {
117
117
  function getPackageDetectionCwd() {
118
118
  return packageDetectionCwd || process.cwd();
119
119
  }
120
+ function resolveExportsEntry(exportsField) {
121
+ if (typeof exportsField === "string") return exportsField;
122
+ if (!exportsField || typeof exportsField !== "object") return void 0;
123
+ const rootExport = exportsField["."];
124
+ if (typeof rootExport === "string") return rootExport;
125
+ if (!rootExport || typeof rootExport !== "object") return void 0;
126
+ const rootExportObject = rootExport;
127
+ return typeof rootExportObject.import === "string" && rootExportObject.import || typeof rootExportObject.default === "string" && rootExportObject.default || typeof rootExportObject.require === "string" && rootExportObject.require || void 0;
128
+ }
120
129
  /**
121
130
  * Escaping rules:
122
131
  * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names.
@@ -155,6 +164,34 @@ function removePathFromNpmPackage(packageString) {
155
164
  function getInstalledPackageJson(pkg, opts) {
156
165
  const cwd = opts?.cwd || getPackageDetectionCwd();
157
166
  const packageName = opts?.packageName || removePathFromNpmPackage(pkg);
167
+ const tryReadPackageJson = (packageJsonPath) => {
168
+ if (!(0, fs.existsSync)(packageJsonPath)) return void 0;
169
+ try {
170
+ return {
171
+ path: packageJsonPath,
172
+ dir: pathe.default.dirname(packageJsonPath),
173
+ packageJson: JSON.parse((0, fs.readFileSync)(packageJsonPath, "utf-8"))
174
+ };
175
+ } catch {
176
+ return;
177
+ }
178
+ };
179
+ const findPackageInPnpmStore = (startDir) => {
180
+ let currentDir = startDir;
181
+ const rootDir = pathe.default.parse(currentDir).root;
182
+ while (true) {
183
+ const pnpmStoreDir = pathe.default.join(currentDir, "node_modules", ".pnpm");
184
+ if ((0, fs.existsSync)(pnpmStoreDir)) try {
185
+ for (const entry of (0, fs.readdirSync)(pnpmStoreDir, { withFileTypes: true })) {
186
+ if (!entry.isDirectory()) continue;
187
+ const candidate = tryReadPackageJson(pathe.default.join(pnpmStoreDir, entry.name, "node_modules", packageName, "package.json"));
188
+ if (candidate?.packageJson.name === packageName) return candidate;
189
+ }
190
+ } catch {}
191
+ if (currentDir === rootDir) break;
192
+ currentDir = pathe.default.dirname(currentDir);
193
+ }
194
+ };
158
195
  try {
159
196
  const projectRequire = (0, module$1.createRequire)(new URL(`file://${pathe.default.join(cwd, "package.json")}`));
160
197
  let resolvedPath;
@@ -187,21 +224,21 @@ function getInstalledPackageJson(pkg, opts) {
187
224
  let currentDir = cwd;
188
225
  const rootDir = pathe.default.parse(currentDir).root;
189
226
  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
- }
227
+ const directCandidate = tryReadPackageJson(pathe.default.join(currentDir, "node_modules", packageName, "package.json"));
228
+ if (directCandidate?.packageJson.name === packageName) return directCandidate;
200
229
  if (currentDir === rootDir) break;
201
230
  currentDir = pathe.default.dirname(currentDir);
202
231
  }
232
+ return findPackageInPnpmStore(cwd);
203
233
  }
204
234
  }
235
+ function getInstalledPackageEntry(pkg, opts) {
236
+ const installed = getInstalledPackageJson(pkg, opts);
237
+ if (!installed) return void 0;
238
+ const packageJson = installed.packageJson;
239
+ const explicitEntry = resolveExportsEntry(packageJson.exports) || (typeof packageJson.module === "string" ? packageJson.module : void 0) || (typeof packageJson.main === "string" ? packageJson.main : void 0) || "index.js";
240
+ return pathe.default.join(installed.dir, explicitEntry);
241
+ }
205
242
  /**
206
243
  * Detect whether the current runtime is Vite 8+ by checking for a Vite version flag
207
244
  * on the plugin hook context, with Rolldown metadata kept as a compatibility fallback.
@@ -927,6 +964,14 @@ function pluginDts(options) {
927
964
  }
928
965
  //#endregion
929
966
  //#region src/utils/normalizeModuleFederationOptions.ts
967
+ const INTERNAL_NAME_PREFIX = "__mfe_internal__";
968
+ function toInternalModuleFederationName(name) {
969
+ return name.startsWith(INTERNAL_NAME_PREFIX) ? name : `${INTERNAL_NAME_PREFIX}${name}`;
970
+ }
971
+ function warnOnReservedInternalNamePrefix(name, kind) {
972
+ if (!name.startsWith(INTERNAL_NAME_PREFIX)) return;
973
+ mfWarn(`Reserved internal ${kind} prefix "${INTERNAL_NAME_PREFIX}" detected in public ${kind} "${name}". This prefix is reserved for internal module federation names and may cause conflicts.`);
974
+ }
930
975
  function normalizeExposesItem(key, item) {
931
976
  let importPath = "";
932
977
  if (typeof item === "string") importPath = item;
@@ -950,6 +995,7 @@ function normalizeRemotes(remotes) {
950
995
  return result;
951
996
  }
952
997
  function normalizeRemoteItem(key, remote) {
998
+ warnOnReservedInternalNamePrefix(key, "remoteAlias");
953
999
  if (typeof remote === "string") {
954
1000
  const separatorIndex = remote.startsWith("@") ? remote.indexOf("@", 1) : remote.indexOf("@");
955
1001
  let entryGlobalName;
@@ -964,6 +1010,7 @@ function normalizeRemoteItem(key, remote) {
964
1010
  return {
965
1011
  type: "var",
966
1012
  name: key,
1013
+ internalName: toInternalModuleFederationName(key),
967
1014
  entry,
968
1015
  entryGlobalName,
969
1016
  shareScope: "default"
@@ -972,9 +1019,13 @@ function normalizeRemoteItem(key, remote) {
972
1019
  return Object.assign({
973
1020
  type: "var",
974
1021
  name: key,
1022
+ internalName: toInternalModuleFederationName(key),
975
1023
  shareScope: "default",
976
1024
  entryGlobalName: key
977
- }, remote);
1025
+ }, {
1026
+ ...remote,
1027
+ internalName: toInternalModuleFederationName(remote.name || key)
1028
+ });
978
1029
  }
979
1030
  /**
980
1031
  * Tries to find the package.json's version of a shared package
@@ -1095,10 +1146,12 @@ function getNormalizeShareItem(key) {
1095
1146
  return options.shared[key] || options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"];
1096
1147
  }
1097
1148
  function normalizeModuleFederationOptions(options) {
1149
+ warnOnReservedInternalNamePrefix(options.name, "containerName");
1098
1150
  if (options.virtualModuleDir && options.virtualModuleDir.includes("/")) throw createModuleFederationError(`Invalid virtualModuleDir: "${options.virtualModuleDir}". The virtualModuleDir option cannot contain slashes (/). Please use a single directory name like '__mf__virtual__your_app_name'.`);
1099
1151
  return config = {
1100
1152
  exposes: normalizeExposes(options.exposes),
1101
1153
  filename: options.filename || "remoteEntry-[hash]",
1154
+ internalName: toInternalModuleFederationName(options.name),
1102
1155
  library: normalizeLibrary(options.library),
1103
1156
  name: options.name,
1104
1157
  remotes: normalizeRemotes(options.remotes),
@@ -1273,7 +1326,7 @@ var VirtualModule = class {
1273
1326
  return (0, pathe.resolve)(getNodeModulesDir(), this.getImportId());
1274
1327
  }
1275
1328
  getImportId() {
1276
- const { name: mfName, virtualModuleDir } = getNormalizeModuleFederationOptions();
1329
+ const { internalName: mfName, virtualModuleDir } = getNormalizeModuleFederationOptions();
1277
1330
  return `${virtualModuleDir}/${packageNameEncode(`${mfName}${this.tag}${this.name}${this.tag}`)}${this.suffix}`;
1278
1331
  }
1279
1332
  writeSync(code, force) {
@@ -1296,7 +1349,7 @@ function getExposesCssMapPlaceholder() {
1296
1349
  return EXPOSES_CSS_MAP_PLACEHOLDER;
1297
1350
  }
1298
1351
  function getVirtualExposesId(options) {
1299
- return `virtual:mf-exposes:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1352
+ return `virtual:mf-exposes:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1300
1353
  }
1301
1354
  function generateExposes(options) {
1302
1355
  return `
@@ -1832,7 +1885,7 @@ function generateLocalSharedImportMap() {
1832
1885
  version: ${JSON.stringify(shareItem.version)},
1833
1886
  scope: [${JSON.stringify(shareItem.scope)}],
1834
1887
  loaded: false,
1835
- from: ${JSON.stringify(options.name)},
1888
+ from: ${JSON.stringify(options.internalName)},
1836
1889
  async get () {
1837
1890
  if (${shareItem.shareConfig.import === false}) {
1838
1891
  throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(key)}}' must be provided by host\`);
@@ -1883,7 +1936,7 @@ function generateLocalSharedImportMap() {
1883
1936
  }
1884
1937
  const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
1885
1938
  function getRemoteEntryId(options) {
1886
- return `${REMOTE_ENTRY_ID}:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1939
+ return `${REMOTE_ENTRY_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1887
1940
  }
1888
1941
  function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
1889
1942
  const pluginImportNames = options.runtimePlugins.map((p, i) => {
@@ -1912,7 +1965,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1912
1965
  ${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
1913
1966
  const initTokens = {}
1914
1967
  const shareScopeName = ${JSON.stringify(options.shareScope)}
1915
- const mfName = ${JSON.stringify(options.name)}
1968
+ const mfName = ${JSON.stringify(options.internalName)}
1916
1969
  let localSharedImportMapPromise
1917
1970
  let exposesMapPromise
1918
1971
 
@@ -2479,7 +2532,7 @@ function pluginModuleParseEnd_default(excludeFn, options) {
2479
2532
  }
2480
2533
  //#endregion
2481
2534
  //#region src/plugins/pluginProxyRemoteEntry.ts
2482
- const filter = (0, _rollup_pluginutils.createFilter)();
2535
+ const filter$1 = (0, _rollup_pluginutils.createFilter)();
2483
2536
  function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposesId }) {
2484
2537
  let viteConfig, _command, root;
2485
2538
  return {
@@ -2519,7 +2572,7 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
2519
2572
  },
2520
2573
  transform(code, id) {
2521
2574
  return mapCodeToCodeWithSourcemap((() => {
2522
- if (!filter(id)) return;
2575
+ if (!filter$1(id)) return;
2523
2576
  if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
2524
2577
  if (id === virtualExposesId) return generateExposes(options);
2525
2578
  if (id.includes(getHostAutoInitPath())) {
@@ -2592,25 +2645,47 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
2592
2645
  }
2593
2646
  //#endregion
2594
2647
  //#region src/plugins/pluginProxyRemotes.ts
2595
- (0, _rollup_pluginutils.createFilter)();
2648
+ const filter = (0, _rollup_pluginutils.createFilter)();
2649
+ function isNodeModulesImporter(importer) {
2650
+ return importer?.includes("/node_modules/") || importer?.includes("\\node_modules\\");
2651
+ }
2596
2652
  function pluginProxyRemotes_default(options) {
2653
+ let command;
2654
+ let root = process.cwd();
2597
2655
  const { remotes } = options;
2656
+ function resolveRemoteId(source, importer, remoteName, isRolldown) {
2657
+ if (source === remoteName) {
2658
+ const installedPackageEntry = getInstalledPackageEntry(source, { cwd: root });
2659
+ if (installedPackageEntry && (importer === void 0 || isNodeModulesImporter(importer))) return installedPackageEntry;
2660
+ }
2661
+ const remoteModule = getRemoteVirtualModule(source, command, isRolldown);
2662
+ addUsedRemote(remoteName, source);
2663
+ return remoteModule.getPath();
2664
+ }
2598
2665
  return {
2599
2666
  name: "proxyRemotes",
2600
2667
  config(config, { command: _command }) {
2668
+ command = _command;
2669
+ root = config.root || process.cwd();
2601
2670
  const isRolldown = getIsRolldown(this);
2602
2671
  Object.keys(remotes).forEach((key) => {
2603
2672
  const remote = remotes[key];
2604
2673
  config.resolve.alias.push({
2605
2674
  find: new RegExp(`^(${remote.name}(\/.*|$))`),
2606
2675
  replacement: "$1",
2607
- customResolver(source) {
2608
- const remoteModule = getRemoteVirtualModule(source, _command, isRolldown);
2609
- addUsedRemote(remote.name, source);
2610
- return remoteModule.getPath();
2676
+ customResolver(source, importer) {
2677
+ return resolveRemoteId(source, importer, remote.name, isRolldown);
2611
2678
  }
2612
2679
  });
2613
2680
  });
2681
+ },
2682
+ resolveId(source, importer) {
2683
+ if (!filter(source)) return;
2684
+ const isRolldown = getIsRolldown(this);
2685
+ for (const remote of Object.values(remotes)) {
2686
+ if (source !== remote.name) continue;
2687
+ return resolveRemoteId(source, importer, remote.name, isRolldown);
2688
+ }
2614
2689
  }
2615
2690
  };
2616
2691
  }
@@ -3086,8 +3161,13 @@ function collectFromRegex(code, isRemoteImport) {
3086
3161
  }
3087
3162
  function pluginRemoteNamedExports(options) {
3088
3163
  const remoteNames = Object.keys(options.remotes);
3089
- function isRemoteImport(source) {
3090
- return remoteNames.some((name) => source === name || source.startsWith(name + "/")) || source.includes("__loadRemote__");
3164
+ const isNodeModulesId = (id) => id.includes("/node_modules/") || id.includes("\\node_modules\\");
3165
+ function isRemoteImport(source, importerId) {
3166
+ return remoteNames.some((name) => {
3167
+ if (source.startsWith(name + "/")) return true;
3168
+ if (source !== name) return false;
3169
+ return !isNodeModulesId(importerId);
3170
+ }) || source.includes("__loadRemote__");
3091
3171
  }
3092
3172
  return {
3093
3173
  name: "module-federation-remote-named-exports",
@@ -3098,13 +3178,14 @@ function pluginRemoteNamedExports(options) {
3098
3178
  if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
3099
3179
  if (!JS_EXTENSIONS_RE.test(id)) return;
3100
3180
  if (!remoteNames.some((name) => code.includes(name))) return;
3181
+ const matchesRemoteImport = (source) => isRemoteImport(source, id);
3101
3182
  let imports;
3102
3183
  try {
3103
- imports = await collectFromAST(this.parse(code), code, isRemoteImport);
3184
+ imports = await collectFromAST(this.parse(code), code, matchesRemoteImport);
3104
3185
  } catch {
3105
- imports = await collectFromEsLexer(code, isRemoteImport);
3186
+ imports = await collectFromEsLexer(code, matchesRemoteImport);
3106
3187
  }
3107
- if ((!imports || imports.length === 0) && REGEX_FALLBACK_EXTENSIONS_RE.test(id)) imports = collectFromRegex(code, isRemoteImport);
3188
+ if ((!imports || imports.length === 0) && REGEX_FALLBACK_EXTENSIONS_RE.test(id)) imports = collectFromRegex(code, matchesRemoteImport);
3108
3189
  if (!imports) return;
3109
3190
  return applyRewrites(code, imports, id);
3110
3191
  }
@@ -3365,16 +3446,23 @@ function createEarlyVirtualModulesPlugin(options) {
3365
3446
  VirtualModule.ensureVirtualPackageExists();
3366
3447
  initVirtualModules(_command, getRemoteEntryId(options));
3367
3448
  const isRolldown = getIsRolldown(this);
3368
- if (remotes && Object.keys(remotes).length > 0) for (const key of Object.keys(remotes)) addUsedRemote(key, key);
3449
+ if (remotes && Object.keys(remotes).length > 0) {
3450
+ for (const key of Object.keys(remotes)) addUsedRemote(key, key);
3451
+ if (_command === "serve" && isRolldown) {
3452
+ config.optimizeDeps = config.optimizeDeps || {};
3453
+ config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
3454
+ config.optimizeDeps.include = config.optimizeDeps.include || [];
3455
+ const collidingInstalledRemotes = Object.keys(remotes || {}).filter((remoteName) => getInstalledPackageJson(remoteName, { cwd: root }));
3456
+ const collidingInstalledRemoteEntries = collidingInstalledRemotes.map((remoteName) => getInstalledPackageEntry(remoteName, { cwd: root }) || remoteName);
3457
+ config.optimizeDeps.exclude.push(...Object.keys(remotes || {}).filter((remoteName) => !collidingInstalledRemotes.includes(remoteName)));
3458
+ config.optimizeDeps.include.push(...collidingInstalledRemoteEntries);
3459
+ }
3460
+ }
3369
3461
  if (shared && Object.keys(shared).length > 0) {
3370
3462
  if (_command === "serve") {
3371
3463
  config.optimizeDeps = config.optimizeDeps || {};
3372
3464
  config.optimizeDeps.include = config.optimizeDeps.include || [];
3373
3465
  config.optimizeDeps.include.push(virtualRuntimeInitStatus.getImportId());
3374
- if (isRolldown) {
3375
- config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
3376
- config.optimizeDeps.exclude.push(...Object.keys(remotes || {}));
3377
- }
3378
3466
  }
3379
3467
  for (const key of Object.keys(shared)) {
3380
3468
  if (key.endsWith("/")) continue;
@@ -3837,12 +3925,12 @@ function federation(mfUserOptions) {
3837
3925
  const prefixToRoot = chunkDir === "." ? "" : `${pathe.default.relative(chunkDir, ".")}/`;
3838
3926
  const replacementExpr = prefixToRoot ? `${escapeUnsafeJsSourceChars(JSON.stringify(prefixToRoot))}+$1` : "$1";
3839
3927
  const replacement = `=function($1){return new URL(${replacementExpr},import.meta.url).href}`;
3840
- const replaced = chunk.code.replace(/=\(?(\w+)(?:,\w+)?\)?\s*=>\s*["'][./][^"']*["']\s*\+\s*\1/, replacement);
3928
+ const replaced = chunk.code.replace(/=\(?(\w+)(?:,\w+)?\)?\s*=>\s*[`"'][./][^`"']*[`"']\s*\+\s*\1/, replacement);
3841
3929
  if (replaced !== chunk.code) {
3842
3930
  chunk.code = replaced;
3843
3931
  continue;
3844
3932
  }
3845
- chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return\s*["'][./][^"']*["']\s*\+\s*\1\s*\}/, replacement);
3933
+ chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return\s*[`"'][./][^`"']*[`"']\s*\+\s*\1\s*\}/, replacement);
3846
3934
  chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return new URL\("\.\.\/"\+\1,import\.meta\.url\)\.href\}/, replacement);
3847
3935
  chunk.code = chunk.code.replace(/new URL\("\.\.\/"\+(\w+),import\.meta\.url\)\.href/g, `new URL(${replacementExpr},import.meta.url).href`);
3848
3936
  }
package/lib/index.d.cts CHANGED
@@ -6,6 +6,7 @@ import { sharePlugin } from "@module-federation/sdk";
6
6
  interface RemoteObjectConfig {
7
7
  type?: string;
8
8
  name: string;
9
+ internalName?: string;
9
10
  entry: string;
10
11
  entryGlobalName?: string;
11
12
  shareScope?: string;
package/lib/index.d.mts CHANGED
@@ -6,6 +6,7 @@ import { ShareStrategy } from "@module-federation/runtime/types";
6
6
  interface RemoteObjectConfig {
7
7
  type?: string;
8
8
  name: string;
9
+ internalName?: string;
9
10
  entry: string;
10
11
  entryGlobalName?: string;
11
12
  shareScope?: string;
package/lib/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import defu from "defu";
3
3
  import * as fs from "fs";
4
- import { existsSync, mkdirSync, readFileSync, statSync, writeFile, writeFileSync } from "fs";
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFile, writeFileSync } from "fs";
5
5
  import { createRequire as createRequire$1 } from "module";
6
6
  import * as path$1 from "pathe";
7
7
  import path, { basename, dirname, join, parse, resolve } from "pathe";
@@ -95,6 +95,15 @@ function setPackageDetectionCwd(cwd) {
95
95
  function getPackageDetectionCwd() {
96
96
  return packageDetectionCwd || process.cwd();
97
97
  }
98
+ function resolveExportsEntry(exportsField) {
99
+ if (typeof exportsField === "string") return exportsField;
100
+ if (!exportsField || typeof exportsField !== "object") return void 0;
101
+ const rootExport = exportsField["."];
102
+ if (typeof rootExport === "string") return rootExport;
103
+ if (!rootExport || typeof rootExport !== "object") return void 0;
104
+ const rootExportObject = rootExport;
105
+ return typeof rootExportObject.import === "string" && rootExportObject.import || typeof rootExportObject.default === "string" && rootExportObject.default || typeof rootExportObject.require === "string" && rootExportObject.require || void 0;
106
+ }
98
107
  /**
99
108
  * Escaping rules:
100
109
  * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names.
@@ -133,6 +142,34 @@ function removePathFromNpmPackage(packageString) {
133
142
  function getInstalledPackageJson(pkg, opts) {
134
143
  const cwd = opts?.cwd || getPackageDetectionCwd();
135
144
  const packageName = opts?.packageName || removePathFromNpmPackage(pkg);
145
+ const tryReadPackageJson = (packageJsonPath) => {
146
+ if (!existsSync(packageJsonPath)) return void 0;
147
+ try {
148
+ return {
149
+ path: packageJsonPath,
150
+ dir: path.dirname(packageJsonPath),
151
+ packageJson: JSON.parse(readFileSync(packageJsonPath, "utf-8"))
152
+ };
153
+ } catch {
154
+ return;
155
+ }
156
+ };
157
+ const findPackageInPnpmStore = (startDir) => {
158
+ let currentDir = startDir;
159
+ const rootDir = path.parse(currentDir).root;
160
+ while (true) {
161
+ const pnpmStoreDir = path.join(currentDir, "node_modules", ".pnpm");
162
+ if (existsSync(pnpmStoreDir)) try {
163
+ for (const entry of readdirSync(pnpmStoreDir, { withFileTypes: true })) {
164
+ if (!entry.isDirectory()) continue;
165
+ const candidate = tryReadPackageJson(path.join(pnpmStoreDir, entry.name, "node_modules", packageName, "package.json"));
166
+ if (candidate?.packageJson.name === packageName) return candidate;
167
+ }
168
+ } catch {}
169
+ if (currentDir === rootDir) break;
170
+ currentDir = path.dirname(currentDir);
171
+ }
172
+ };
136
173
  try {
137
174
  const projectRequire = createRequire$1(new URL(`file://${path.join(cwd, "package.json")}`));
138
175
  let resolvedPath;
@@ -165,21 +202,21 @@ function getInstalledPackageJson(pkg, opts) {
165
202
  let currentDir = cwd;
166
203
  const rootDir = path.parse(currentDir).root;
167
204
  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
- }
205
+ const directCandidate = tryReadPackageJson(path.join(currentDir, "node_modules", packageName, "package.json"));
206
+ if (directCandidate?.packageJson.name === packageName) return directCandidate;
178
207
  if (currentDir === rootDir) break;
179
208
  currentDir = path.dirname(currentDir);
180
209
  }
210
+ return findPackageInPnpmStore(cwd);
181
211
  }
182
212
  }
213
+ function getInstalledPackageEntry(pkg, opts) {
214
+ const installed = getInstalledPackageJson(pkg, opts);
215
+ if (!installed) return void 0;
216
+ const packageJson = installed.packageJson;
217
+ const explicitEntry = resolveExportsEntry(packageJson.exports) || (typeof packageJson.module === "string" ? packageJson.module : void 0) || (typeof packageJson.main === "string" ? packageJson.main : void 0) || "index.js";
218
+ return path.join(installed.dir, explicitEntry);
219
+ }
183
220
  /**
184
221
  * Detect whether the current runtime is Vite 8+ by checking for a Vite version flag
185
222
  * on the plugin hook context, with Rolldown metadata kept as a compatibility fallback.
@@ -905,6 +942,14 @@ function pluginDts(options) {
905
942
  }
906
943
  //#endregion
907
944
  //#region src/utils/normalizeModuleFederationOptions.ts
945
+ const INTERNAL_NAME_PREFIX = "__mfe_internal__";
946
+ function toInternalModuleFederationName(name) {
947
+ return name.startsWith(INTERNAL_NAME_PREFIX) ? name : `${INTERNAL_NAME_PREFIX}${name}`;
948
+ }
949
+ function warnOnReservedInternalNamePrefix(name, kind) {
950
+ if (!name.startsWith(INTERNAL_NAME_PREFIX)) return;
951
+ mfWarn(`Reserved internal ${kind} prefix "${INTERNAL_NAME_PREFIX}" detected in public ${kind} "${name}". This prefix is reserved for internal module federation names and may cause conflicts.`);
952
+ }
908
953
  function normalizeExposesItem(key, item) {
909
954
  let importPath = "";
910
955
  if (typeof item === "string") importPath = item;
@@ -928,6 +973,7 @@ function normalizeRemotes(remotes) {
928
973
  return result;
929
974
  }
930
975
  function normalizeRemoteItem(key, remote) {
976
+ warnOnReservedInternalNamePrefix(key, "remoteAlias");
931
977
  if (typeof remote === "string") {
932
978
  const separatorIndex = remote.startsWith("@") ? remote.indexOf("@", 1) : remote.indexOf("@");
933
979
  let entryGlobalName;
@@ -942,6 +988,7 @@ function normalizeRemoteItem(key, remote) {
942
988
  return {
943
989
  type: "var",
944
990
  name: key,
991
+ internalName: toInternalModuleFederationName(key),
945
992
  entry,
946
993
  entryGlobalName,
947
994
  shareScope: "default"
@@ -950,9 +997,13 @@ function normalizeRemoteItem(key, remote) {
950
997
  return Object.assign({
951
998
  type: "var",
952
999
  name: key,
1000
+ internalName: toInternalModuleFederationName(key),
953
1001
  shareScope: "default",
954
1002
  entryGlobalName: key
955
- }, remote);
1003
+ }, {
1004
+ ...remote,
1005
+ internalName: toInternalModuleFederationName(remote.name || key)
1006
+ });
956
1007
  }
957
1008
  /**
958
1009
  * Tries to find the package.json's version of a shared package
@@ -1072,10 +1123,12 @@ function getNormalizeShareItem(key) {
1072
1123
  return options.shared[key] || options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"];
1073
1124
  }
1074
1125
  function normalizeModuleFederationOptions(options) {
1126
+ warnOnReservedInternalNamePrefix(options.name, "containerName");
1075
1127
  if (options.virtualModuleDir && options.virtualModuleDir.includes("/")) throw createModuleFederationError(`Invalid virtualModuleDir: "${options.virtualModuleDir}". The virtualModuleDir option cannot contain slashes (/). Please use a single directory name like '__mf__virtual__your_app_name'.`);
1076
1128
  return config = {
1077
1129
  exposes: normalizeExposes(options.exposes),
1078
1130
  filename: options.filename || "remoteEntry-[hash]",
1131
+ internalName: toInternalModuleFederationName(options.name),
1079
1132
  library: normalizeLibrary(options.library),
1080
1133
  name: options.name,
1081
1134
  remotes: normalizeRemotes(options.remotes),
@@ -1250,7 +1303,7 @@ var VirtualModule = class {
1250
1303
  return resolve(getNodeModulesDir(), this.getImportId());
1251
1304
  }
1252
1305
  getImportId() {
1253
- const { name: mfName, virtualModuleDir } = getNormalizeModuleFederationOptions();
1306
+ const { internalName: mfName, virtualModuleDir } = getNormalizeModuleFederationOptions();
1254
1307
  return `${virtualModuleDir}/${packageNameEncode(`${mfName}${this.tag}${this.name}${this.tag}`)}${this.suffix}`;
1255
1308
  }
1256
1309
  writeSync(code, force) {
@@ -1273,7 +1326,7 @@ function getExposesCssMapPlaceholder() {
1273
1326
  return EXPOSES_CSS_MAP_PLACEHOLDER;
1274
1327
  }
1275
1328
  function getVirtualExposesId(options) {
1276
- return `virtual:mf-exposes:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1329
+ return `virtual:mf-exposes:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1277
1330
  }
1278
1331
  function generateExposes(options) {
1279
1332
  return `
@@ -1809,7 +1862,7 @@ function generateLocalSharedImportMap() {
1809
1862
  version: ${JSON.stringify(shareItem.version)},
1810
1863
  scope: [${JSON.stringify(shareItem.scope)}],
1811
1864
  loaded: false,
1812
- from: ${JSON.stringify(options.name)},
1865
+ from: ${JSON.stringify(options.internalName)},
1813
1866
  async get () {
1814
1867
  if (${shareItem.shareConfig.import === false}) {
1815
1868
  throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(key)}}' must be provided by host\`);
@@ -1860,7 +1913,7 @@ function generateLocalSharedImportMap() {
1860
1913
  }
1861
1914
  const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
1862
1915
  function getRemoteEntryId(options) {
1863
- return `${REMOTE_ENTRY_ID}:${`${options.name}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1916
+ return `${REMOTE_ENTRY_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
1864
1917
  }
1865
1918
  function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
1866
1919
  const pluginImportNames = options.runtimePlugins.map((p, i) => {
@@ -1889,7 +1942,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
1889
1942
  ${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
1890
1943
  const initTokens = {}
1891
1944
  const shareScopeName = ${JSON.stringify(options.shareScope)}
1892
- const mfName = ${JSON.stringify(options.name)}
1945
+ const mfName = ${JSON.stringify(options.internalName)}
1893
1946
  let localSharedImportMapPromise
1894
1947
  let exposesMapPromise
1895
1948
 
@@ -2456,7 +2509,7 @@ function pluginModuleParseEnd_default(excludeFn, options) {
2456
2509
  }
2457
2510
  //#endregion
2458
2511
  //#region src/plugins/pluginProxyRemoteEntry.ts
2459
- const filter = createFilter();
2512
+ const filter$1 = createFilter();
2460
2513
  function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposesId }) {
2461
2514
  let viteConfig, _command, root;
2462
2515
  return {
@@ -2496,7 +2549,7 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
2496
2549
  },
2497
2550
  transform(code, id) {
2498
2551
  return mapCodeToCodeWithSourcemap((() => {
2499
- if (!filter(id)) return;
2552
+ if (!filter$1(id)) return;
2500
2553
  if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
2501
2554
  if (id === virtualExposesId) return generateExposes(options);
2502
2555
  if (id.includes(getHostAutoInitPath())) {
@@ -2569,25 +2622,47 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
2569
2622
  }
2570
2623
  //#endregion
2571
2624
  //#region src/plugins/pluginProxyRemotes.ts
2572
- createFilter();
2625
+ const filter = createFilter();
2626
+ function isNodeModulesImporter(importer) {
2627
+ return importer?.includes("/node_modules/") || importer?.includes("\\node_modules\\");
2628
+ }
2573
2629
  function pluginProxyRemotes_default(options) {
2630
+ let command;
2631
+ let root = process.cwd();
2574
2632
  const { remotes } = options;
2633
+ function resolveRemoteId(source, importer, remoteName, isRolldown) {
2634
+ if (source === remoteName) {
2635
+ const installedPackageEntry = getInstalledPackageEntry(source, { cwd: root });
2636
+ if (installedPackageEntry && (importer === void 0 || isNodeModulesImporter(importer))) return installedPackageEntry;
2637
+ }
2638
+ const remoteModule = getRemoteVirtualModule(source, command, isRolldown);
2639
+ addUsedRemote(remoteName, source);
2640
+ return remoteModule.getPath();
2641
+ }
2575
2642
  return {
2576
2643
  name: "proxyRemotes",
2577
2644
  config(config, { command: _command }) {
2645
+ command = _command;
2646
+ root = config.root || process.cwd();
2578
2647
  const isRolldown = getIsRolldown(this);
2579
2648
  Object.keys(remotes).forEach((key) => {
2580
2649
  const remote = remotes[key];
2581
2650
  config.resolve.alias.push({
2582
2651
  find: new RegExp(`^(${remote.name}(\/.*|$))`),
2583
2652
  replacement: "$1",
2584
- customResolver(source) {
2585
- const remoteModule = getRemoteVirtualModule(source, _command, isRolldown);
2586
- addUsedRemote(remote.name, source);
2587
- return remoteModule.getPath();
2653
+ customResolver(source, importer) {
2654
+ return resolveRemoteId(source, importer, remote.name, isRolldown);
2588
2655
  }
2589
2656
  });
2590
2657
  });
2658
+ },
2659
+ resolveId(source, importer) {
2660
+ if (!filter(source)) return;
2661
+ const isRolldown = getIsRolldown(this);
2662
+ for (const remote of Object.values(remotes)) {
2663
+ if (source !== remote.name) continue;
2664
+ return resolveRemoteId(source, importer, remote.name, isRolldown);
2665
+ }
2591
2666
  }
2592
2667
  };
2593
2668
  }
@@ -3063,8 +3138,13 @@ function collectFromRegex(code, isRemoteImport) {
3063
3138
  }
3064
3139
  function pluginRemoteNamedExports(options) {
3065
3140
  const remoteNames = Object.keys(options.remotes);
3066
- function isRemoteImport(source) {
3067
- return remoteNames.some((name) => source === name || source.startsWith(name + "/")) || source.includes("__loadRemote__");
3141
+ const isNodeModulesId = (id) => id.includes("/node_modules/") || id.includes("\\node_modules\\");
3142
+ function isRemoteImport(source, importerId) {
3143
+ return remoteNames.some((name) => {
3144
+ if (source.startsWith(name + "/")) return true;
3145
+ if (source !== name) return false;
3146
+ return !isNodeModulesId(importerId);
3147
+ }) || source.includes("__loadRemote__");
3068
3148
  }
3069
3149
  return {
3070
3150
  name: "module-federation-remote-named-exports",
@@ -3075,13 +3155,14 @@ function pluginRemoteNamedExports(options) {
3075
3155
  if (id.includes("__loadRemote__") || id.includes("__loadShare__")) return;
3076
3156
  if (!JS_EXTENSIONS_RE.test(id)) return;
3077
3157
  if (!remoteNames.some((name) => code.includes(name))) return;
3158
+ const matchesRemoteImport = (source) => isRemoteImport(source, id);
3078
3159
  let imports;
3079
3160
  try {
3080
- imports = await collectFromAST(this.parse(code), code, isRemoteImport);
3161
+ imports = await collectFromAST(this.parse(code), code, matchesRemoteImport);
3081
3162
  } catch {
3082
- imports = await collectFromEsLexer(code, isRemoteImport);
3163
+ imports = await collectFromEsLexer(code, matchesRemoteImport);
3083
3164
  }
3084
- if ((!imports || imports.length === 0) && REGEX_FALLBACK_EXTENSIONS_RE.test(id)) imports = collectFromRegex(code, isRemoteImport);
3165
+ if ((!imports || imports.length === 0) && REGEX_FALLBACK_EXTENSIONS_RE.test(id)) imports = collectFromRegex(code, matchesRemoteImport);
3085
3166
  if (!imports) return;
3086
3167
  return applyRewrites(code, imports, id);
3087
3168
  }
@@ -3342,16 +3423,23 @@ function createEarlyVirtualModulesPlugin(options) {
3342
3423
  VirtualModule.ensureVirtualPackageExists();
3343
3424
  initVirtualModules(_command, getRemoteEntryId(options));
3344
3425
  const isRolldown = getIsRolldown(this);
3345
- if (remotes && Object.keys(remotes).length > 0) for (const key of Object.keys(remotes)) addUsedRemote(key, key);
3426
+ if (remotes && Object.keys(remotes).length > 0) {
3427
+ for (const key of Object.keys(remotes)) addUsedRemote(key, key);
3428
+ if (_command === "serve" && isRolldown) {
3429
+ config.optimizeDeps = config.optimizeDeps || {};
3430
+ config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
3431
+ config.optimizeDeps.include = config.optimizeDeps.include || [];
3432
+ const collidingInstalledRemotes = Object.keys(remotes || {}).filter((remoteName) => getInstalledPackageJson(remoteName, { cwd: root }));
3433
+ const collidingInstalledRemoteEntries = collidingInstalledRemotes.map((remoteName) => getInstalledPackageEntry(remoteName, { cwd: root }) || remoteName);
3434
+ config.optimizeDeps.exclude.push(...Object.keys(remotes || {}).filter((remoteName) => !collidingInstalledRemotes.includes(remoteName)));
3435
+ config.optimizeDeps.include.push(...collidingInstalledRemoteEntries);
3436
+ }
3437
+ }
3346
3438
  if (shared && Object.keys(shared).length > 0) {
3347
3439
  if (_command === "serve") {
3348
3440
  config.optimizeDeps = config.optimizeDeps || {};
3349
3441
  config.optimizeDeps.include = config.optimizeDeps.include || [];
3350
3442
  config.optimizeDeps.include.push(virtualRuntimeInitStatus.getImportId());
3351
- if (isRolldown) {
3352
- config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
3353
- config.optimizeDeps.exclude.push(...Object.keys(remotes || {}));
3354
- }
3355
3443
  }
3356
3444
  for (const key of Object.keys(shared)) {
3357
3445
  if (key.endsWith("/")) continue;
@@ -3814,12 +3902,12 @@ function federation(mfUserOptions) {
3814
3902
  const prefixToRoot = chunkDir === "." ? "" : `${path.relative(chunkDir, ".")}/`;
3815
3903
  const replacementExpr = prefixToRoot ? `${escapeUnsafeJsSourceChars(JSON.stringify(prefixToRoot))}+$1` : "$1";
3816
3904
  const replacement = `=function($1){return new URL(${replacementExpr},import.meta.url).href}`;
3817
- const replaced = chunk.code.replace(/=\(?(\w+)(?:,\w+)?\)?\s*=>\s*["'][./][^"']*["']\s*\+\s*\1/, replacement);
3905
+ const replaced = chunk.code.replace(/=\(?(\w+)(?:,\w+)?\)?\s*=>\s*[`"'][./][^`"']*[`"']\s*\+\s*\1/, replacement);
3818
3906
  if (replaced !== chunk.code) {
3819
3907
  chunk.code = replaced;
3820
3908
  continue;
3821
3909
  }
3822
- chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return\s*["'][./][^"']*["']\s*\+\s*\1\s*\}/, replacement);
3910
+ chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return\s*[`"'][./][^`"']*[`"']\s*\+\s*\1\s*\}/, replacement);
3823
3911
  chunk.code = chunk.code.replace(/=function\((\w+)(?:,\w+)?\)\{return new URL\("\.\.\/"\+\1,import\.meta\.url\)\.href\}/, replacement);
3824
3912
  chunk.code = chunk.code.replace(/new URL\("\.\.\/"\+(\w+),import\.meta\.url\)\.href/g, `new URL(${replacementExpr},import.meta.url).href`);
3825
3913
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/vite",
3
- "version": "1.14.4",
3
+ "version": "1.14.5",
4
4
  "description": "Vite plugin for Module Federation",
5
5
  "type": "module",
6
6
  "main": "./lib/index.cjs",
@@ -63,9 +63,6 @@
63
63
  },
64
64
  "homepage": "https://github.com/module-federation/vite#readme",
65
65
  "packageManager": "pnpm@10.28.2",
66
- "overrides": {
67
- "koa": "3.1.2"
68
- },
69
66
  "peerDependencies": {
70
67
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
71
68
  },
@@ -84,9 +81,7 @@
84
81
  "@changesets/cli": "^2.30.0",
85
82
  "@playwright/test": "^1.58.2",
86
83
  "@types/node": "^25.3.3",
87
- "cjs-dep": "workspace:*",
88
84
  "husky": "^9.1.7",
89
- "mime-types": "^3.0.2",
90
85
  "oxfmt": "^0.36.0",
91
86
  "rollup": "^4.47.1",
92
87
  "tsdown": "^0.21.0",