@module-federation/vite 1.22.0 → 1.23.0

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
@@ -119,6 +119,9 @@ export default defineConfig({
119
119
  // optional: additional "var" remoteEntry file
120
120
  // needed only for legacy hosts with "var" usage (remote.type = 'var')
121
121
  varFilename: "varRemoteEntry.js",
122
+ // optional: exact name of the SSR remote entry
123
+ // default: "<filename base>.ssr.js" (e.g. remoteEntry.ssr.js)
124
+ ssrFilename: "remoteEntry.ssr.js",
122
125
  exposes: {
123
126
  "./remote-app": "./src/App.vue",
124
127
  },
@@ -151,6 +154,18 @@ shared: {
151
154
 
152
155
  `suppressMissingImportWarning` defaults to `false`. Enable it only when the host is guaranteed to provide the dependency and local named-export detection is unnecessary.
153
156
 
157
+ In a production build, a host-only dependency that the exposed modules reach only through a dynamic `import()` is not fetched while the remote initializes. It is loaded from the host's provider when that `import()` runs, before the imported chunk evaluates. A dependency imported statically by an exposed module, or configured with `eager: true`, is still loaded during initialization.
158
+
159
+ ### Shared dependencies installed in several places
160
+
161
+ An absolute import that resolves inside a different `node_modules` directory than the one the share itself resolves to, for example a workspace package with its own nested copy, is not treated as the shared module by default. Set `allowNodeModulesSuffixMatch: true` on the share to match it by the path after `node_modules/`, as webpack and rspack do with the same option:
162
+
163
+ ```ts
164
+ shared: {
165
+ react: { singleton: true, allowNodeModulesSuffixMatch: true },
166
+ },
167
+ ```
168
+
154
169
  ## The Host Application configuration
155
170
 
156
171
  file **host/vite.config.ts**
@@ -263,6 +278,8 @@ federation({
263
278
 
264
279
  The `"vm"` strategy requires Node.js to run with `--experimental-vm-modules`. When unavailable, the loader warns once and falls back to `"temp-file"`. Vite 8 development uses `ModuleRunner`; this option primarily affects build and preview SSR entry loading.
265
280
 
281
+ The SSR remote entry is emitted as `<filename base>.ssr.js` next to the browser entry, or under the exact name given by `ssrFilename`, and is advertised as `metaData.ssrRemoteEntry` in `mf-manifest.json`. It keeps `@module-federation/runtime`, `@module-federation/runtime-core` and `@module-federation/sdk` external by default so Node resolves them from the host. Hosts that cannot resolve those packages, such as a plain `@module-federation/enhanced` host using the SDK's reference Node loader, need a self-contained entry: opt the packages back in with Vite's `ssr.noExternal` (for example `ssr: { noExternal: true }`) on the remote.
282
+
266
283
  ## Runtime capability optimization
267
284
 
268
285
  Runtime features that a build never uses can be removed at build time:
@@ -389,7 +406,9 @@ The `externalRuntime` rewrite applies to the browser remote graph; SSR remote en
389
406
 
390
407
  Do not set `build.rollupOptions.output.codeSplitting` or
391
408
  `build.rolldownOptions.output.codeSplitting` to `false` — it will be **ignored** (with a warning).
392
- Module Federation requires chunk splitting so `runtimeInitStatus` and deferred `loadShare` wrappers stay isolated for correct bootstrap order. Eager `loadShare` wrappers are coalesced into one `loadShare-eager` chunk on Vite 8+ to reduce startup requests.
409
+ Module Federation requires chunk splitting so `runtimeInitStatus` and deferred `loadShare` wrappers stay isolated for correct bootstrap order. Eager `loadShare` wrappers are coalesced into one `loadShare-eager` chunk to reduce startup requests, on both Rolldown (Vite 8+) and Rollup (Vite 5–7).
410
+
411
+ Non-eager `loadShare` wrappers that reach their local `__prebuild__` fallback only through `import()` are merged into one `__loadShare__shared` chunk per `federation()` instance, with the shared-cache helpers included once. A wrapper that statically imports its local payload keeps its own chunk, since merging it could close a top-level-await cycle. `__prebuild__` fallbacks stay in separate lazily imported chunks, so a consumer never downloads its local copy when a peer already provides the share. The plugin warns at build time if a fallback ends up inside the merged chunk.
393
412
 
394
413
  ### `codeSplitting.groups` (Vite 8+ / Rolldown)
395
414
 
@@ -1,4 +1,5 @@
1
- import { existsSync, readFileSync, readdirSync } from "fs";
1
+ import { d as getNodeModulesSuffix, r as getSharedRuntimeKey } from "./sharedKeyMatcher-Od_nYxJA.js";
2
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "fs";
2
3
  import { createRequire } from "module";
3
4
  import * as path$1 from "node:path";
4
5
  import { fileURLToPath, pathToFileURL } from "url";
@@ -34,6 +35,29 @@ const moduleFederationConsole = {
34
35
  const mfWarn = moduleFederationConsole.warn;
35
36
  const mfError = moduleFederationConsole.error;
36
37
  //#endregion
38
+ //#region src/utils/reactShares.ts
39
+ /** Property under which React 19 exposes its internals object. */
40
+ const REACT_CLIENT_INTERNALS_KEY = "__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE";
41
+ /**
42
+ * Every property React has used for its internals object, newest first. Two
43
+ * module objects that expose the same internals are the same React instance.
44
+ */
45
+ const REACT_INTERNALS_KEYS = [
46
+ REACT_CLIENT_INTERNALS_KEY,
47
+ "__TEST_INTERNALS",
48
+ "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED"
49
+ ];
50
+ /**
51
+ * Importing this share's local fallback registers a DOM renderer as a side
52
+ * effect. An entry-injected leaf must not evaluate its own copy while the host
53
+ * renderer already owns the root, so its wrapper waits for the cache or init.
54
+ */
55
+ const REACT_DOM_CLIENT_SHARE = "react-dom/client";
56
+ /** React and its subpath exports must bind synchronously for CJS consumers. */
57
+ function isReactCoreShare(pkg) {
58
+ return pkg === "react" || pkg.startsWith("react/");
59
+ }
60
+ //#endregion
37
61
  //#region src/utils/packageUtils.ts
38
62
  const dependencyPresenceCache = /* @__PURE__ */ new Map();
39
63
  let packageDetectionCwd;
@@ -41,6 +65,7 @@ function getDependencyCacheKey(cwd, dependencyName) {
41
65
  return `${cwd}:${dependencyName}`;
42
66
  }
43
67
  const installedPackageJsonCache = /* @__PURE__ */ new Map();
68
+ const dependentsLookupCache = /* @__PURE__ */ new Map();
44
69
  function setPackageDetectionCwd(cwd) {
45
70
  packageDetectionCwd = cwd;
46
71
  }
@@ -129,6 +154,20 @@ function getPackageExportsTarget(pkg, packageName, exportsField) {
129
154
  if (subpath !== ".") return matchExportsSubpath(record, subpath);
130
155
  return record["."] ?? (!Object.keys(record).some((key) => key.startsWith(".")) ? record : void 0);
131
156
  }
157
+ /** Whether `pkg` can be located from `opts.cwd` (or the detection cwd). Aliased or non-hoisted packages may still resolve through Vite. */
158
+ function isPackageInstalled(pkg, opts = {}) {
159
+ return getInstalledPackageJson(getPackageName(pkg), opts) !== void 0;
160
+ }
161
+ /**
162
+ * Whether the installed package's `exports` field permits `pkg`. Lenient when the package cannot
163
+ * be found or has no `exports`: callers that need a hard "is installed" gate use `isPackageInstalled`.
164
+ */
165
+ function isPackageExportAvailable(pkg, opts = {}) {
166
+ const packageName = getPackageName(pkg);
167
+ const installed = getInstalledPackageJson(packageName, opts);
168
+ if (installed?.packageJson.exports == null) return true;
169
+ return resolveExportsEntry(getPackageExportsTarget(pkg, packageName, installed.packageJson.exports), opts.conditions) !== void 0;
170
+ }
132
171
  /**
133
172
  * Escaping rules:
134
173
  * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names.
@@ -180,10 +219,9 @@ function getPackageName(packageString) {
180
219
  return match ? match[0] : packageString;
181
220
  }
182
221
  function getPackageNameFromNodeModulePath(source) {
183
- const normalized = source.replace(/\\/g, "/");
184
- const nodeModulesIndex = normalized.lastIndexOf("/node_modules/");
185
- if (nodeModulesIndex < 0) return;
186
- const parts = normalized.slice(nodeModulesIndex + 14).split("/");
222
+ const suffix = getNodeModulesSuffix(source);
223
+ if (!suffix) return;
224
+ const parts = suffix.split("/");
187
225
  if (!parts[0]) return;
188
226
  if (parts[0].startsWith("@")) return parts[1] ? `${parts[0]}/${parts[1]}` : void 0;
189
227
  return parts[0];
@@ -199,7 +237,7 @@ function getSharedCacheKeyParts(input) {
199
237
  }
200
238
  function getSharedCacheDescriptor(pkg, shareItem) {
201
239
  const parts = getSharedCacheKeyParts({
202
- pkg,
240
+ pkg: shareItem.shareConfig.request !== void 0 || shareItem.shareConfig.shareKey !== void 0 ? getSharedRuntimeKey(pkg, shareItem) : pkg,
203
241
  singleton: shareItem.shareConfig.singleton,
204
242
  version: shareItem.version,
205
243
  scope: shareItem.scope
@@ -265,7 +303,47 @@ const sharedCacheHelperCode = `const __mfGetSharedCacheDescriptor = (pkg, single
265
303
  };
266
304
  const __mfReadSharedCacheOwner = (cache, descriptor) =>
267
305
  cache[__mfSharedCacheOwnersKey]?.[descriptor.canonical];
306
+ const __mfGetSharedModuleIdentity = (value) => {
307
+ const candidates = [value, value?.default];
308
+ for (const candidate of candidates) {
309
+ if (!candidate || typeof candidate !== "object") continue;
310
+ for (const key of ${JSON.stringify(REACT_INTERNALS_KEYS)}) {
311
+ const internals = candidate[key];
312
+ if (internals && typeof internals === "object") {
313
+ return { internals };
314
+ }
315
+ }
316
+ if (typeof candidate.useState === "function") return { hook: candidate.useState };
317
+ }
318
+ return undefined;
319
+ };
320
+ const __mfIsSameSharedModule = (current, next) => {
321
+ if (current === next) return true;
322
+ const currentIdentity = __mfGetSharedModuleIdentity(current);
323
+ const nextIdentity = __mfGetSharedModuleIdentity(next);
324
+ if (!currentIdentity || !nextIdentity) return false;
325
+ const currentInternals = currentIdentity.internals;
326
+ const nextInternals = nextIdentity.internals;
327
+ // Internals identity is authoritative when either side exposes it.
328
+ // Never treat absent fields as equal (undefined === undefined).
329
+ if (currentInternals !== undefined || nextInternals !== undefined) {
330
+ return (
331
+ currentInternals !== undefined &&
332
+ currentInternals === nextInternals
333
+ );
334
+ }
335
+ // Hook identity is only a fallback when neither side exposes internals.
336
+ return (
337
+ typeof currentIdentity.hook === "function" &&
338
+ currentIdentity.hook === nextIdentity.hook
339
+ );
340
+ };
268
341
  const __mfWriteSharedCache = (cache, descriptor, value, owner) => {
342
+ // Rewriting the same instance is a no-op on purpose: the first
343
+ // provider keeps ownership and subscribers are not re-notified, so
344
+ // an already-bound renderer never sees its React rebound.
345
+ const current = __mfReadSharedCache(cache, descriptor);
346
+ if (current !== undefined && __mfIsSameSharedModule(current, value)) return value;
269
347
  cache[descriptor.canonical] = value;
270
348
  const aliases = descriptor.aliases || [];
271
349
  for (const alias of aliases) {
@@ -364,85 +442,181 @@ function getInstalledPackageJson(pkg, opts) {
364
442
  installedPackageJsonCache.set(cacheKey, result);
365
443
  return result;
366
444
  }
445
+ function tryReadPackageJson(packageJsonPath, expectedName) {
446
+ if (!existsSync(packageJsonPath)) return void 0;
447
+ try {
448
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
449
+ if (expectedName !== void 0 && packageJson.name !== expectedName) return void 0;
450
+ return {
451
+ path: packageJsonPath,
452
+ dir: path$1.dirname(packageJsonPath),
453
+ packageJson
454
+ };
455
+ } catch {
456
+ return;
457
+ }
458
+ }
367
459
  function resolveInstalledPackageJson(pkg, cwd, packageName, opts) {
368
- const tryReadPackageJson = (packageJsonPath) => {
369
- if (!existsSync(packageJsonPath)) return void 0;
370
- try {
371
- return {
372
- path: packageJsonPath,
373
- dir: path$1.dirname(packageJsonPath),
374
- packageJson: JSON.parse(readFileSync(packageJsonPath, "utf-8"))
375
- };
376
- } catch {
377
- return;
378
- }
379
- };
380
460
  const findPackageInPnpmStore = (startDir) => {
381
- let currentDir = startDir;
382
- while (true) {
383
- const pnpmStoreDir = path$1.join(currentDir, "node_modules", ".pnpm");
384
- if (existsSync(pnpmStoreDir)) try {
461
+ for (const dir of ancestorDirs(startDir)) {
462
+ const pnpmStoreDir = path$1.join(dir, "node_modules", ".pnpm");
463
+ if (!existsSync(pnpmStoreDir)) continue;
464
+ try {
385
465
  for (const entry of readdirSync(pnpmStoreDir, { withFileTypes: true })) {
386
466
  if (!entry.isDirectory()) continue;
387
467
  const candidate = tryReadPackageJson(path$1.join(pnpmStoreDir, entry.name, "node_modules", packageName, "package.json"));
388
468
  if (candidate?.packageJson.name === packageName) return candidate;
389
469
  }
390
470
  } catch {}
391
- const parentDir = path$1.dirname(currentDir);
392
- if (parentDir === currentDir) break;
393
- currentDir = parentDir;
394
471
  }
395
472
  };
396
473
  let resolvedPath;
397
474
  try {
398
- const projectRequire = createRequire(pathToFileURL(path$1.join(cwd, "package.json")));
475
+ const rootPackageJson = path$1.join(cwd, "package.json");
399
476
  if (opts?.fromResolvedEntry) resolvedPath = opts.fromResolvedEntry;
400
477
  else try {
401
- resolvedPath = projectRequire.resolve(pkg);
478
+ resolvedPath = resolveModulePath(pkg, rootPackageJson);
402
479
  } catch {
403
- resolvedPath = projectRequire.resolve(packageName);
480
+ resolvedPath = resolveModulePath(packageName, rootPackageJson);
404
481
  }
405
482
  } catch {
406
483
  resolvedPath = void 0;
407
484
  }
408
485
  if (resolvedPath !== void 0 && !path$1.isAbsolute(resolvedPath)) resolvedPath = void 0;
409
- if (resolvedPath !== void 0) try {
410
- let currentDir = path$1.dirname(resolvedPath);
411
- let matchingPackage;
412
- while (true) {
413
- const packageJsonPath = path$1.join(currentDir, "package.json");
414
- if (existsSync(packageJsonPath)) {
415
- const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
416
- try {
417
- const packageJson = JSON.parse(packageJsonContent);
418
- if (packageJson.name === packageName) {
419
- const packageInfo = {
420
- path: packageJsonPath,
421
- dir: currentDir,
422
- packageJson
423
- };
424
- if (currentDir.endsWith(path$1.join("node_modules", packageName))) return packageInfo;
425
- matchingPackage ??= packageInfo;
426
- }
427
- } catch (error) {
428
- if (!(error instanceof SyntaxError)) throw error;
486
+ if (resolvedPath !== void 0) {
487
+ const owner = findOwningPackageJson(path$1.dirname(resolvedPath), packageName);
488
+ if (owner) return owner;
489
+ }
490
+ for (const dir of ancestorDirs(cwd)) {
491
+ const directCandidate = tryReadPackageJson(path$1.join(dir, "node_modules", packageName, "package.json"), packageName);
492
+ if (directCandidate) return directCandidate;
493
+ }
494
+ return findPackageInDependents(packageName, cwd) ?? findPackageInPnpmStore(cwd);
495
+ }
496
+ /**
497
+ * A package installed only under a dependent is invisible to the walks above.
498
+ * Resolving it from each dependent in turn follows the same links that dependent
499
+ * does, so the copy found is the version it is installed against rather than
500
+ * whichever one the pnpm store happens to list first.
501
+ */
502
+ function findPackageInDependents(packageName, cwd) {
503
+ const cacheKey = `${cwd}\0${packageName}`;
504
+ if (!dependentsLookupCache.has(cacheKey)) dependentsLookupCache.set(cacheKey, walkDependents(packageName, cwd));
505
+ return dependentsLookupCache.get(cacheKey);
506
+ }
507
+ function walkDependents(packageName, cwd) {
508
+ const root = tryReadPackageJson(path$1.join(cwd, "package.json"));
509
+ if (!root) return void 0;
510
+ const visited = /* @__PURE__ */ new Set();
511
+ const rootDependencyGroups = [getDependencyNames(root.packageJson), getDependencyNames(root.packageJson, ["devDependencies"])];
512
+ for (const rootDependencies of rootDependencyGroups) {
513
+ let level = rootDependencies.map((name) => ({
514
+ name,
515
+ fromDir: cwd
516
+ }));
517
+ while (level.length) {
518
+ const candidates = [];
519
+ const nextLevel = [];
520
+ for (const { name, fromDir } of level) {
521
+ if (name === packageName) continue;
522
+ const dependent = findPackageFrom(name, fromDir);
523
+ if (!dependent || visited.has(dependent.dir)) continue;
524
+ visited.add(dependent.dir);
525
+ const dependencies = getDependencyNames(dependent.packageJson);
526
+ if (dependencies.includes(packageName)) {
527
+ const copy = findPackageFrom(packageName, dependent.dir);
528
+ if (copy) candidates.push({
529
+ copy,
530
+ via: name
531
+ });
429
532
  }
533
+ for (const dependency of dependencies) nextLevel.push({
534
+ name: dependency,
535
+ fromDir: dependent.dir
536
+ });
430
537
  }
431
- const parentDir = path$1.dirname(currentDir);
432
- if (parentDir === currentDir) break;
433
- currentDir = parentDir;
538
+ if (candidates.length) {
539
+ warnOnAmbiguousDependents(packageName, candidates);
540
+ return candidates[0].copy;
541
+ }
542
+ level = nextLevel;
434
543
  }
435
- return matchingPackage;
436
- } catch {}
437
- let currentDir = cwd;
544
+ }
545
+ }
546
+ function warnOnAmbiguousDependents(packageName, candidates) {
547
+ const viaByVersion = /* @__PURE__ */ new Map();
548
+ for (const { copy, via } of candidates) if (!viaByVersion.has(copy.packageJson.version)) viaByVersion.set(copy.packageJson.version, via);
549
+ if (viaByVersion.size < 2) return;
550
+ const versions = [...viaByVersion].map(([version, via]) => `${version} (via ${via})`);
551
+ mfWarn(`Shared package "${packageName}" is not a dependency of the project, and its closest dependents install different versions: ${versions.join(", ")}. The fallback uses ${versions[0]}. Add "${packageName}" to the project's dependencies or set its shared "import" to choose the version.`);
552
+ }
553
+ /** The package Node's `node_modules` lookup finds from `fromDir`, read at its real path. */
554
+ function findPackageFrom(packageName, fromDir) {
555
+ const entry = findNodeModulesEntry(packageName, fromDir);
556
+ if (!entry) return void 0;
557
+ try {
558
+ return tryReadPackageJson(path$1.join(realpathSync(entry), "package.json"), packageName);
559
+ } catch {
560
+ return;
561
+ }
562
+ }
563
+ /**
564
+ * Walks up from `startDir` to the package.json named `packageName`, preferring the
565
+ * one at `node_modules/<packageName>` so a nested `package.json` (for example a
566
+ * subpath's `type: module` marker) does not shadow the package root.
567
+ */
568
+ function findOwningPackageJson(startDir, packageName) {
569
+ let matchingPackage;
570
+ for (const dir of ancestorDirs(startDir)) {
571
+ const candidate = tryReadPackageJson(path$1.join(dir, "package.json"), packageName);
572
+ if (!candidate) continue;
573
+ if (dir.endsWith(path$1.join("node_modules", packageName))) return candidate;
574
+ matchingPackage ??= candidate;
575
+ }
576
+ return matchingPackage;
577
+ }
578
+ function* ancestorDirs(startDir) {
579
+ let dir = startDir;
438
580
  while (true) {
439
- const directCandidate = tryReadPackageJson(path$1.join(currentDir, "node_modules", packageName, "package.json"));
440
- if (directCandidate?.packageJson.name === packageName) return directCandidate;
441
- const parentDir = path$1.dirname(currentDir);
442
- if (parentDir === currentDir) break;
443
- currentDir = parentDir;
581
+ yield dir;
582
+ const parentDir = path$1.dirname(dir);
583
+ if (parentDir === dir) return;
584
+ dir = parentDir;
585
+ }
586
+ }
587
+ /** The first `node_modules/<packageName>` that Node's lookup reaches from `fromDir`. */
588
+ function findNodeModulesEntry(packageName, fromDir) {
589
+ for (const dir of ancestorDirs(fromDir)) {
590
+ const entry = path$1.join(dir, "node_modules", packageName);
591
+ if (existsSync(entry)) return entry;
592
+ }
593
+ }
594
+ /**
595
+ * `require.resolve(specifier)` from the file `from`, limited to the `node_modules` lookup
596
+ * bundlers perform. Node also searches NODE_PATH, which pnpm's bin shims point at the
597
+ * store's hoisted `node_modules`, so it can find packages the bundler cannot.
598
+ */
599
+ function resolveModulePath(specifier, from) {
600
+ const resolved = createRequire(pathToFileURL(from)).resolve(specifier);
601
+ if (!path$1.isAbsolute(resolved) || specifier.startsWith(".") || path$1.isAbsolute(specifier) || process.versions.pnp) return resolved;
602
+ if (findNodeModulesEntry(getPackageName(specifier), path$1.dirname(from))) return resolved;
603
+ const error = /* @__PURE__ */ new Error(`Cannot find module '${specifier}' from '${from}'`);
604
+ error.code = "MODULE_NOT_FOUND";
605
+ throw error;
606
+ }
607
+ function getDependencyNames(packageJson, fields = [
608
+ "dependencies",
609
+ "peerDependencies",
610
+ "optionalDependencies"
611
+ ]) {
612
+ if (!packageJson) return [];
613
+ const names = /* @__PURE__ */ new Set();
614
+ for (const field of fields) {
615
+ const deps = packageJson[field];
616
+ if (!deps || typeof deps !== "object") continue;
617
+ for (const dep of Object.keys(deps)) names.add(dep);
444
618
  }
445
- return findPackageInPnpmStore(cwd);
619
+ return [...names];
446
620
  }
447
621
  function getInstalledPackageEntry(pkg, opts) {
448
622
  const installed = getInstalledPackageJson(pkg, opts);
@@ -451,7 +625,7 @@ function getInstalledPackageEntry(pkg, opts) {
451
625
  const packageName = opts?.packageName || getPackageName(pkg);
452
626
  const packageJson = installed.packageJson;
453
627
  if (pkg !== packageName && (opts?.resolveSubpathWithRequire !== false || packageJson.exports === void 0)) try {
454
- return createRequire(pathToFileURL(path$1.join(cwd, "package.json"))).resolve(pkg);
628
+ return resolveModulePath(pkg, path$1.join(cwd, "package.json"));
455
629
  } catch {}
456
630
  const explicitEntry = resolveExportsEntry(getPackageExportsTarget(pkg, packageName, packageJson.exports), opts?.conditions) || (typeof packageJson.module === "string" ? packageJson.module : void 0) || (typeof packageJson.main === "string" ? packageJson.main : void 0) || "index.js";
457
631
  return path$1.join(installed.dir, explicitEntry);
@@ -465,6 +639,18 @@ function getIsRolldown(ctx) {
465
639
  const viteMajor = Number(String(viteVersion ?? "").split(".")[0]);
466
640
  return Number.isFinite(viteMajor) && viteMajor >= 8 || !!ctx?.meta?.rolldownVersion;
467
641
  }
642
+ /**
643
+ * Rolldown 1.2.10 added `codeSplitting.groups[].debugName` and warns when a group's
644
+ * `name` is a function without one. Older versions reject it as an unknown option.
645
+ */
646
+ function supportsCodeSplittingGroupDebugName(ctx) {
647
+ const rolldownVersion = ctx?.meta?.rolldownVersion;
648
+ if (typeof rolldownVersion !== "string") return false;
649
+ const [major = 0, minor = 0, patch = 0] = rolldownVersion.split(/[.+-]/, 3).map((part) => parseInt(part, 10) || 0);
650
+ if (major !== 1) return major > 1;
651
+ if (minor !== 2) return minor > 2;
652
+ return patch >= 10;
653
+ }
468
654
  /** Walk up from Vite `config.root` (Nuxt may point at `.nuxt` cache dirs). */
469
655
  function isNuxtProjectRoot(root) {
470
656
  let dir = root;
@@ -499,4 +685,4 @@ function hasPackageDependency(dependencyName, cwd = packageDetectionCwd || proce
499
685
  //#region src/utils/dtsConstants.ts
500
686
  const DEFAULT_PUBLIC_TYPES_FOLDER = "@mf-types";
501
687
  //#endregion
502
- export { mfError as _, getPackageDetectionCwd as a, getSharedCacheDescriptor as c, packageNameDecode as d, packageNameEncode as f, createModuleFederationError as g, sharedCacheHelperCode as h, getIsRolldown as i, hasPackageDependency as l, setPackageDetectionCwd as m, getInstalledPackageEntry as n, getPackageName as o, resolveImportPath as p, getInstalledPackageJson as r, getPackageNameFromNodeModulePath as s, DEFAULT_PUBLIC_TYPES_FOLDER as t, isNuxtProjectRoot as u, mfWarn as v };
688
+ export { isReactCoreShare as C, mfWarn as E, REACT_DOM_CLIENT_SHARE as S, mfError as T, resolveModulePath as _, getIsRolldown as a, supportsCodeSplittingGroupDebugName as b, getPackageNameFromNodeModulePath as c, isNuxtProjectRoot as d, isPackageExportAvailable as f, resolveImportPath as g, packageNameEncode as h, getInstalledPackageJson as i, getSharedCacheDescriptor as l, packageNameDecode as m, getDependencyNames as n, getPackageDetectionCwd as o, isPackageInstalled as p, getInstalledPackageEntry as r, getPackageName as s, DEFAULT_PUBLIC_TYPES_FOLDER as t, hasPackageDependency as u, setPackageDetectionCwd as v, createModuleFederationError as w, REACT_CLIENT_INTERNALS_KEY as x, sharedCacheHelperCode as y };
package/lib/index.d.ts CHANGED
@@ -31,6 +31,12 @@ type ModuleFederationOptions = {
31
31
  import: string;
32
32
  }> | undefined;
33
33
  filename?: string;
34
+ /**
35
+ * Exact file name of the SSR remote entry emitted next to `filename`.
36
+ * Defaults to `<filename base>.ssr<ext>` so it never clobbers the browser
37
+ * entry when both builds share an output directory. `[hash]` is not supported.
38
+ */
39
+ ssrFilename?: string;
34
40
  library?: any;
35
41
  name: string;
36
42
  remotes?: Record<string, string | RemoteObjectConfig> | undefined;
@@ -57,10 +63,13 @@ type ModuleFederationOptions = {
57
63
  name?: string;
58
64
  version?: string;
59
65
  shareScope?: string;
66
+ request?: moduleFederationPlugin.SharedConfig['request'];
67
+ shareKey?: moduleFederationPlugin.SharedConfig['shareKey'];
60
68
  singleton?: boolean;
61
69
  eager?: boolean;
62
70
  requiredVersion?: moduleFederationPlugin.SharedConfig['requiredVersion'];
63
71
  strictVersion?: boolean;
72
+ allowNodeModulesSuffixMatch?: boolean;
64
73
  /** Suppress the missing local dependency warning for `import: false` shares. */
65
74
  suppressMissingImportWarning?: boolean;
66
75
  treeShaking?: TreeShakingConfig;
@@ -122,8 +131,11 @@ type ModuleFederationOptions = {
122
131
  disableSnapshot?: boolean;
123
132
  /**
124
133
  * Additional packages to mark as external in the SSR remote entry build.
125
- * Shared packages and MF runtime packages are always external. Use this to
134
+ * Shared packages and MF runtime packages are external by default. Use this to
126
135
  * add any other Node-only packages that should not be bundled into the SSR entry.
136
+ * To emit a self-contained SSR entry (loadable by hosts that cannot resolve the
137
+ * MF runtime from Node, such as the `@module-federation/sdk` reference loader),
138
+ * opt the MF runtime packages back in through Vite's `ssr.noExternal`.
127
139
  */
128
140
  ssrExternals?: string[];
129
141
  /**
@@ -230,6 +242,7 @@ interface DtsRemoteOptions {
230
242
  extractRemoteTypes?: boolean;
231
243
  abortOnError?: boolean;
232
244
  deleteTsConfig?: boolean;
245
+ afterGenerate?: (options: moduleFederationPlugin.DtsGenerateTypesHookOptions) => Promise<void> | void;
233
246
  }
234
247
  interface DtsHostOptions {
235
248
  typesFolder?: string;