# vite-plugin-federation Full Documentation Snapshot
Generated from checked-in repository docs. Prefer source files for edits.
# Root README
Source: README.md
# vite-plugin-federation
[](https://github.com/jskits/vite-plugin-federation/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/vite-plugin-federation)

vite-plugin-federation - A Vite/Rollup plugin for Module Federation.
Documentation site:
---
## Table of Contents
- [What is Module Federation?](#what-is-module-federation)
- [Why this plugin](#why-this-plugin)
- [Compared to Other Vite Federation Plugins](#compared-to-other-vite-federation-plugins)
- [Install](#install)
- [Public API Contract](#public-api-contract)
- [Quick Start](#quick-start)
- [Remote](#a-remote)
- [Host](#a-host)
- [SSR Host](#an-ssr-host)
- [Plugin Options](#plugin-options)
- [Runtime API](#runtime-api)
- [Manifest Protocol](#manifest-protocol)
- [Dev Experience](#dev-experience)
- [SSR](#ssr)
- [TypeScript / DTS](#typescript--dts)
- [Production Runtime](#production-runtime)
- [Multi-Tenant Deployments](#multi-tenant-deployments)
- [Preload & Performance](#preload--performance)
- [Security](#security)
- [OriginJS Compatibility & Migration](#originjs-compatibility--migration)
- [Examples](#examples)
- [Troubleshooting](#troubleshooting)
- [Repository Layout](#repository-layout)
- [Development](#development)
- [Release Flow](#release-flow)
- [CI / CD](#ci--cd)
- [License](#license)
---
## What is Module Federation?
[Module Federation](https://module-federation.io/) lets one frontend application load code from another frontend at runtime.
Each frontend can be built and deployed independently.
A typical setup has:
- a **host** or shell app, which owns the page and routing
- one or more **remotes**, which expose components, routes, or modules
- shared dependencies such as React, Vue, or design-system packages, negotiated at runtime
This is useful when multiple teams need to ship parts of the same product independently.
Each team can update its own remote without rebuilding and redeploying one large frontend every time.
Use this plugin when you want Vite apps to participate in that architecture.
It lets you build remotes, consume remotes from a host, and share dependencies safely.
It also loads remote manifests in production.
You probably do not need Module Federation if all code is released together in one app.
A normal npm package or Vite's built-in code splitting may be enough for simpler cases.
---
## Why this plugin
| Need | What this plugin gives you |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Vite 5 / 6 / 7 / 8 + Rolldown** in one plugin | First-class compiler adapter, including the Vite 8 module-preload helper rewrite and `rolldownOptions` handling. |
| **Manifest-first remote loading** | Builds emit `mf-manifest.json` + `mf-stats.json` + `mf-debug.json`. Hosts consume via curated `vite-plugin-federation/runtime` helpers. |
| **Real dev HMR for remotes** | Sidecar dev runtime with classified updates when `dev.remoteHmr: true`: `partial`, `style`, `types`, `full`. |
| **SSR (Node)** | Dedicated `ssrRemoteEntry`, `createServerFederationInstance`, asset-preload collection for streaming HTML. |
| **Production-grade host loader** | Cache TTL, `staleWhileRevalidate`, retries with jitter, timeouts, fallback URLs, circuit breaker, request collapsing. |
| **Multi-tenant** | `createFederationRuntimeScope(runtimeKey)` for isolated manifest cache, breaker, and debug records per tenant on a single page. |
| **Security** | SRI / SHA-256 integrity verification (multi-mode), private/authenticated manifests, CSP & Trusted Types guidance, and a signed-manifest recipe. |
| **Observability** | `getFederationDebugInfo()`, telemetry hooks, dev devtools panel, stable error codes (`MFV-001` … `MFV-007`). |
| **OriginJS migration shim** | `virtual:__federation__` and `__federation_method_*` shim enabled by default for common OriginJS migration paths. |
A side-by-side feature table against `@module-federation/vite` and `@originjs/vite-plugin-federation`
lives in [`COMPARISON.md`](COMPARISON.md).
### GA Support Scope
`vite-plugin-federation` 1.0 is generally available for manifest-first Vite remotes, browser hosts,
Node SSR hosts, DTS generation and consumption, dev remote HMR, and the curated runtime APIs.
Webpack/SystemJS/`var` remotes remain compatibility paths covered by e2e and should still be
validated in each migration.
Signed manifests are a documented supply-chain pattern; signature verification is intentionally
provided through a custom fetch wrapper rather than built into the default runtime.
---
## Compared to Other Vite Federation Plugins
See [**COMPARISON.md**](COMPARISON.md) for the full feature matrix, decision guide, and citations
against:
- [`@module-federation/vite`](https://www.npmjs.com/package/@module-federation/vite)
- [`@originjs/vite-plugin-federation`](https://www.npmjs.com/package/@originjs/vite-plugin-federation)
Migration cheatsheets:
- [docs/originjs-migration.md](docs/originjs-migration.md) — from `@originjs/vite-plugin-federation`
- [docs/migrate-from-module-federation-vite.md](docs/migrate-from-module-federation-vite.md) — from `@module-federation/vite`
---
## Install
```bash
pnpm add -D vite-plugin-federation
# or
npm install -D vite-plugin-federation
# or
yarn add -D vite-plugin-federation
```
**Peer dependency:** `vite@^5 || ^6 || ^7 || ^8` (Rolldown supported).
**Node:** `>=20.19.0`.
The plugin exact-pins `@module-federation/runtime@2.3.3`,
`@module-federation/sdk@2.3.3`, and `@module-federation/dts-plugin@2.3.3`.
It also aliases all `@module-federation/runtime` imports to a single bridge so federation state
stays shared across consumers in your app.
---
## Public API Contract
The v1.x compatibility contract covers the plugin option surface, `vite-plugin-federation/runtime`
exports, manifest schema `1.0.0`, devtools contract `1.0.0`, `MFV-*` error-code meanings, Node
`>=20.19.0`, and Vite `^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`.
Full policy: [`docs/public-api-contract.md`](docs/public-api-contract.md).
---
## Quick Start
### A remote
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'catalog',
filename: 'remoteEntry.js', // stable name for examples; default is remoteEntry-[hash]
exposes: {
'./Button': './src/Button.tsx',
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react/': { singleton: true, requiredVersion: '^19.2.4' },
},
// manifest defaults to true → emits mf-manifest.json + mf-stats.json + mf-debug.json
}),
],
build: {
target: 'esnext',
cssCodeSplit: true,
},
});
```
### A host
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'shell',
remotes: {
// String value → manifest-first remote (recommended)
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
},
}),
],
build: { target: 'esnext' },
});
```
Load a remote at runtime:
```tsx
// app.tsx
import { lazy, Suspense } from 'react';
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
const CatalogButton = lazy(async () => {
const mod = await loadRemoteFromManifest(
'catalog/Button',
'https://cdn.example.com/catalog/mf-manifest.json',
{ cacheTtl: 30_000, retries: 2, timeout: 4_000 },
);
return { default: mod.default ?? mod };
});
export function App() {
return (
);
}
```
If you prefer the classic `loadRemote('catalog/Button')` style, that works too:
```ts
import { loadRemote } from 'vite-plugin-federation/runtime';
const mod = await loadRemote('catalog/Button');
```
### An SSR host
```ts
// vite.config.ts
import federation from 'vite-plugin-federation';
export default {
plugins: [
federation({
name: 'ssr-shell',
remotes: { catalog: 'https://cdn.example.com/catalog/mf-manifest.json' },
target: 'node', // optional; auto-detected from `build.ssr`
}),
],
};
```
```ts
// server.ts
import {
createServerFederationInstance,
fetchFederationManifest,
loadRemoteFromManifest,
collectFederationManifestPreloadLinks,
} from 'vite-plugin-federation/runtime';
createServerFederationInstance({ name: 'ssr-shell', remotes: [], shared: {} });
const manifestUrl = process.env.CATALOG_MANIFEST_URL!;
const manifest = await fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
const mod = await loadRemoteFromManifest('catalog/Button', manifestUrl, {
target: 'node',
});
// Stream / hints
const links = collectFederationManifestPreloadLinks(manifestUrl, manifest, './Button');
```
> Node must currently run with `--experimental-vm-modules` because the underlying MF runtime
> uses the VM module loader for ESM remote evaluation. See [docs/production-runtime.md](docs/production-runtime.md).
---
## Plugin Options
The full reference lives in [`docs/plugin-api.md`](docs/plugin-api.md). The most-used
options:
| Option | Type | Default | Notes |
| ------------------------ | ---------------------------------------------- | --------------------- | ------------------------------------------------------------- |
| `name` | `string` | **required** | Public application/container name. |
| `filename` | `string` | `remoteEntry-[hash]` | Browser remote entry filename. |
| `varFilename` | `string` | — | Emits an additional var-style entry for legacy hosts. |
| `exposes` | `Record` | `{}` | Remote expose map. |
| `remotes` | `Record` | `{}` | Manifest URLs or object configs. |
| `shared` | `string[] \| Record` | `{}` | Shared providers/consumers. |
| `manifest` | `boolean \| PluginManifestOptions` | `true` | Emits `mf-manifest.json` + `mf-stats.json` + `mf-debug.json`. |
| `dts` | `boolean \| PluginDtsOptions` | auto for TS projects | Type generation / consumption; set `false` to disable. |
| `dev` | `boolean \| PluginDevOptions` | enabled | Devtools and type hints default on; remote HMR is opt-in. |
| `compat` | `boolean \| CompatibilityOptions` | `true` | OriginJS `virtual:__federation__` shim. |
| `shareStrategy` | `'loaded-first' \| 'version-first'` | `'version-first'` | Shared provider selection. |
| `shareScope` | `string` | `'default'` | Default share scope. |
| `publicPath` | `string` | Vite `base` or `auto` | Public path for manifest asset URLs. |
| `bundleAllCSS` | `boolean` | `false` | Add all CSS to every expose entry. |
| `runtimePlugins` | `Array` | `[]` | Runtime plugin imports. |
| `target` | `'web' \| 'node'` | from build | Forces browser/server output. |
| `hostInitInjectLocation` | `'entry' \| 'html'` | `'html'` | Where host runtime init is injected. |
| `moduleParseTimeout` | `number` (s) | `10` | Total module parse budget. |
| `moduleParseIdleTimeout` | `number` (s) | — | Idle parse timeout, reset per module. |
### Exposes
```ts
exposes: {
'./Button': './src/Button.tsx',
'./ManualCssButton': {
import: './src/ManualCssButton.tsx',
css: { inject: 'manual' }, // 'head' | 'manual' | 'none' | true | false
},
}
```
`'manual'` writes CSS hrefs to the global CSS bucket instead of appending styles to `document.head`.
This is useful for Shadow DOM or staged migration.
`dontAppendStylesToHead: true` is accepted as the OriginJS-compatible alias.
Do not expose the same file that `index.html` loads as a Vite module entry, such as
`src/main.ts`, `src/main.tsx`, or `src/main.js`. That file should only bootstrap and mount the
remote app. Expose components, routes, or loader modules instead. In dev, the plugin warns when an
`exposes` entry points at the HTML module entry.
### Remotes
```ts
remotes: {
// Recommended: manifest-first
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
// Object form for legacy / mixed setups
legacy: {
name: 'legacyRemote',
entry: 'https://cdn.example.com/legacy/remoteEntry.js',
type: 'module',
entryGlobalName: 'legacyRemote',
from: 'vite', // 'vite' | 'webpack'
format: 'esm', // 'esm' | 'systemjs' | 'var'
shareScope: 'default',
},
}
```
### Shared
```ts
shared: {
react: {
singleton: true,
strictSingleton: true,
requiredVersion: '^19.2.4',
strictVersion: true,
allowNodeModulesSuffixMatch: true, // pnpm/symlinked layouts
},
'react/': { // trailing slash → matches package subpaths
singleton: true,
requiredVersion: '^19.2.4',
allowNodeModulesSuffixMatch: true,
},
// Host-only share — host must provide; remote must not bundle a fallback
'design-system': { import: false, requiredVersion: '^2.0.0' },
}
```
---
## Runtime API
Imported from `vite-plugin-federation/runtime`.
Full reference: [`docs/runtime-api.md`](docs/runtime-api.md).
```ts
import {
// Instances
createFederationInstance,
createServerFederationInstance,
createFederationRuntimeScope,
// Manifest
fetchFederationManifest,
registerManifestRemote,
registerManifestRemotes,
loadRemoteFromManifest,
refreshRemote,
connectRuntimeRemoteHmr,
// Loading
loadRemote,
loadShare,
loadShareSync,
// Preload & warmup
warmFederationRemotes,
preloadRemote,
createFederationManifestPreloadPlan,
collectFederationManifestPreloadLinks,
// Security
verifyFederationManifestAssets,
// Observability
getFederationDebugInfo,
clearFederationRuntimeCaches,
// Re-exports of @module-federation/runtime
registerPlugins,
registerRemotes,
registerShared,
} from 'vite-plugin-federation/runtime';
```
The most useful production helper is `loadRemoteFromManifest()`.
It fetches and validates the manifest, picks the correct entry for the target (`web` or `node`),
optionally verifies integrity, registers the remote with the MF runtime, and loads the expose.
The helper also applies cache TTL, retries, and circuit breaker support.
---
## Manifest Protocol
Every manifest-enabled build emits three artifacts under the configured `manifest.filePath`:
| Artifact | Purpose |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mf-manifest.json` | Primary contract: name, version, exposes, shared, remotes, build metadata, optional `ssrRemoteEntry`, optional integrity / content hashes, optional preload hints. |
| `mf-stats.json` | Build diagnostics, shared resolution metadata, asset analysis. |
| `mf-debug.json` | Plugin options snapshot, capability flags, diagnostics. Useful in CI. |
Schema and field definitions: [`docs/manifest-protocol.md`](docs/manifest-protocol.md).
JSON Schemas: [`docs/schemas/`](docs/schemas).
---
## Dev Experience
The dev pipeline runs the federation virtual modules through a sidecar runtime.
That lets the host see real updates from a remote without a rebuild cycle.
See [`docs/dev-hmr.md`](docs/dev-hmr.md) for the full strategy table.
```ts
federation({
// ...
dev: {
remoteHmr: true, // opt in to host <-> remote HMR wiring
devtools: true, // default; set false to disable the devtools endpoint/overlay
disableLiveReload: true, // DTS worker default: avoid page reloads for type-only changes
disableHotTypesReload: false,
disableDynamicRemoteTypeHints: false,
},
});
```
Hosts with configured `remotes` are wired automatically. Runtime-registered remotes created with
`registerRemotes()` or `loadRemoteFromManifest()` are only known in the browser; call
`connectRuntimeRemoteHmr(remoteAlias, manifestOrEntryUrl)` after registration to connect their
`/__mf_hmr` endpoint and close the returned connection on disposal.
Update classification:
| Update kind | What happens |
| ----------- | ---------------------------------------------------------------------- |
| `partial` | `server.reloadModule()` for the affected expose, falls back to `full`. |
| `style` | Refresh the affected stylesheet(s); no reload. |
| `types` | Sync `.d.ts` to the host; no reload. |
| `full` | Full host reload. |
Dev endpoints exposed by the dev server:
- `/mf-manifest.json` (or your configured `manifest.filePath` / `manifest.fileName`)
- `/mf-debug.json` (derived from the manifest file name)
- `/remoteEntry.js` or the configured `filename` in dev
- `/__mf_hmr` when `dev.remoteHmr: true`
- `/__mf_devtools` unless `dev.devtools: false`
- `dist/.dev-server.zip` and `dist/.dev-server.d.ts` when dev DTS generation is active
---
## SSR
- `target: 'node'` is auto-detected from `build.ssr`. It emits a separate `ssrRemoteEntry.js`
per remote and tree-shakes browser-only branches via the `ENV_TARGET` define.
- The runtime exposes `createServerFederationInstance()` with `inBrowser: false` and the Node
remote-entry loader. Manifest registration prefers `ssrRemoteEntry` for `node` targets. It falls
back to `remoteEntry` if no SSR entry is declared.
- `collectFederationManifestPreloadLinks()` returns deduplicated `` descriptors for streaming
HTML. It dedupes `modulepreload` and `stylesheet` links across multiple expose hits in the same
render pass.
- Vite's module-preload helper is patched to short-circuit when `document` / `window` are
absent, so an expose evaluated during SSR cannot crash the renderer.
End-to-end flow and operational tips: [`docs/production-runtime.md`](docs/production-runtime.md).
---
## TypeScript / DTS
```ts
federation({
// ...
dts: {
generateTypes: { abortOnError: true },
consumeTypes: { typesOnBuild: true, abortOnError: true },
},
});
```
- Wraps `@module-federation/dts-plugin@2.3.3`.
- Remote builds emit `@mf-types.zip` and a per-expose `@mf-types.d.ts`.
- Hosts discover type URLs from the manifest (`metaData.types.path / .api`) with optional
`remoteTypeUrls` override.
- Dev hosts receive **live type updates** via the dev WebSocket channel without a page reload.
- `typesOnBuild: true` fails the host build if remote types cannot be fetched.
Workflows: [`docs/dts-workflows.md`](docs/dts-workflows.md).
---
## Production Runtime
Manifest-first loading with operational guardrails:
```ts
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
const mod = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
target: 'web', // or 'node'
cacheTtl: 30_000, // ms; 0 = always refetch
staleWhileRevalidate: true, // serve cached, refresh in the background
force: false, // bypass cache for this call
fallbackUrls: [backupManifestUrl],
retries: 2,
retryDelay: 200, // ms, with jitter
timeout: 4_000, // ms, per attempt
circuitBreaker: { failureThreshold: 3, cooldownMs: 30_000 },
integrity: { mode: 'prefer-integrity' }, // or true / { mode: 'integrity' | 'content-hash' | 'both' }
fetch, // bring your own
fetchInit: { credentials: 'include' },
shareScope: 'default',
hooks: {
manifestFetch: (e) => log(e),
remoteRegister: (e) => log(e),
remoteLoad: (e) => log(e),
remoteRefresh: (e) => log(e),
},
});
```
Concurrent calls for the same manifest URL **collapse into a single in-flight fetch**.
Cached entries record `sourceUrl` (which fallback URL actually served), latency, integrity
mode used, and breaker state for each call.
See [`docs/production-runtime.md`](docs/production-runtime.md) and [`docs/runtime-api.md`](docs/runtime-api.md)
for the full option reference.
---
## Multi-Tenant Deployments
If you serve multiple tenants on the same page (per-customer remote sets, A/B variants, etc.),
isolate them with a runtime scope:
```ts
import { createFederationRuntimeScope } from 'vite-plugin-federation/runtime';
const tenant = createFederationRuntimeScope(tenantId);
await tenant.registerManifestRemote('shop', tenantManifestUrl, { shareScope: tenantId });
const Page = await tenant.loadRemoteFromManifest('shop/Page', tenantManifestUrl);
```
Each scope owns its own manifest cache, in-flight registrations, circuit breaker, debug
records, and load metrics — so a noisy tenant cannot affect any other tenant's failure budget
or cache state. See [`docs/multi-tenant.md`](docs/multi-tenant.md).
---
## Preload & Performance
```ts
import {
createFederationManifestPreloadPlan,
fetchFederationManifest,
warmFederationRemotes,
} from 'vite-plugin-federation/runtime';
// Warm a set of remotes at app boot. Set preload: false to register only.
await warmFederationRemotes({
catalog: {
manifestUrl: catalogManifestUrl,
preload: { resourceCategory: 'sync' },
},
checkout: {
manifestUrl: checkoutManifestUrl,
preload: false,
},
});
// Build a route-aware preload plan
const catalogManifest = await fetchFederationManifest(catalogManifestUrl, { cacheTtl: 30_000 });
const plan = createFederationManifestPreloadPlan(
catalogManifestUrl,
catalogManifest,
{ '/product/:id': './Button' },
{
asyncChunkPolicy: 'css', // 'none' | 'js' | 'css' | 'all'
},
);
// plan.links → [{ rel: 'modulepreload', href }, { rel: 'stylesheet', href }, ...]
// Per-remote runtime metrics surface through getFederationDebugInfo()
// .runtime.remoteLoadMetrics
```
Background and tuning: [`docs/preload-performance.md`](docs/preload-performance.md).
---
## Security
| Capability | Where |
| -------------------------------------------------------------------------------------------------------- | --------------------- |
| Subresource integrity in manifest (SRI + SHA-256) | `docs/security.md` |
| `verifyFederationManifestAssets()` (multi-mode: `prefer-integrity`, `integrity`, `content-hash`, `both`) | `docs/runtime-api.md` |
| Private / authenticated manifests via `fetchInit` + custom `fetch` | `docs/security.md` |
| Signed manifest workflow (detached signature verified in `fetch` wrapper) | `docs/security.md` |
| CSP / Trusted Types guidance (manifest-first ESM, no inline scripts) | `docs/security.md` |
---
## OriginJS Compatibility & Migration
The OriginJS `virtual:__federation__` API is enabled by default for migration. All five common
legacy methods work unchanged:
```ts
import {
__federation_method_setRemote,
__federation_method_getRemote,
__federation_method_ensure,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';
```
Legacy paths like `./Button` are normalized to `remote/Button` and routed through
`@module-federation/runtime`.
For CSS migration, `dontAppendStylesToHead: true` is preserved as a `css.inject: 'manual'` synonym.
The end-to-end `examples/originjs-compat-host` validates this against a real `remoteEntry.js`.
It also validates a webpack `library.type: 'system'` remote.
Compatibility is intentionally scoped: browser CommonJS remotes are not supported, `systemjs`
remotes require `globalThis.System.import`, and unsupported `from` values produce `MFV-007`
warnings.
Turn the shim off when you've fully migrated:
```ts
federation({
// ...
compat: { originjs: false, virtualFederationShim: false },
});
```
Migration walkthrough: [`docs/originjs-migration.md`](docs/originjs-migration.md).
Compatibility matrix: [`docs/compatibility-matrix.md`](docs/compatibility-matrix.md).
---
## Examples
The repo ships working examples for the main supported frameworks and production scenarios:
| Path | What it covers |
| ------------------------------------------------- | -------------------------------------------------------------- |
| `examples/react-remote` & `examples/react-host` | Baseline React remote ↔ host. |
| `examples/react-ssr-host` | React SSR host consuming a remote. |
| `examples/vue-remote` & `examples/vue-host` | Vue 3. |
| `examples/svelte-remote` & `examples/svelte-host` | Svelte. |
| `examples/lit-remote` & `examples/lit-host` | Lit / web components. |
| `examples/multi-remote-host` | Single host loading multiple remotes. |
| `examples/workspace-shared-{lib,remote,host}` | pnpm workspace + symlinked shared dependency. |
| `examples/dts-remote` & `examples/dts-host` | DTS auto-generation + host consumption. |
| `examples/shared-host-only-{remote,host}` | Host-only shares (`import: false`). |
| `examples/shared-negotiation-{remote,host}` | Cross-remote shared version negotiation. |
| `examples/shared-strict-fallback-app` | Strict-singleton fallback policy. |
| `examples/originjs-compat-host` | `virtual:__federation__` shim against real remotes. |
| `examples/webpack-systemjs-remote` | Vite host consuming a webpack `library.type: 'system'` remote. |
Useful local commands:
```bash
pnpm install
pnpm examples:build # React host/remote + SSR + DTS smoke path
pnpm examples:dev # React host/remote dev loop
pnpm examples:dts:build
pnpm examples:ssr:build
pnpm build # package + all workspaces with build scripts
pnpm --filter vite-plugin-federation test:e2e:browser-matrix
pnpm --filter vite-plugin-federation test:e2e:compat
pnpm --filter vite-plugin-federation test:e2e:shared
```
---
## Troubleshooting
The plugin uses stable, greppable error codes:
| Code | Meaning |
| --------- | -------------------------------------------------------------- |
| `MFV-001` | Plugin configuration error (missing `name`, etc.) |
| `MFV-002` | Alias conflict between a shared key and another resolver. |
| `MFV-003` | Shared dependency miss / fallback / strict-singleton conflict. |
| `MFV-004` | Manifest fetch / validation failed (with `sourceUrl`). |
| `MFV-005` | Expose missing or unsupported legacy `format`. |
| `MFV-006` | SSR remote entry missing for `target: 'node'`. |
| `MFV-007` | Dynamic import rewrite warning or unsupported legacy `from`. |
Snapshot the runtime in dev:
```ts
import { getFederationDebugInfo } from 'vite-plugin-federation/runtime';
console.log(getFederationDebugInfo());
// → instance, registered remotes, manifest cache + fetch timeline,
// integrity verification log, circuit breaker state, load metrics,
// shared providers + sharedResolutionGraph
```
Full guide: [`docs/troubleshooting.md`](docs/troubleshooting.md).
---
## Repository Layout
| Path | Purpose |
| ---------------------------------- | -------------------------------------------------------------- |
| `packages/vite-plugin-federation/` | Published plugin package and runtime implementation. |
| `examples/` | Local host/remote apps used by smoke tests and e2e suites. |
| `docs/` | API references, production runtime, migration, and operations. |
| `COMPARISON.md` | Feature matrix against reviewed npm package versions. |
| `.github/workflows/` | CI, extended e2e, and release automation. |
---
## Development
Requirements: **Node ≥ 20.19**, **pnpm 10.33.0**.
```bash
pnpm install
pnpm check # lint + type-check + unit tests for everything
pnpm dev # turbo dev for the package
pnpm build # build the package
pnpm lint
pnpm test # vitest, all workspaces
pnpm format
```
End-to-end suites (Playwright):
```bash
pnpm --filter vite-plugin-federation test:e2e # default + compat + shared + dts:dev
pnpm --filter vite-plugin-federation test:e2e:browser-matrix # Chromium / Firefox / WebKit
pnpm --filter vite-plugin-federation test:e2e:multi-remote
pnpm --filter vite-plugin-federation test:e2e:shared
pnpm --filter vite-plugin-federation test:e2e:ssr
pnpm --filter vite-plugin-federation test:e2e:compat
pnpm --filter vite-plugin-federation test:e2e:dts:dev
pnpm test:vite-matrix:smoke
```
E2E ports default to the documented local ports. If a local process already owns one of them, set
the matching `MF_E2E__PORT` variable before running the suite, for example
`MF_E2E_REACT_REMOTE_PORT=5274 pnpm --filter vite-plugin-federation test:e2e:multi-remote`.
To run an SSR example by hand:
```bash
pnpm --filter example-react-remote preview
pnpm --filter example-react-ssr-host serve
```
The SSR host launches Node with `--experimental-vm-modules`.
The flag is required because the MF runtime uses the VM module loader for remote ESM evaluation on
the server.
### Internal docs
- [`docs/compiler-adapter.md`](docs/compiler-adapter.md) — Rolldown vs Rollup transform notes,
control-chunk rules, the known CJS bundle warning.
- [`docs/devtools-runtime-contract.md`](docs/devtools-runtime-contract.md) — devtools event
contract.
- [`docs/manifest-protocol.md`](docs/manifest-protocol.md) — manifest schema + field rules.
---
## Release Flow
Versioning is managed by [Changesets](https://github.com/changesets/changesets):
```bash
pnpm changeset # author a changeset
pnpm version-packages # bump versions + update generated release files
pnpm release # build the package and publish via Changesets
```
The release workflow publishes from a semver tag (`v*.*.*`) or manual dispatch with a matching tag.
Full policy, quality gates, and step-by-step instructions:
[`docs/release-checklist.md`](docs/release-checklist.md).
---
## CI / CD
- **`.github/workflows/ci.yml`** — runs `pnpm check` on PRs and pushes to `main`; Node 22 also
runs the package smoke test and Vite peer runtime matrix smoke.
- **`.github/workflows/extended-e2e.yml`** — manual and weekly Playwright coverage for compat,
shared runtime, multi-remote, browser matrix, SSR, and DTS dev sync on Node 20 and 22.
- **`.github/workflows/release.yml`** — publishes matching `v*.*.*` tags or a manual tag dispatch
through npm Trusted Publisher / OIDC.
- Publishing does not use `NPM_AUTH_TOKEN` / `NODE_AUTH_TOKEN`; the npm Trusted Publisher entry must
match this repository and `release.yml`.
- Published packages use `provenance: true`.
---
## License
[MIT](https://github.com/jskits/vite-plugin-federation/blob/main/LICENSE)
---
# Package README
Source: packages/vite-plugin-federation/README.md
# vite-plugin-federation
[](https://github.com/jskits/vite-plugin-federation/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/vite-plugin-federation)

A production-ready GA Module Federation 2.0 plugin for Vite 5, 6, 7, and 8.
It is built around manifest-first remote loading, Node SSR, live DTS workflows, dev remote HMR,
multi-tenant runtime scopes, and operational runtime controls.
It also includes an OriginJS-compatible `virtual:__federation__` migration shim for existing Vite
federation apps.
## What is Module Federation?
Module Federation lets one frontend application load code from another frontend at runtime.
Each frontend can be built and deployed independently.
A typical setup has:
- a **host** or shell app, which owns the page and routing
- one or more **remotes**, which expose components, routes, or modules
- shared dependencies such as React, Vue, or design-system packages, negotiated at runtime
This is useful when multiple teams need to ship parts of the same product independently.
Each team can update its own remote without rebuilding and redeploying one large frontend every time.
Use this package when you want Vite apps to participate in that architecture.
It lets you build remotes, consume remotes from a host, and share dependencies safely.
It also loads remote manifests in production.
You probably do not need Module Federation if all code is released together in one app.
A normal npm package or Vite's built-in code splitting may be enough for simpler cases.
## GA Support Scope
`vite-plugin-federation` 1.0 is generally available for manifest-first Vite remotes, browser hosts,
Node SSR hosts, DTS generation and consumption, dev remote HMR, and the curated runtime APIs.
Webpack/SystemJS/`var` remotes are supported compatibility paths, but mixed migrations should be
validated per application.
Signed manifest verification is intentionally handled through a custom `fetch` wrapper so teams can
use their own signing and trust model.
## Install
```bash
pnpm add -D vite-plugin-federation
```
```bash
npm install -D vite-plugin-federation
```
```bash
yarn add -D vite-plugin-federation
```
Requirements:
- Node `>=20.19.0`
- Vite `^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`
These ranges are the v1.x compatibility floor.
The package exports:
- `vite-plugin-federation`: the Vite plugin.
- `vite-plugin-federation/runtime`: runtime helpers for manifest loading, SSR, preload, integrity,
diagnostics, and scoped runtimes.
## Why Use It
| Need | What this package provides |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| Vite 5 through Vite 8 | Rollup and Rolldown-aware output handling, including module-preload helper rewrites. |
| Manifest-first federation | `mf-manifest.json`, `mf-stats.json`, and `mf-debug.json` outputs by default. |
| Production host loading | Cache TTL, stale-while-revalidate, retries, timeouts, fallback URLs, request collapsing, and circuit breakers. |
| Node SSR | `ssrRemoteEntry`, server runtime creation, target-aware manifest registration, and SSR preload link collection. |
| Shared dependency control | Singleton diagnostics, strict singleton mode, version checks, host-only shares, and pnpm suffix matching. |
| DTS workflows | Remote type archives, host type consumption, manifest-derived type URLs, and dev hot sync. |
| Multi-tenant runtime isolation | Per-runtime-key manifest cache, breaker state, debug records, and load metrics. |
| Migration | OriginJS-compatible `virtual:__federation__` shim and legacy remote compatibility paths. |
## Quick Start
### Remote
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'catalog',
exposes: {
'./Button': './src/Button.tsx',
},
shared: {
react: { singleton: true, requiredVersion: '^19.0.0' },
'react/': { singleton: true, requiredVersion: '^19.0.0' },
},
}),
],
build: {
target: 'esnext',
},
});
```
By default, a remote build emits:
- `remoteEntry-[hash].js`
- `mf-manifest.json`
- `mf-stats.json`
- `mf-debug.json`
Use a fixed remote entry name when your deployment needs one:
```ts
federation({
name: 'catalog',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.tsx',
},
});
```
### Browser Host
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
shared: {
react: { singleton: true, requiredVersion: '^19.0.0' },
'react/': { singleton: true, requiredVersion: '^19.0.0' },
},
}),
],
build: {
target: 'esnext',
},
});
```
Load a manifest remote explicitly:
```tsx
import { lazy, Suspense } from 'react';
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
const catalogManifestUrl = 'https://cdn.example.com/catalog/mf-manifest.json';
const CatalogButton = lazy(async () => {
const mod = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
cacheTtl: 30_000,
retries: 2,
timeout: 4_000,
});
return { default: mod.default ?? mod };
});
export function App() {
return (
);
}
```
You can also use the classic Module Federation runtime style after remotes are registered:
```ts
import { loadRemote } from 'vite-plugin-federation/runtime';
const mod = await loadRemote('catalog/Button');
```
### Node SSR Host
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'ssr-shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
target: 'node',
}),
],
build: {
ssr: true,
target: 'esnext',
},
});
```
```ts
// server.ts
import {
collectFederationManifestPreloadLinks,
createServerFederationInstance,
fetchFederationManifest,
loadRemoteFromManifest,
} from 'vite-plugin-federation/runtime';
createServerFederationInstance({
name: 'ssr-shell',
remotes: [],
shared: {},
});
const manifestUrl = process.env.CATALOG_MANIFEST_URL!;
const manifest = await fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
const mod = await loadRemoteFromManifest('catalog/Button', manifestUrl, {
target: 'node',
timeout: 4_000,
});
const preloadLinks = collectFederationManifestPreloadLinks(manifestUrl, manifest, './Button');
```
Node SSR currently requires Node's VM ESM loader support:
```bash
node --experimental-vm-modules server.js
```
Do not accept tenant- or request-controlled manifest URLs directly in SSR. Treat a Node-target
remote as server-executed code, and route all manifest URLs through an operator-controlled allowlist.
## Plugin Configuration
```ts
import federation from 'vite-plugin-federation';
federation({
name: 'shell',
remotes: {},
exposes: {},
shared: {},
});
```
Common options:
| Option | Default | Description |
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------ |
| `name` | Required | Public application/container name. |
| `filename` | `remoteEntry-[hash]` | Browser remote entry filename. |
| `varFilename` | None | Emits an additional var-style remote entry for legacy hosts. |
| `exposes` | `{}` | Remote expose map. Keys usually start with `./`. |
| `remotes` | `{}` | Host remote map. Manifest URLs are recommended. |
| `shared` | `{}` | Shared providers and consumers. |
| `manifest` | `true` | Emits manifest, stats, and debug artifacts. |
| `dts` | Auto for TypeScript projects | Type generation and consumption through `@module-federation/dts-plugin`. |
| `dev` | Enabled | Devtools, type hints, live reload, hot types, and opt-in remote HMR. |
| `compat` | `true` | OriginJS-compatible virtual federation shim. |
| `shareStrategy` | `version-first` | Runtime shared provider selection. |
| `shareScope` | `default` | Default share scope. |
| `publicPath` | Vite `base` or `auto` | Public path used in generated manifest asset URLs. |
| `bundleAllCSS` | `false` | Adds all CSS assets to every expose manifest entry. |
| `runtimePlugins` | `[]` | Runtime plugin imports passed to Module Federation runtime init. |
| `target` | Build target | `web` or `node`; SSR builds auto-detect `node`. |
| `hostInitInjectLocation` | `html` | Inject host runtime initialization in HTML or entry. |
| `moduleParseTimeout` | `10` | Total module parse timeout in seconds. |
| `moduleParseIdleTimeout` | None | Idle parse timeout in seconds, reset after every parsed module. |
### Exposes
```ts
federation({
name: 'catalog',
exposes: {
'./Button': './src/Button.tsx',
'./ManualCssButton': {
import: './src/ManualCssButton.tsx',
css: { inject: 'manual' },
},
},
});
```
CSS modes:
- `head`: append CSS to `document.head`.
- `manual`: write CSS hrefs to the global CSS bucket for the consumer to apply.
- `none`: do not inject CSS.
`dontAppendStylesToHead: true` is accepted as an OriginJS-compatible alias for manual CSS handling.
### Remotes
Prefer manifest URLs for new hosts:
```ts
federation({
name: 'shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
});
```
Use object remotes for compatibility paths:
```ts
federation({
name: 'shell',
remotes: {
legacy: {
name: 'legacyRemote',
entry: 'https://cdn.example.com/legacy/remoteEntry.js',
entryGlobalName: 'legacyRemote',
from: 'vite',
format: 'esm',
type: 'module',
shareScope: 'default',
},
},
});
```
`format: 'systemjs'` expects `globalThis.System.import` at runtime. Browser CommonJS remotes are not
part of the supported compatibility scope.
### Shared Dependencies
```ts
federation({
name: 'shell',
shared: {
react: {
singleton: true,
strictSingleton: true,
requiredVersion: '^19.0.0',
strictVersion: true,
allowNodeModulesSuffixMatch: true,
},
'react/': {
singleton: true,
requiredVersion: '^19.0.0',
allowNodeModulesSuffixMatch: true,
},
'design-system': {
import: false,
requiredVersion: '^2.0.0',
},
},
});
```
Notes:
- Use trailing slash keys such as `react/` or `@scope/pkg/` for package subpaths.
- Use `allowNodeModulesSuffixMatch` for pnpm or symlinked workspace layouts.
- Use `import: false` for host-only shared modules. The host must provide the share; the remote will
not bundle a fallback.
- Use `strictSingleton` when singleton conflicts should fail instead of silently falling back.
### DTS
```ts
federation({
name: 'catalog',
dts: {
generateTypes: {
abortOnError: true,
},
consumeTypes: {
typesOnBuild: true,
abortOnError: true,
},
},
});
```
In TypeScript projects, DTS support is enabled automatically unless `dts: false` is configured.
Remote builds emit `@mf-types.zip` and per-expose `.d.ts` artifacts; hosts can discover type URLs
from `mf-manifest.json`.
### Dev HMR And Devtools
```ts
federation({
name: 'shell',
dev: {
remoteHmr: true,
devtools: true,
disableLiveReload: true,
disableHotTypesReload: false,
disableDynamicRemoteTypeHints: false,
},
});
```
Remote HMR is opt-in. Devtools and dynamic type hints are enabled by default in dev.
Dev servers expose:
- `/mf-manifest.json` or your configured manifest path.
- `/mf-debug.json`.
- `/remoteEntry.js` or the configured `filename`.
- `/__mf_hmr` when `dev.remoteHmr: true`.
- `/__mf_devtools` unless `dev.devtools: false`.
## Runtime API
Import runtime helpers from `vite-plugin-federation/runtime`:
```ts
import {
clearFederationRuntimeCaches,
collectFederationManifestExposeAssets,
collectFederationManifestPreloadLinks,
createFederationInstance,
createFederationManifestPreloadPlan,
createFederationRuntimeScope,
createServerFederationInstance,
fetchFederationManifest,
getFederationDebugInfo,
loadRemote,
loadRemoteFromManifest,
loadShare,
loadShareSync,
preloadRemote,
refreshRemote,
registerManifestRemote,
registerManifestRemotes,
registerPlugins,
registerRemotes,
registerShared,
verifyFederationManifestAssets,
warmFederationRemotes,
} from 'vite-plugin-federation/runtime';
```
The high-level production path is `loadRemoteFromManifest()`:
```ts
const mod = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
target: 'web',
cacheTtl: 30_000,
staleWhileRevalidate: true,
fallbackUrls: [backupManifestUrl],
retries: 2,
retryDelay: 200,
timeout: 4_000,
circuitBreaker: {
failureThreshold: 3,
cooldownMs: 30_000,
},
integrity: { mode: 'prefer-integrity' },
fetch,
fetchInit: { credentials: 'include' },
hooks: {
manifestFetch: (event) => console.debug(event),
remoteRegister: (event) => console.debug(event),
remoteLoad: (event) => console.debug(event),
remoteRefresh: (event) => console.debug(event),
},
});
```
### Runtime Scopes
Use runtime scopes when multiple tenants, experiments, or apps share one page:
```ts
import { createFederationRuntimeScope } from 'vite-plugin-federation/runtime';
const tenant = createFederationRuntimeScope('tenant-a');
await tenant.registerManifestRemote('catalog', tenantManifestUrl, {
shareScope: 'tenant-a',
});
const mod = await tenant.loadRemoteFromManifest('catalog/Button', tenantManifestUrl);
```
Each scope partitions manifest cache, in-flight requests, remote registration records, circuit
breaker state, load metrics, and debug snapshots.
### Preload
```ts
import {
createFederationManifestPreloadPlan,
fetchFederationManifest,
warmFederationRemotes,
} from 'vite-plugin-federation/runtime';
await warmFederationRemotes({
catalog: {
manifestUrl: catalogManifestUrl,
preload: { resourceCategory: 'sync' },
},
checkout: {
manifestUrl: checkoutManifestUrl,
preload: false,
},
});
const manifest = await fetchFederationManifest(catalogManifestUrl, { cacheTtl: 30_000 });
const plan = createFederationManifestPreloadPlan(
catalogManifestUrl,
manifest,
{
'/product/:id': './Button',
},
{
asyncChunkPolicy: 'css',
},
);
```
For SSR rendering, use `collectFederationManifestPreloadLinks()` to create deduped
`modulepreload` and `stylesheet` descriptors.
### Integrity And Authenticated Manifests
```ts
const mod = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
integrity: { mode: 'both' },
fetchInit: {
credentials: 'include',
headers: {
Authorization: `Bearer ${token}`,
},
},
});
```
Integrity modes:
- `prefer-integrity`: use SRI when present, otherwise use `contentHash`.
- `integrity`: require SRI.
- `content-hash`: require `contentHash`.
- `both`: require both values when verifying annotated assets.
Use `verifyFederationManifestAssets()` when you need to verify expose and preload assets declared in the manifest:
```ts
await verifyFederationManifestAssets(catalogManifestUrl, manifest, {
integrity: { mode: 'both' },
requireIntegrity: true,
});
```
For signed manifests, verify the detached signature in a custom `fetch` wrapper before returning the `Response` to the runtime.
### Diagnostics
```ts
import { getFederationDebugInfo } from 'vite-plugin-federation/runtime';
console.log(getFederationDebugInfo());
```
The debug snapshot includes:
- Module Federation runtime instance state.
- Registered remotes and shared providers.
- Manifest cache entries and source URLs.
- Manifest fetch timeline.
- Integrity verification results.
- Circuit breaker state.
- Remote load metrics.
- Shared resolution graph and singleton conflict diagnostics.
## OriginJS Migration
The OriginJS-compatible virtual module is enabled by default:
```ts
import {
__federation_method_ensure,
__federation_method_getRemote,
__federation_method_setRemote,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';
```
Disable it after migration:
```ts
federation({
name: 'shell',
compat: {
originjs: false,
virtualFederationShim: false,
},
});
```
## Security Notes
- Prefer manifest-first ESM remotes over legacy script injection paths.
- Treat Node SSR remotes as server-executed code.
- Do not pass user-controlled manifest URLs into SSR runtime helpers.
- Use origin allowlists for manifest URLs, remote entries, CSP, and Trusted Types policies.
- Use `fetchInit` or custom `fetch` for private manifests.
- Verify SRI/content hashes for high-trust deployments.
- Verify signed manifests outside the default runtime with a custom fetch wrapper.
- Keep Webpack/SystemJS/`var` compatibility paths isolated if they need looser CSP.
## Error Codes
| Code | Meaning |
| --------- | ------------------------------------------------------------------ |
| `MFV-001` | Plugin configuration error. |
| `MFV-002` | Alias conflict between a shared key and another resolver. |
| `MFV-003` | Shared dependency miss, fallback, or strict-singleton conflict. |
| `MFV-004` | Manifest fetch or validation failure. |
| `MFV-005` | Missing expose or unsupported legacy remote format. |
| `MFV-006` | SSR remote entry missing for a Node-target load. |
| `MFV-007` | Dynamic import rewrite warning or unsupported legacy `from` value. |
## Documentation
- [Documentation site](https://jskits.github.io/vite-plugin-federation/)
- [Public API contract](https://jskits.github.io/vite-plugin-federation/public-api-contract)
- [Plugin API](https://jskits.github.io/vite-plugin-federation/plugin-api)
- [Runtime API](https://jskits.github.io/vite-plugin-federation/runtime-api)
- [Manifest protocol](https://jskits.github.io/vite-plugin-federation/manifest-protocol)
- [Production runtime](https://jskits.github.io/vite-plugin-federation/production-runtime)
- [DTS workflows](https://jskits.github.io/vite-plugin-federation/dts-workflows)
- [Dev HMR](https://jskits.github.io/vite-plugin-federation/dev-hmr)
- [Security](https://jskits.github.io/vite-plugin-federation/security)
- [OriginJS migration](https://jskits.github.io/vite-plugin-federation/originjs-migration)
- [Compatibility matrix](https://jskits.github.io/vite-plugin-federation/compatibility-matrix)
## Examples
The repository includes E2E-backed examples for React, Vue, Svelte, Lit, SSR, DTS, shared
negotiation, workspace shared dependencies, OriginJS migration, and Webpack/SystemJS compatibility:
https://github.com/jskits/vite-plugin-federation/tree/main/examples
## License
MIT
---
# Plugin Comparison
Source: COMPARISON.md
# Vite Module Federation — Plugin Comparison
A feature-by-feature comparison of three Vite Module Federation plugins.
The comparison is based on npm package metadata, published package source, shipped docs, and this
repository's local tests.
For `@module-federation/vite@1.15.4`, this table counts the official Vite adapter plus two runtime
packages: `@module-federation/runtime@2.4.0` and `@module-federation/runtime-core@2.4.0`.
When a feature is available through those lower-level runtime APIs or an optional official runtime
plugin rather than as a turnkey Vite-adapter helper, the cell says so explicitly.
Optional runtime plugins are noted as separate installs; they are not counted as adapter defaults.
npm also publishes `@module-federation/core@1.0.0-canary.1`.
That package is not in `@module-federation/vite@1.15.4`'s dependency graph.
No comparison claim below relies on it.
| Symbol | Meaning |
| ------ | ------------------------------------------- |
| ✅ | First-class support (documented + tested) |
| 🟡 | Partial / lower-level API / optional plugin |
| ❌ | Not supported |
| n/a | Not applicable to that plugin's design |
> Sources were rechecked against the package versions below on 2026-05-13. Footnote markers
> like `[1]` refer to the evidence notes at the bottom of this document.
---
## At a Glance
| | **`vite-plugin-federation`** (this repo) | **`@module-federation/vite@1.15.4`** | **`@originjs/vite-plugin-federation`** |
| ------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| Reviewed version | `1.0.0` [1] | `@module-federation/vite@1.15.4`, `runtime@2.4.0`, `runtime-core@2.4.0`; `@module-federation/core@1.0.0-canary.1` is not in this adapter dependency graph [2] | `1.4.1` [3] |
| Vite peer range | `^5 \|\| ^6 \|\| ^7 \|\| ^8` [1] | `^5 \|\| ^6 \|\| ^7 \|\| ^8` [2] | not declared (devDep `^4.0.5`) [3] |
| Rolldown / Vite 8 | ✅ first-class compiler adapter | ✅ supported | ❌ not targeted |
| Underlying MF runtime | `@module-federation/runtime@2.3.3` (wrapped + extended) | `@module-federation/runtime@2.4.0` + `@module-federation/runtime-core@2.4.0` | hand-rolled (`virtual:__federation__`) |
| Manifest protocol | ✅ `mf-manifest.json` + `mf-stats.json` + `mf-debug.json` | ✅ `mf-manifest.json` + `mf-stats.json`, including `ssrRemoteEntry` meta | ❌ none |
| Curated runtime entry | ✅ `vite-plugin-federation/runtime` [1] | 🟡 import lower-level helpers from `@module-federation/runtime@2.4.0` | ❌ (`virtual:__federation__` only) |
| Dev mode for **remotes** | ✅ classified updates: `partial`, `style`, `types`, `full` | 🟡 React native HMR path + WS/reconnect; full reload fallback [2] | ❌ remote must be `vite build --watch` |
| SSR (Node) | ✅ separate SSR entry + `createServerFederationInstance` | 🟡 `target: 'node'` + `ssrRemoteEntry` metadata; no server helper | ❌ |
| OriginJS migration shim | ✅ `virtual:__federation__` compat layer enabled by default | ❌ | n/a (it _is_ this API) |
---
## 1. Module Federation Core
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------------------------------------------- | :---------------------------------------------: | :------------------------------------------------------------------------------------------: | :--------------------------------: |
| `name` / `filename` | ✅ | ✅ | ✅ |
| `exposes` | ✅ | ✅ | ✅ |
| `remotes` | ✅ (string or object form, manifest-first URLs) | ✅ | ✅ |
| `shared` | ✅ | ✅ | ✅ |
| Custom `shareScope` | ✅ | ✅ | ✅ [3] |
| Multiple scopes / runtime keys | ✅ `createFederationRuntimeScope` [4] | 🟡 `@module-federation/runtime@2.4.0` instances/share scopes; no tenant `runtimeKey` wrapper | ❌ |
| Runtime plugins (`registerPlugins`) | ✅ `runtimePlugins` option + re-exported [5] | ✅ `runtimePlugins` option + `@module-federation/runtime@2.4.0` `registerPlugins` [2] | ❌ (no runtime plugin system) |
| Custom compiler adapter / `compat: { originjs, virtualFederationShim }` | ✅ [6] | ❌ | ❌ |
| `from: 'vite' \| 'webpack'` normalization | ✅ (one-time `MFV-007` warning on unknown) | ❌ | ✅ legacy field |
## 2. Remote Formats
| Format | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------- | :-----------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :---------------------------------------: |
| `esm` / `module` | ✅ primary | ✅ primary | ✅ primary |
| `var` (browser global) | ✅ via `varFilename` [6] | ✅ via `varFilename` | ✅ via `loadJS()` script tag |
| `systemjs` / `system` | ✅ runtime-guarded; throws `MFV-005` if `System.import` missing [6] | 🟡 generated bootstrap has `globalThis.System.import` fallback; no OriginJS `format: 'systemjs'` shim [2] | ✅ same code path as `esm` |
| Manifest-first remote URL (`https://.../mf-manifest.json`) | ✅ recommended path | ✅ | ❌ |
| Webpack-built remote consumed by Vite host | ✅ via compat e2e (webpack `library.type: 'system'` remote) [6] | ✅ `vite-webpack-rspack` e2e [12] | ✅ legacy `from: 'webpack'` examples [13] |
| CommonJS browser remote | ❌ unsupported by design (use SSR/ESM instead) | ❌ | ❌ |
## 3. Shared Dependencies
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------- | :-------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | :------------------------------------: |
| `singleton` | ✅ | ✅ | ❌ (commented out in public types) [3] |
| `eager` | ❌ plugin config (`@module-federation/runtime@2.3.3` can register it) | ❌ plugin config (`@module-federation/runtime@2.4.0` can register it) | ❌ (commented out in public types) [3] |
| `strictVersion` | ✅ | ✅ | ❌ |
| `requiredVersion` (semver range) | ✅ | ✅ | ✅ (custom `satisfy` impl) |
| Version negotiation across remotes | ✅ via `@module-federation/runtime@2.3.3` | ✅ via `@module-federation/runtime@2.4.0` | 🟡 first-match only |
| Workspace / scoped shares (`react/`, pnpm symlinks) | ✅ `allowNodeModulesSuffixMatch` [4] | 🟡 workspace e2e; no documented suffix matching [12] | ❌ |
| Host-only shares (`import: false`) | ✅ explicit `MFV-003` on miss [4] | 🟡 supported in types/tests/examples; sparse README docs [12] | ❌ |
| Strict-singleton fallback policy | ✅ recorded in `sharedResolutionGraph` [4] | ❌ | ❌ |
| `bundleAllCSS` option | ✅ option + per-expose CSS modes [4] | ✅ option | ❌ |
| Shared module preload (``) | ✅ via preload plan API | ✅ `@module-federation/runtime@2.4.0` `preloadRemote` + manifest asset analysis [2] | ✅ `modulePreload` flag |
## 4. Exposes & Named Exports
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------- | :------------------------------------------------------------: | :-------------------------------------------------: | :--------------------------------------: |
| Default export | ✅ | ✅ | ✅ |
| Named exports (`import { foo } from 'remote/x'`) | ✅ — Rolldown-aware via `pluginRemoteNamedExports` rewrite [6] | ✅ Rollup `syntheticNamedExports`; Rolldown limited | 🟡 single-import only |
| Per-expose CSS injection mode (`'head' \| 'manual' \| 'none'`) | ✅ [4] | 🟡 single global `bundleAllCSS` | ✅ `dontAppendStylesToHead` (per-expose) |
| Custom expose chunk name (`exposes[].name`) | ❌ | ❌ | ✅ [3] |
| `varFilename` for additional var-style entry | ✅ | ✅ | n/a |
## 5. Dev Experience
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------- | :------------------------------------------------------------: | :-------------------------------------------------------------------------: | :-------------------------------------: |
| Dev server for **host** | ✅ | ✅ | ✅ |
| Dev server for **remote** (no rebuild) | ✅ — sidecar dev runtime [4] | ✅ | ❌ remote must `vite build --watch` [3] |
| Remote-change HMR signal to host | ✅ classified updates: `partial`, `style`, `types`, `full` [4] | 🟡 React native strategy; WS metadata/reconnect; full reload fallback [2] | ❌ |
| Batched / coalesced updates | ✅ [4] | ❌ | ❌ |
| Live `.d.ts` sync (no reload) | ✅ via `dts-plugin` dev worker + WS [4] | 🟡 dev worker present; no local e2e found | ❌ |
| Dynamic remote-type hints injection | ✅ option | ✅ via `@module-federation/dts-plugin` | ❌ |
| Dev top-level-await proxy | ✅ | ✅ | n/a |
| Dev manifest endpoint(s) | ✅ | ✅ served at configured manifest filename (default `/mf-manifest.json`) [2] | ❌ |
## 6. TypeScript / DTS
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------- | :------------------------------------------: | :------------------------------------------: | :--------------------------------: |
| Auto `.d.ts` generation for `exposes` | ✅ via `@module-federation/dts-plugin@2.3.3` | ✅ via `@module-federation/dts-plugin@2.4.0` | ❌ (hand-written types only) |
| `@mf-types.zip` artifact | ✅ | ✅ | ❌ |
| Manifest-derived type URL on host | ✅ + `remoteTypeUrls` override [4] | ✅ | ❌ |
| `typesOnBuild: true` (fail build on missing types) | ✅ | ✅ | ❌ |
| Hot type updates in dev (no reload) | ✅ | 🟡 dev worker exists; no local e2e found | ❌ |
## 7. SSR (Node)
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------- | :-------------------------------------------------: | :-------------------------------------------------------------------------------: | :--------------------------------: |
| `target: 'web' \| 'node'` resolution | ✅ (auto from `build.ssr`, `ENV_TARGET` define) [6] | ✅ same mechanism | ❌ |
| Dedicated `ssrRemoteEntry` artifact | ✅ separate file emitted | 🟡 `metaData.ssrRemoteEntry` emitted; `1.15.4` aliases it to `remoteEntry` [2] | ❌ |
| Server runtime instance API (`createServerFederationInstance`) | ✅ [4] | 🟡 `@module-federation/runtime@2.4.0` `createInstance`; no server-specific helper | ❌ |
| Asset-preload collection for streaming HTML | ✅ `collectFederationManifestPreloadLinks` [4] | 🟡 manifest assetAnalysis + runtime preload; no streaming link collector | ❌ |
| Hydration-safe module-preload helper | ✅ short-circuits when `document` is undefined [6] | ✅ similar patch | ❌ |
| Required Node flags | `--experimental-vm-modules` [4] | n/a | n/a |
## 8. Production Runtime (host loader)
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------------------- | :-------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Manifest remote loading API | ✅ `loadRemoteFromManifest()` [4] | ✅ manifest remotes via `@module-federation/runtime@2.4.0` `loadRemote` [2] | ❌ |
| Cache TTL / `staleWhileRevalidate` / `force` refresh | ✅ [4] | 🟡 `@module-federation/runtime-core@2.4.0` manifest cache exists; no TTL/SWR wrapper [2] | ❌ |
| Retry with jitter + timeout | ✅ [4] | 🟡 separately installable official `@module-federation/retry-plugin@2.4.0`; no adapter default [2] | ❌ |
| Fallback URLs | ✅ records effective `sourceUrl` | 🟡 separately installable `@module-federation/retry-plugin@2.4.0` domain / manifest-domain rotation [2] | ❌ |
| Circuit breaker (`failureThreshold`, `cooldownMs`) | ✅ [4] | ❌ | ❌ |
| Concurrent-fetch request collapsing | ✅ [4] | 🟡 `@module-federation/runtime-core@2.4.0` global loading / manifest loading maps [2] | ❌ |
| Integrity verification (SRI / SHA-256, multi-mode) | ✅ `verifyFederationManifestAssets` [7] | ❌ | ❌ |
| Private / authenticated manifests (`fetchInit`, custom `fetch`) | ✅ [7] | 🟡 `@module-federation/runtime-core@2.4.0` `loaderHook.fetch`; optional retry plugin `fetchOptions` requires separate install [2] | ❌ |
| Signed manifest workflow | ✅ documented + custom-fetch hook [7] | ❌ | ❌ |
| CSP / Trusted Types guidance | ✅ [7] | ❌ | ❌ |
## 9. Multi-Tenant / Scoping
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------------------- | :-----------------------------------------------: | :-----------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Per-tenant `runtimeKey` scope | ✅ `createFederationRuntimeScope(runtimeKey)` [8] | ❌ | ❌ |
| Tenant-isolated manifest cache | ✅ | 🟡 possible with separate `@module-federation/runtime@2.4.0` instances; no `runtimeKey` cache partition | ❌ |
| Tenant-isolated circuit breaker / debug records | ✅ | ❌ | ❌ |
| Per-tenant `shareScope` | ✅ | ✅ (one global scope per plugin instance) | ✅ (same) |
## 10. Preload & Performance
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------------------- | :------------------------------------------: | :-----------------------------------------------------------------------------------: | :--------------------------------: |
| Route-aware preload plan | ✅ `createFederationManifestPreloadPlan` [9] | ❌ | ❌ |
| Async-chunk preload policy (`'css' \| 'none' \| 'js' \| 'all'`) | ✅ [9] | 🟡 `@module-federation/runtime@2.4.0` `preloadRemote` filters/resource categories [2] | ❌ |
| Remote warming (`warmFederationRemotes`) | ✅ [9] | ✅ `@module-federation/runtime@2.4.0` `preloadRemote` [2] | ❌ |
| Per-remote load metrics (registration / load / total) | ✅ [9] | ❌ | ❌ |
| Manifest-declared route hints | ✅ | ❌ | ❌ |
## 11. Observability & DevTools
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Dev devtools panel + browser events | ✅ `pluginDevtools.ts` [4] | ❌ | ❌ |
| `getFederationDebugInfo()` snapshot | ✅ (instance, manifests, fetch timeline, integrity, breaker, metrics, shared graph) [4] | 🟡 `@module-federation/runtime-core@2.4.0` exposes `globalThis.__FEDERATION__`, snapshots, and debug constructor; no same helper [2] | ❌ |
| Telemetry hooks (`manifestFetch`, `remoteRegister`, `remoteLoad`, `remoteRefresh`) | ✅ [4] | 🟡 runtime hooks such as `loaderHook.fetch`, `registerRemote`, `onLoad`, `errorLoadRemote`; no same named telemetry API / `remoteRefresh` [2] | ❌ |
| Stable error codes (`MFV-001` … `MFV-007`) | ✅ [10] | 🟡 `@module-federation/runtime@2.4.0` has its own error codes; not `MFV-*` | ❌ generic strings |
| `mf-debug.json` build artifact | ✅ | ❌ | ❌ |
## 12. Security
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------- | :---------------------------------------------------------------------: | :------------------------------: | :------------------------------------: |
| Subresource integrity in manifest | ✅ [7] | ❌ | ❌ |
| Content-hash verification | ✅ multi-mode (`prefer-integrity`, `integrity`, `content-hash`, `both`) | ❌ | ❌ |
| CSP / nonce friendly browser builds | ✅ documented; browser target avoids inline scripts [7] | 🟡 similar target tree-shaking | 🟡 script-tag legacy paths need review |
| Trusted Types support | ✅ documented | ❌ | ❌ |
| Signed manifest pattern | ✅ via `fetch` wrapper | ❌ | ❌ |
## 13. Compiler / Build Adapter
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------: | :--------------------------------: | :--------------------------------: |
| Rollup output handling | ✅ | ✅ | ✅ |
| Rolldown output handling (Vite 8) | ✅ separate `rolldownOptions` path + `getRolldownOptions` rewrite [6] | ✅ separate `rolldownOptions` path | ❌ |
| Top-level-await deadlock prevention (CJS-proxy helper inlining) | ✅ two-pass `generateBundle` [6] | ✅ same algorithm (forked here) | n/a |
| `runtimeInit` + `loadShare` chunk isolation | ✅ enforced (rejects user `manualChunks` that conflict, with warning) [6] | ✅ same | n/a |
| Module-preload helper rewrite to `import.meta.url` (so cross-origin remote chunks resolve correctly) | ✅ covers Vite 8, esbuild, terser, arrow + function form [6] | ✅ same | 🟡 partial |
| `commonjsOptions.strictRequires: 'auto'` defaulting | ✅ | ✅ | ❌ |
| `output.codeSplitting.groups` guard (Vite 8) | ✅ | ✅ | n/a |
## 14. OriginJS Compatibility
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------------- | :--------------------------------------------------------------------------------------: | :------------------------------: | :--------------------------------: |
| `virtual:__federation__` shim | ✅ enabled by default; toggle via `compat.virtualFederationShim` [11] | ❌ | n/a (it _is_ the API) |
| `__federation_method_setRemote / getRemote / ensure / unwrap / wrap` | ✅ all five | ❌ | ✅ |
| Normalizes OriginJS `./Button` → `remote/Button` | ✅ | ❌ | n/a |
| `dontAppendStylesToHead` → `css.inject: 'manual'` mapping | ✅ | ❌ | ✅ native |
| Drop-in adoption from OriginJS apps | ✅ + e2e validates a Vite host loading a webpack `systemjs` remote through the shim [11] | ❌ requires rewrite | n/a |
## 15. Public API Surface
### `vite-plugin-federation` (this repo)
```ts
// Build-time plugin
import federation, { type ModuleFederationOptions } from 'vite-plugin-federation';
// Production runtime helpers (curated subpath export)
import {
createFederationInstance,
createServerFederationInstance,
createFederationRuntimeScope,
fetchFederationManifest,
registerManifestRemote,
registerManifestRemotes,
loadRemoteFromManifest,
refreshRemote,
warmFederationRemotes,
preloadRemote,
createFederationManifestPreloadPlan,
collectFederationManifestPreloadLinks,
verifyFederationManifestAssets,
getFederationDebugInfo,
clearFederationRuntimeCaches,
// re-exports of @module-federation/runtime@2.3.3
registerPlugins,
registerRemotes,
registerShared,
loadRemote,
loadShare,
loadShareSync,
} from 'vite-plugin-federation/runtime';
```
### `@module-federation/vite@1.15.4`
```ts
import { federation } from '@module-federation/vite';
// Reviewed runtime API package: @module-federation/runtime@2.4.0
import { loadRemote, registerRemotes } from '@module-federation/runtime';
```
### `@originjs/vite-plugin-federation`
```ts
import federation from '@originjs/vite-plugin-federation';
import {
__federation_method_ensure,
__federation_method_getRemote,
__federation_method_setRemote,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';
```
## 16. Test & Example Surface
| Surface | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------- | :----------------------------------------------------------------------: | :--------------------------------------------------------------------------: | :--------------------------------: |
| Unit tests | ✅ vitest | ✅ vitest | ✅ vitest |
| Integration tests (build pipeline) | ✅ | ✅ (`integration/`) | 🟡 |
| Browser e2e (Playwright) | ✅ 6 configs: default, compat, multi-remote, shared, ssr, browser-matrix | ✅ vite-vite + vite-webpack-rspack | ✅ |
| SSR e2e | ✅ | ❌ | ❌ |
| Browser matrix | ✅ Chromium / Firefox / WebKit | ❌ Chromium only | ❌ |
| Vite peer-version smoke | ✅ packed tarball builds and runs against Vite 5 / 6 / 7 / 8 | ✅ CI integration matrix Vite 5 / 6 / 7 / 8; examples include 6 / 7 / 8 [12] | ❌ Vite 4-focused |
| Webpack-remote interop e2e | ✅ `examples/webpack-systemjs-remote` through compat shim | ✅ `vite-webpack-rspack` [12] | ✅ webpack/SystemJS examples [13] |
| Workspace / pnpm-symlink shared | ✅ `examples/workspace-shared-*` | ✅ `@vite-vite/shared-lib` e2e [12] | ❌ |
| DTS hot-sync e2e | ✅ `e2e/dts-dev-hot-sync.mjs` | ❌ | ❌ |
---
## 17. Ecosystem, Options & Governance
| Surface | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------- | :--------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------: |
| Framework examples | ✅ React / Vue / Svelte / Lit / SSR / DTS / workspace [4] | 🟡 local examples cover Vite/Vite, Vite/Webpack/Rspack, runtime-register, Rust/Rsbuild; broader ecosystem examples are not bundled here [12] | ✅ Vue / React / Rollup / Webpack examples [13] |
| `@module-federation/vite@1.15.4` options | ✅ baseline options plus runtime / SSR / compat extensions | ✅ baseline adapter options [12] | ❌ legacy option model |
| Migration docs | ✅ OriginJS + `@module-federation/vite@1.15.4` guides [4] | 🟡 README/examples, no migration guide in `1.15.4` package [12] | 🟡 legacy docs, no MF-runtime guide [13] |
| License | MIT [14] | MIT [14] | MulanPSL-2.0 [14] |
---
## When to Choose Which
### Pick `vite-plugin-federation` (this repo) when…
- You need **production-oriented host loading** with cache TTL, retries, circuit breaker, fallback
URLs, and integrity verification.
- You ship to **multiple tenants on the same page** and need scoped manifest caches and share
scopes.
- You serve **SSR** (React, Vinext, etc.) and need a server-aware federation instance with
asset-preload collection.
- You want **stable error codes** (`MFV-NNN`) and a debug snapshot for ops.
- You're **migrating off OriginJS** and want the legacy `virtual:__federation__` API to keep
working while you move to manifest-first config.
- You target **Vite 8 + Rolldown** today.
### Pick `@module-federation/vite@1.15.4` when…
- You're already in the `@module-federation/*` ecosystem and want a thin, official Vite adapter
whose runtime semantics exactly match `@module-federation/runtime@2.4.0` defaults.
- You value the broader `@module-federation/*` ecosystem examples and are comfortable importing
runtime helpers directly from `@module-federation/runtime@2.4.0`.
- You're comfortable composing lower-level `@module-federation/runtime@2.4.0` /
`@module-federation/runtime-core@2.4.0` APIs and optional official runtime plugins for
resilience instead of using this repo's built-in policy wrapper.
- You want the **smallest MF-runtime-based adapter surface** that still produces a manifest.
### Pick `@originjs/vite-plugin-federation` when…
- You have an existing app already on OriginJS and **don't yet need dev mode for remotes**
(e.g., remotes are deployed as built artifacts and the host is the only thing under active
dev).
- You rely on OriginJS's legacy `format` / `from` / `externalType` model or its webpack/SystemJS
examples.
- You can't add the `@module-federation/runtime@2.3.3` dependency (e.g., the project disallows new
runtime deps), and the lack of singleton / strictVersion negotiation is acceptable.
- You're targeting **Vite 4** specifically.
> If you're starting a new micro-frontend in 2026 on Vite 5+, the OriginJS plugin's
> "remote must be `vite build --watch`" constraint and lack of a shared-version negotiator
> make it the wrong default for new code.
---
## Migration Notes
- **From `@originjs/vite-plugin-federation` → `vite-plugin-federation`:** the `virtual:__federation__`
API works unchanged; map `dontAppendStylesToHead: true` to `css.inject: 'manual'` (or leave
it — the shim normalizes both). See `docs/originjs-migration.md` and
`examples/originjs-compat-host`.
- **From `@module-federation/vite@1.15.4` → `vite-plugin-federation`:** the plugin options shape is a
superset; replace `import { federation } from '@module-federation/vite'` with
`import federation from 'vite-plugin-federation'`. Direct
`@module-federation/runtime@2.4.0` imports already expose official helpers such as
`registerPlugins`, `preloadRemote`, `registerRemotes`, and `loadRemote`; switch selected call
sites to `vite-plugin-federation/runtime` when you want this repo's
cached/retried/integrity-checked manifest helpers and debug snapshot wrapper.
---
## Evidence Notes
- [1] `packages/vite-plugin-federation/package.json`, README, docs, unit tests, and e2e suites in this repository.
- [2] npm package metadata and tarballs rechecked on 2026-05-13:
`@module-federation/vite@1.15.4` depends on `@module-federation/runtime@2.4.0`,
`@module-federation/sdk@2.4.0`, and `@module-federation/dts-plugin@2.4.0`;
`@module-federation/runtime@2.4.0` depends on `@module-federation/runtime-core@2.4.0`.
`@module-federation/core@1.0.0-canary.1` was also checked, but it is not in
`@module-federation/vite@1.15.4`'s dependency graph, so feature claims above are based on
`@module-federation/vite@1.15.4`, `@module-federation/runtime@2.4.0`, and
`@module-federation/runtime-core@2.4.0`. Optional retry/fallback rows reference the separately
installable official `@module-federation/retry-plugin@2.4.0`; it is not in this adapter
dependency graph.
Relevant published/local APIs include `remoteHmr`, `runtimePlugins`, `preloadRemote`,
`registerPlugins`, `loaderHook.fetch`, `registerRemote`, `onLoad`, `errorLoadRemote`,
manifest cache/loading state, `globalThis.__FEDERATION__`, `globalThis.System.import`
bootstrap fallback, and manifest `metaData.ssrRemoteEntry`.
- [3] `@originjs/vite-plugin-federation@1.4.1` npm metadata, public types, and examples.
- [4] Local docs/tests for this repository's runtime wrapper, dev HMR, SSR, DTS, examples, and e2e suites.
- [5] `vite-plugin-federation/runtime` re-exports selected `@module-federation/runtime@2.3.3` helpers.
- [6] Local compiler adapter and OriginJS compatibility implementation/tests.
- [7] Local production-runtime and security docs/tests.
- [8] Local multi-tenant docs/tests.
- [9] Local preload/performance docs/tests.
- [10] Local logger and public API contract docs/tests.
- [11] Local OriginJS migration shim docs/tests.
- [12] Local `@module-federation/vite@1.15.4` source checkout: README, types, integration tests, Playwright config, bundled examples, and CI workflow metadata. Broader ecosystem examples outside this checkout were not revalidated here.
- [13] OriginJS public README/examples.
- [14] Package licenses in the reviewed npm packages.
---
# Getting Started
Source: docs/getting-started.md
# Getting Started
`vite-plugin-federation` lets Vite applications publish remote modules and load them from a host at
runtime. The recommended production path is manifest-first: remotes publish `mf-manifest.json`, and
hosts load exposed modules through `vite-plugin-federation/runtime`.
## Install
```bash
pnpm add -D vite-plugin-federation
```
The package supports Node `>=20.19.0` and Vite `^5 || ^6 || ^7 || ^8`.
## Remote
```ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'catalog',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.tsx',
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
'react/': { singleton: true, requiredVersion: '^19.2.4' },
'react-dom/': { singleton: true, requiredVersion: '^19.2.4' },
},
}),
],
build: {
target: 'esnext',
cssCodeSplit: true,
},
});
```
Manifest generation is enabled by default. A production remote build emits:
- `mf-manifest.json`
- `mf-stats.json`
- `mf-debug.json`
- browser and optional SSR remote entries
- exposed JavaScript, CSS, and DTS artifacts
## Host
```ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
},
}),
],
build: { target: 'esnext' },
});
```
## Load A Remote
```tsx
import { lazy, Suspense, type ComponentType } from 'react';
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
type RemoteButtonModule = { default: ComponentType };
const CatalogButton = lazy(async () => {
const mod = await loadRemoteFromManifest(
'catalog/Button',
'https://cdn.example.com/catalog/mf-manifest.json',
{
cacheTtl: 30_000,
integrity: true,
retries: 2,
timeout: 4_000,
},
);
return { default: mod.default };
});
export function App() {
return (
);
}
```
Use [Runtime API](runtime-api.md) for the complete helper surface.
## SSR Host
```ts
import {
collectFederationManifestPreloadLinks,
createServerFederationInstance,
fetchFederationManifest,
loadRemoteFromManifest,
} from 'vite-plugin-federation/runtime';
createServerFederationInstance({
name: 'ssr-shell',
remotes: [],
shared: {},
});
const manifestUrl = 'https://cdn.example.com/catalog/mf-manifest.json';
const manifest = await fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
const links = collectFederationManifestPreloadLinks(manifestUrl, manifest, ['./Button']);
const mod = await loadRemoteFromManifest('catalog/Button', manifestUrl, { target: 'node' });
```
See [Production Runtime](production-runtime.md) for SSR entry selection, streaming preload links,
refresh behavior, and deployment checklists.
## Next Steps
- Review [Plugin API](plugin-api.md) before expanding `exposes`, `remotes`, `shared`, `dev`, `dts`, or `compat`.
- Review [Manifest Protocol](manifest-protocol.md) before wiring deployment and cache policy.
- Review [Security](security.md) before loading private manifests or enabling integrity verification.
- Review [Troubleshooting](troubleshooting.md) when an `MFV-*` error code appears.
---
# Public API Contract
Source: docs/public-api-contract.md
# Public API Contract
This document defines the compatibility surface that `vite-plugin-federation` keeps stable for the
1.x release line.
## Compatibility Policy
- Patch and minor releases in `1.x` must preserve existing public option names, runtime export names,
manifest schema semantics, devtools contract fields, and `MFV-*` error-code meanings.
- Minor releases may add new options, optional manifest fields, runtime helpers, devtools fields, or
error codes when existing consumers can ignore them safely.
- Deprecations must keep the old behavior working for the rest of the `1.x` line unless a security or
upstream runtime issue makes that impossible. Removing or changing a documented contract requires a
new major version.
- Internal virtual modules, generated chunk filenames, private debug state not documented here, and
implementation details under `src/` are not public contracts.
## Package And Engine Contract
The published package keeps these entry points stable:
- `vite-plugin-federation`
- `vite-plugin-federation/runtime`
The v1.x support floor is:
- Node `>=20.19.0`
- Vite `^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`
Dropping support for Node `>=20.19.0` or any currently declared Vite major in that peer range is a
major-version change unless a security or upstream end-of-life issue requires a documented exception.
## Plugin Options
The package root exports the default plugin function and the named `federation` function. It also
exports the public `ModuleFederationOptions` and `PluginManifestOptions` types.
The `ModuleFederationOptions` field names and current defaults are part of the v1.x contract:
| Option | Compatibility commitment |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| `name` | Required public app/container name. |
| `filename` | Browser remote entry filename; default remains `remoteEntry-[hash]`. |
| `varFilename` | Additional var-style entry filename for legacy hosts. |
| `exposes` | String and object expose forms, including `css.inject` and `dontAppendStylesToHead`. |
| `remotes` | Manifest URL strings and object remote configs. |
| `shared` | Array and object forms, including singleton, strict version, host-only, and suffix matching controls. |
| `manifest` | `boolean` or `PluginManifestOptions`; default remains enabled. |
| `dts` | `boolean` or DTS options; TypeScript projects keep automatic enablement unless disabled. |
| `dev` | `boolean` or dev options for devtools, live reload, type hints, hot types, and remote HMR. |
| `compat` | `boolean` or compatibility options; OriginJS compatibility remains enabled by default. |
| `shareStrategy` | Runtime share strategy; default remains `version-first`. |
| `shareScope` | Default share scope; default remains `default`. |
| `publicPath` | Public path for generated manifest asset URLs. |
| `bundleAllCSS` | Adds CSS assets to expose manifest entries; default remains `false`. |
| `runtimePlugins` | Runtime plugin import list passed to the Module Federation runtime. |
| `target` | `web` or `node`; SSR builds continue to auto-detect `node`. |
| `hostInitInjectLocation` | Host runtime init injection location; default remains `html`. |
| `moduleParseTimeout` | Total module parse timeout in seconds; default remains `10`. |
| `moduleParseIdleTimeout` | Optional idle parse timeout in seconds. |
| `virtualModuleDir` | Virtual module directory option; slash rejection remains `MFV-001`. |
| `library` | Passed through for Module Federation compatibility. |
| `runtime` | Passed through for Module Federation compatibility. |
| `getPublicPath` | Passed through for Module Federation compatibility. |
| `implementation` | Runtime implementation override. |
| `ignoreOrigin` | Origin handling compatibility flag. |
## Runtime Exports
The `vite-plugin-federation/runtime` function export names are part of the v1.x contract:
- `getInstance`
- `createFederationInstance`
- `createServerFederationInstance`
- `registerPlugins`
- `registerGlobalPlugins`
- `registerRemotes`
- `registerShared`
- `getRemoteEntry`
- `getRemoteInfo`
- `loadScript`
- `loadScriptNode`
- `loadShare`
- `loadShareSync`
- `loadRemote`
- `preloadRemote`
- `fetchFederationManifest`
- `registerManifestRemote`
- `registerManifestRemotes`
- `loadRemoteFromManifest`
- `refreshRemote`
- `warmFederationRemotes`
- `resolveFederationManifestAssetUrl`
- `findFederationManifestExpose`
- `collectFederationManifestExposeAssets`
- `collectFederationManifestPreloadLinks`
- `createFederationManifestPreloadPlan`
- `verifyFederationManifestAssets`
- `clearFederationRuntimeCaches`
- `getFederationDebugInfo`
- `getFederationRuntimeScopeDebugInfo`
- `createFederationRuntimeScope`
Exported TypeScript types from `vite-plugin-federation/runtime` are also public. Existing type names,
required fields, and return shapes must remain assignable for `1.x`; additive optional fields are
allowed.
## Manifest Schema
Manifest-enabled builds emit `mf-manifest.json`, `mf-stats.json`, and `mf-debug.json` artifacts. The
v1 schema contract is `schemaVersion: "1.0.0"` and is documented in
[manifest-protocol.md](manifest-protocol.md).
Compatibility rules:
- A `1.0.0` runtime accepts missing `schemaVersion` as a legacy manifest.
- A `1.0.0` runtime accepts same-major `1.x.y` manifests when required runtime fields remain usable.
- A `1.0.0` runtime rejects future major manifests with `MFV-004`.
- Patch and minor schema changes in major version `1` must be additive.
## Devtools Contract
The devtools browser global and dev server payload are versioned as contract `1.0.0` in
[devtools-runtime-contract.md](devtools-runtime-contract.md).
These names remain stable for `1.x`:
- `window.__VITE_PLUGIN_FEDERATION_DEVTOOLS__`
- `//__mf_devtools`
- `vite-plugin-federation:debug`
- `vite-plugin-federation:remote-update`
- `vite-plugin-federation:remote-style-update`
- `vite-plugin-federation:remote-types-update`
New fields and events may be added, but existing top-level fields and event meanings must remain
compatible.
## Error Codes
The `MFV-*` code meanings are stable for `1.x`:
| Code | Meaning |
| --------- | --------------------------------------------------------------- |
| `MFV-001` | Plugin configuration error. |
| `MFV-002` | Alias conflict between a shared key and another resolver. |
| `MFV-003` | Shared dependency miss, fallback, or strict-singleton conflict. |
| `MFV-004` | Manifest fetch or validation failure. |
| `MFV-005` | Missing expose or unsupported legacy remote format. |
| `MFV-006` | SSR remote entry missing for a Node-target load. |
| `MFV-007` | Dynamic import rewrite warning or unsupported legacy `from`. |
New codes may be added for new failure classes. Existing codes must not be reused for unrelated
failure semantics.
---
# Plugin API
Source: docs/plugin-api.md
# Plugin API Reference
Import the plugin from the package root:
```ts
import federation from 'vite-plugin-federation';
```
## Minimal Remote
```ts
federation({
name: 'catalog',
exposes: {
'./Button': './src/Button.tsx',
},
manifest: true,
shared: {
react: {
singleton: true,
requiredVersion: '^19.2.4',
},
'react/': {
singleton: true,
requiredVersion: '^19.2.4',
},
},
});
```
## Minimal Host
```ts
federation({
name: 'shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
shared: {
react: {
singleton: true,
requiredVersion: '^19.2.4',
},
},
});
```
## `ModuleFederationOptions`
| Option | Type | Default | Description |
| ------------------------ | ---------------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------- |
| `name` | `string` | required | Public application/container name. |
| `filename` | `string` | `remoteEntry-[hash]` | Browser remote entry filename. |
| `varFilename` | `string` | `undefined` | Emits an additional var-style remote entry for legacy hosts. |
| `exposes` | `Record` | `{}` | Remote expose map. Keys should usually start with `./`. |
| `remotes` | `Record` | `{}` | Host remote map. Values may be manifest URLs or object configs. |
| `shared` | `string[] \| Record` | `{}` | Shared dependency providers/consumers. |
| `manifest` | `boolean \| PluginManifestOptions` | `true` | Emits `mf-manifest.json`, `mf-stats.json`, and `mf-debug.json` when enabled. |
| `dts` | `boolean \| PluginDtsOptions` | auto for TS projects | Enables type generation and/or type consumption through `@module-federation/dts-plugin`. |
| `dev` | `boolean \| PluginDevOptions` | enabled | Controls devtools, live reload, remote HMR, type hints, and hot type reload. |
| `compat` | `boolean \| CompatibilityOptions` | `true` | Enables OriginJS-compatible virtual federation APIs. |
| `shareStrategy` | `'loaded-first' \| 'version-first'` | `version-first` | Shared provider selection strategy passed to the runtime. |
| `shareScope` | `string` | `default` | Default share scope. |
| `publicPath` | `string` | Vite `base` or `auto` | Public path used for generated manifest asset URLs. |
| `bundleAllCSS` | `boolean` | `false` | Adds all CSS assets from the bundle to every expose manifest entry. |
| `runtimePlugins` | `Array` | `[]` | Runtime plugin imports passed to Module Federation runtime init. |
| `target` | `'web' \| 'node'` | build target | Overrides generated remote target. SSR builds normally use `node`. |
| `virtualModuleDir` | `string` | `__mf__virtual` | Internal virtual module directory name. It cannot contain `/`. |
| `hostInitInjectLocation` | `'entry' \| 'html'` | `html` | Where host runtime initialization is injected. |
| `moduleParseTimeout` | `number` | `10` | Total module parsing timeout in seconds. |
| `moduleParseIdleTimeout` | `number` | `undefined` | Idle parsing timeout in seconds, reset after every parsed module. |
## Exposes
```ts
exposes: {
'./Button': './src/Button.tsx',
'./ManualCssButton': {
import: './src/ManualCssButton.tsx',
css: {
inject: 'manual',
},
},
}
```
Expose config:
| Field | Type | Description |
| ------------------------ | ----------------------------------------------- | -------------------------------------------------- |
| `import` | `string` | Source module path. |
| `css.inject` | `true \| false \| 'head' \| 'manual' \| 'none'` | CSS injection mode. |
| `dontAppendStylesToHead` | `boolean` | OriginJS-compatible alias for manual CSS handling. |
`manual` CSS exposes write hrefs to the global CSS bucket instead of appending styles to `head`.
Use this for Shadow DOM or migration compatibility.
Do not expose the same file that `index.html` loads as a Vite module entry, such as
`src/main.ts`, `src/main.tsx`, or `src/main.js`. That file should only bootstrap and mount the
remote app. Expose components, routes, or loader modules instead. In dev, the plugin warns when an
`exposes` entry points at the HTML module entry.
## Remotes
String remotes are treated as manifest-first remotes:
```ts
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
}
```
Object remotes support compatibility fields:
```ts
remotes: {
legacy: {
name: 'legacyRemote',
entry: 'https://cdn.example.com/legacy/remoteEntry.js',
type: 'module',
entryGlobalName: 'legacyRemote',
from: 'vite',
format: 'esm',
shareScope: 'default',
},
}
```
Prefer manifest URLs for new applications. Use object remotes for legacy `remoteEntry.js`,
OriginJS migration, or explicit `systemjs`/`var` compatibility.
## Shared
```ts
shared: {
react: {
singleton: true,
strictSingleton: true,
requiredVersion: '^19.2.4',
strictVersion: true,
allowNodeModulesSuffixMatch: true,
},
'react/': {
singleton: true,
requiredVersion: '^19.2.4',
allowNodeModulesSuffixMatch: true,
},
}
```
Shared config fields:
| Field | Type | Description |
| ----------------------------- | ----------------- | ------------------------------------------------------------------------ |
| `name` | `string` | Runtime share name override. |
| `version` | `string` | Provided version override. |
| `shareScope` | `string` | Share scope override. |
| `singleton` | `boolean` | Prefer one provider for this package. |
| `strictSingleton` | `boolean` | Turn singleton conflicts into `MFV-003` errors. |
| `requiredVersion` | `string` | Consumer version range. |
| `strictVersion` | `boolean` | Treat incompatible versions as strict failures. |
| `allowNodeModulesSuffixMatch` | `boolean` | Match pnpm/nested `node_modules` resolved ids by package suffix. |
| `import` | `string \| false` | Local fallback import override, or `false` for host-only shared modules. |
Use trailing slash shared keys for package subpaths, for example `react/` or `@scope/pkg/`.
## Manifest Options
```ts
manifest: {
fileName: 'mf-manifest.json',
filePath: 'federation',
disableAssetsAnalyze: false,
}
```
Manifest builds also emit `mf-stats.json` and `mf-debug.json`. See
[manifest-protocol.md](manifest-protocol.md).
## Dev Options
```ts
dev: {
remoteHmr: true, // opt in to host <-> remote HMR wiring
devtools: true, // default; set false to disable the devtools endpoint/overlay
disableLiveReload: true,
disableHotTypesReload: false,
disableDynamicRemoteTypeHints: false,
}
```
Devtools and DTS type hints are enabled by default in dev. Remote HMR is opt-in and only runs
when `dev.remoteHmr: true`.
The devtools contract is documented in [devtools-runtime-contract.md](devtools-runtime-contract.md).
## DTS Options
```ts
dts: {
generateTypes: {
abortOnError: true,
},
consumeTypes: {
typesOnBuild: true,
abortOnError: true,
},
}
```
In TypeScript projects, DTS support is enabled automatically unless `dts: false` is configured.
See [dts-workflows.md](dts-workflows.md) for complete DTS workflows and defaults.
## Compatibility Options
```ts
compat: {
originjs: true,
virtualFederationShim: true,
}
```
Disable compatibility for strictly manifest-first applications:
```ts
compat: false;
```
See [originjs-migration.md](originjs-migration.md) and
[compatibility-matrix.md](compatibility-matrix.md).
---
# Runtime API
Source: docs/runtime-api.md
# Runtime API Reference
Import production runtime helpers from:
```ts
import {
createFederationInstance,
createFederationManifestPreloadPlan,
createFederationRuntimeScope,
createServerFederationInstance,
loadRemoteFromManifest,
warmFederationRemotes,
} from 'vite-plugin-federation/runtime';
```
The runtime wraps `@module-federation/runtime` and adds manifest-first loading, SSR target
selection, preload helpers, rollout controls, and debug snapshots.
## Instance APIs
### `createFederationInstance(options)`
Creates a browser/runtime host instance and installs shared diagnostics plugins.
```ts
createFederationInstance({
name: 'shell',
remotes: [],
shared: {},
});
```
### `createServerFederationInstance(options)`
Creates a server-side runtime instance with `inBrowser: false` and the Node remote entry loader.
Use this in SSR hosts before loading node-target remotes.
```ts
createServerFederationInstance({
name: 'ssr-shell',
remotes: [],
shared: {},
});
```
## Manifest Fetching
### `fetchFederationManifest(manifestUrl, options?)`
Fetches and validates `mf-manifest.json`.
Important options:
- `cache`, `cacheTtl`, `force`
- `fallbackUrls`
- `staleWhileRevalidate`
- `circuitBreaker`
- `fetch`, `fetchInit`
- `retries`, `retryDelay`, `timeout`
- `hooks`
```ts
const manifest = await fetchFederationManifest(catalogManifestUrl, {
cacheTtl: 30_000,
fallbackUrls: [catalogBackupManifestUrl],
staleWhileRevalidate: true,
timeout: 4_000,
});
```
## Manifest Remote Registration
### `registerManifestRemote(remoteAlias, manifestUrl, options?)`
Fetches a manifest, selects the correct entry for the target, optionally verifies entry integrity,
and registers the remote with the Module Federation runtime.
```ts
await registerManifestRemote('catalog', catalogManifestUrl, {
target: 'web',
shareScope: 'default',
integrity: true,
});
```
For `target: 'node'`, `ssrRemoteEntry` is selected first and `remoteEntry` is used only when no SSR
entry is declared.
### `registerManifestRemotes(remotes, target?)`
Registers multiple manifest remotes concurrently.
```ts
await registerManifestRemotes({
catalog: catalogManifestUrl,
checkout: {
manifestUrl: checkoutManifestUrl,
cacheTtl: 60_000,
},
});
```
## Remote Loading
### `loadRemoteFromManifest(remoteId, manifestUrl, options?)`
Registers the manifest remote if needed and then loads an exposed module.
```ts
const remoteModule = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
cacheTtl: 30_000,
retries: 2,
timeout: 4_000,
});
const CatalogButton = remoteModule.default ?? remoteModule;
```
### `loadRemote(remoteId, options?)`
Delegates to `@module-federation/runtime` and records load success/failure in
`getFederationDebugInfo()`.
### `refreshRemote(remoteIdOrAlias, options?)`
Invalidates manifest registration and re-registers the remote. Browser targets append a timestamp
query to remote entries on forced refresh.
```ts
await refreshRemote('catalog', {
invalidateManifest: true,
timeout: 4_000,
});
```
### `connectRuntimeRemoteHmr(remoteAlias, manifestOrEntryUrl, options?)`
Connects a browser runtime-registered remote to its dev HMR endpoint. Use this when a remote is
registered dynamically with `registerRemotes()` or `loadRemoteFromManifest()`, because the Vite dev
server cannot automatically connect remotes it did not see in `federation({ remotes })`.
```ts
import { connectRuntimeRemoteHmr, registerRemotes } from 'vite-plugin-federation/runtime';
registerRemotes([{ name: 'catalog', entry: catalogManifestUrl, type: 'module' }]);
const hmr = connectRuntimeRemoteHmr('catalog', catalogManifestUrl);
if (import.meta.hot) {
import.meta.hot.dispose(() => hmr.close());
}
```
The connector reads the remote `/__mf_hmr` metadata, opens the remote Vite WebSocket, dispatches
remote update browser events, refreshes remote stylesheet links, and calls `refreshRemote()` for
partial expose updates. The remote must enable `dev.remoteHmr: true`.
## Shared APIs
The runtime re-exports and wraps shared APIs:
- `loadShare`
- `loadShareSync`
- `registerShared`
The wrappers record shared resolution graph entries, selected providers, fallbacks, version
compatibility, and singleton conflicts.
## Preload And Manifest Assets
### `collectFederationManifestExposeAssets(manifestUrl, manifest, exposePath)`
Returns resolved CSS and JS asset URLs for one expose.
### `collectFederationManifestPreloadLinks(manifestUrl, manifest, exposePaths, options?)`
Returns deduped SSR-ready `` descriptors for exposes.
```ts
const links = collectFederationManifestPreloadLinks(manifestUrl, manifest, ['./Button']);
```
Options:
- `includeCss`
- `includeJs`
- `includeAsyncCss`
- `includeAsyncJs`
- `crossorigin`
### `createFederationManifestPreloadPlan(manifestUrl, manifest, routes, options?)`
Builds a route-level preload plan from actual route-to-expose usage and optional manifest
`preload` hints.
```ts
const plan = createFederationManifestPreloadPlan(manifestUrl, manifest, {
'/checkout': ['./Cart', './PaymentForm'],
});
```
Additional options:
- `asyncChunkPolicy`: `'css'` by default, or `'none'`, `'js'`, `'all'`.
- `includeManifestHints`: set `false` to ignore manifest-level `preload` hints.
## Runtime Scopes
### `createFederationRuntimeScope(runtimeKey)`
Creates scoped wrappers for tenant or experiment isolation. The scope injects `runtimeKey` into
manifest fetching, registration, remote loading, refreshing, and warmups.
```ts
const tenant = createFederationRuntimeScope('tenant-a');
await tenant.registerManifestRemote('catalog', manifestUrl, {
shareScope: 'tenant-a',
});
```
`runtimeKey` partitions manifest cache, pending requests, circuit breaker state, registered manifest
remote debug records, and remote load metrics. Use `tenant.getFederationDebugInfo()` for a filtered
debug snapshot. `tenant.connectRuntimeRemoteHmr()` also scopes the refresh options it passes to
`refreshRemote()`.
### `warmFederationRemotes(remotes, options?)`
Registers selected manifest remotes and optionally calls runtime `preloadRemote` for each one.
```ts
await warmFederationRemotes({
catalog: {
manifestUrl: catalogManifestUrl,
preload: { resourceCategory: 'sync' },
},
});
```
Set `preload: false` to warm only the manifest cache and remote registration.
## Integrity
`registerManifestRemote` and `loadRemoteFromManifest` support:
```ts
integrity: true;
integrity: {
mode: 'prefer-integrity' | 'integrity' | 'content-hash' | 'both';
}
```
The runtime verifies `metaData.remoteEntry` or `metaData.ssrRemoteEntry` hashes before
registration. Results are visible in `getFederationDebugInfo().runtime.manifestIntegrityChecks`.
### `verifyFederationManifestAssets(manifestUrl, manifest, options?)`
Verifies annotated expose and preload assets declared in a manifest. String assets are skipped by
default; set `requireIntegrity: true` when every declared asset must include `integrity` or
`contentHash` metadata.
```ts
await verifyFederationManifestAssets(manifestUrl, manifest, {
integrity: { mode: 'both' },
requireIntegrity: true,
});
```
## Hooks And Telemetry
Manifest runtime controls accept:
```ts
hooks: {
manifestFetch(event) {},
remoteRegister(event) {},
remoteLoad(event) {},
remoteRefresh(event) {},
telemetry(event) {},
}
```
Events include `kind`, `stage`, `timestamp`, and contextual fields such as `manifestUrl`,
`sourceUrl`, `remoteId`, `runtimeKey`, `entry`, `target`, `status`, `statusCode`, `durationMs`,
and `error`.
## Debugging
### `getFederationDebugInfo()`
Returns diagnostics, runtime instance snapshot, registered remotes, manifest cache, fetch timeline,
integrity checks, circuit breaker state, remote load metrics, shared providers, and shared
resolution graph.
### `clearFederationRuntimeCaches()`
Clears manifest cache, node module cache, registration requests, debug histories, and shared
resolution history. Use only in tests or controlled runtime resets.
## Re-exports
The runtime also re-exports selected Module Federation runtime helpers:
- `getInstance`
- `getRemoteEntry`
- `getRemoteInfo`
- `loadScript`
- `loadScriptNode`
- `preloadRemote`
- `registerGlobalPlugins`
- `registerPlugins`
- `registerRemotes`
---
# Manifest Protocol
Source: docs/manifest-protocol.md
# Manifest Protocol
The federation manifest protocol is the stable artifact contract for remote builds, host runtime
loading, debug tooling, and external deployment tooling.
## Artifacts
Remote builds emit three related JSON artifacts:
- `mf-manifest.json`: the runtime contract consumed by hosts.
- `mf-stats.json`: the manifest plus build output summaries and diagnostics.
- `mf-debug.json`: plugin options, capability flags, diagnostics, and a manifest snapshot.
JSON Schemas are committed in:
- [mf-manifest.schema.json](schemas/mf-manifest.schema.json)
- [mf-stats.schema.json](schemas/mf-stats.schema.json)
- [mf-debug.schema.json](schemas/mf-debug.schema.json)
Compatibility fixtures are listed in
[Schemas And Fixtures](reference/schemas.md#compatibility-fixtures). The test suite validates the
exact `1.0.0` fixtures against these schemas and checks the runtime compatibility policy for legacy,
same-major, and next-major manifest fixtures.
## Schema Version
All new artifacts include a top-level `schemaVersion` field. The current version is `1.0.0`.
Runtime compatibility rules:
- Missing `schemaVersion` is accepted as a legacy manifest for backward compatibility.
- Same-major versions are accepted, so `1.1.0` can be consumed by a `1.0.0` runtime.
- Different major versions are rejected with `MFV-004`.
- Non-string schema versions are rejected with `MFV-004`.
- Malformed known fields are rejected with `MFV-004`.
- Target-specific entry validation happens when a host registers a manifest remote.
The JSON Schemas describe the exact emitted `1.0.0` shape. Runtime validation is intentionally more
permissive than schema validation so hosts can consume older manifests and same-major future
manifests.
## Manifest Shape
Required top-level fields:
- `schemaVersion`: manifest protocol version.
- `id`: remote application id.
- `name`: remote runtime name.
- `metaData`: remote entry, build, type, public path, and plugin metadata.
- `remotes`: remotes referenced by this build.
Optional fields:
- `shared`: shared modules used by the build. It is omitted when asset analysis is disabled.
- `exposes`: exposed modules and their JS/CSS assets. It is omitted when asset analysis is disabled.
`metaData.remoteEntry` is the browser entry.
`metaData.ssrRemoteEntry` is the Node/SSR entry when available.
Browser hosts prefer `remoteEntry`.
Node hosts prefer `ssrRemoteEntry` and fall back to `remoteEntry` only when necessary.
Generated entries can include `integrity` and `contentHash`.
`integrity` uses the Subresource Integrity format for the emitted entry file.
`contentHash` is a SHA-256 hex digest for deployment tooling.
Generated manifests also include a top-level `release` object and `metaData.buildInfo.releaseId`.
Set `VITE_PLUGIN_FEDERATION_RELEASE_ID` or `MF_RELEASE_ID` in CI when the release id should differ
from the default `buildName:buildVersion` value.
Runtime target validation:
- `target: 'web'` requires a usable `metaData.remoteEntry.name`; `ssrRemoteEntry.name` is accepted
only as a compatibility fallback.
- `target: 'node'` requires a usable `metaData.ssrRemoteEntry.name`; `remoteEntry.name` is accepted
as the legacy fallback when no SSR entry exists.
- If `remoteEntry` or `ssrRemoteEntry` is present, `name`, `path`, and `type` must be strings when
provided.
- A malformed SSR entry is reported as `MFV-006` for Node targets instead of silently falling back
to a browser entry.
## Compatibility Policy
Patch and minor changes within major version `1` must be additive.
Backward-compatible changes:
- Adding optional top-level fields.
- Adding optional fields to `metaData`, entries, `shared`, `remotes`, `exposes`, stats, or debug
artifacts.
- Adding new diagnostic fields or capability flags.
- Adding new enum-like string values when older runtimes can ignore them safely.
Forward-compatible runtime behavior:
- A `1.0.0` runtime accepts missing `schemaVersion` as a legacy manifest.
- A `1.0.0` runtime accepts `1.x.y` manifests when required runtime fields are still usable.
- A `1.0.0` runtime rejects `2.x.y` manifests because required fields or semantics may have changed.
- Exact JSON Schema validation remains stricter than runtime validation. Use it for deployment
pipelines and external tooling.
Breaking changes that require a new major schema version:
- Removing required fields from generated artifacts.
- Changing the type or meaning of an existing field.
- Changing URL resolution semantics for `remoteEntry`, `ssrRemoteEntry`, or asset paths.
- Requiring hosts to use a different target-selection or share-scope behavior.
Schema fixture policy:
- Every schema version should have exact artifact fixtures.
- Legacy and future-version fixtures should remain in the suite to prevent accidental compatibility
regressions.
- A schema major bump must add new fixtures before changing runtime acceptance logic.
## Hosting And Cache Rules
The manifest is the deployment coordination artifact. It should be served with `no-cache`,
`max-age=0, must-revalidate`, or another short revalidation policy. Hosts may cache it in memory
with runtime `cacheTtl`, but CDNs should not make stale manifests difficult to invalidate.
Recommended headers:
```text
mf-manifest.json Cache-Control: no-cache
mf-stats.json Cache-Control: no-cache
mf-debug.json Cache-Control: no-cache
remoteEntry*.js Cache-Control: public, max-age=31536000, immutable
assets/* Cache-Control: public, max-age=31536000, immutable
@mf-types* Cache-Control: public, max-age=31536000, immutable
```
Hosting requirements:
- The manifest URL must be stable for a deployed remote environment.
- Entry and asset files referenced by a manifest must remain available for at least as long as any
host can cache that manifest.
- Cross-origin hosts need CORS for manifest JSON, ESM entries, chunks, CSS, and type artifacts.
- `publicPath: "auto"` keeps asset URLs relative to the manifest URL; an explicit `publicPath`
should be absolute or resolvable from the manifest URL.
- Deployments should publish manifest, stats, debug, entries, assets, and type artifacts
atomically. If atomic publishing is unavailable, publish immutable assets first and the manifest
last.
Rollback guidance:
- Roll back by restoring the previous manifest after confirming its referenced immutable assets are
still hosted.
- Do not mutate files referenced by an already published manifest; publish a new manifest instead.
- Keep `release.id` and `contentHash` in logs so production incidents can identify the exact remote
build.
Hosts should:
- Validate exact artifact shape with the committed JSON Schema in build/deployment pipelines.
- Rely on runtime validation for production loading.
- Treat `MFV-004` schema failures as deployment incompatibilities.
Remote deployments should:
- Publish all three artifacts for each build.
- Keep manifests short-lived or revalidated in caches.
- Serve hashed JS/CSS assets with long immutable caching.
---
# Production Runtime
Source: docs/production-runtime.md
# Production Runtime Guide
This guide defines the production contract for manifest-first federation in this repository.
It focuses on behavior that matters after deployment: artifact traceability, resilient manifest
loading, SSR entry selection, asset discovery, and runtime observability.
## Remote Artifact Contract
Remote applications should keep `manifest` enabled in the federation config. Build output includes:
- `mf-manifest.json`: stable machine-readable contract used by hosts.
- `mf-stats.json`: compatibility-oriented artifact summary for external tooling.
- `mf-debug.json`: plugin diagnostics, normalized options, and manifest generation metadata.
New artifacts include `schemaVersion: "1.0.0"`. See
[manifest-protocol.md](manifest-protocol.md) for JSON Schemas and compatibility rules.
The manifest `metaData.buildInfo` is generated from environment variables at build time. Preferred
variables are checked first, then common CI provider variables are used, and local builds fall back
to the remote name plus `local`.
```bash
VITE_PLUGIN_FEDERATION_BUILD_VERSION=8f3c2d1
VITE_PLUGIN_FEDERATION_BUILD_NAME=main
pnpm --filter example-react-remote build
```
Supported build metadata variables:
- Build version: `VITE_PLUGIN_FEDERATION_BUILD_VERSION`, `MF_BUILD_VERSION`, `GITHUB_SHA`, `VERCEL_GIT_COMMIT_SHA`.
- Build name: `VITE_PLUGIN_FEDERATION_BUILD_NAME`, `MF_BUILD_NAME`, `GITHUB_REF_NAME`, `VERCEL_GIT_COMMIT_REF`.
The emitted `pluginVersion` is injected from the package version during the package build, so
published artifacts can be traced back to the plugin version that generated them.
Generated manifests also include `release.id`, `metaData.buildInfo.releaseId`, and entry-level
`integrity`/`contentHash` metadata for `remoteEntry` and `ssrRemoteEntry`.
That metadata is emitted when the entry files are present in the build output.
Set `VITE_PLUGIN_FEDERATION_RELEASE_ID` or `MF_RELEASE_ID` in CI for a stable application release id;
otherwise it defaults to `buildName:buildVersion`.
Expose assets and manifest preload hints can also declare optional integrity metadata. Use
`verifyFederationManifestAssets()` for annotated asset verification. See
[security.md](security.md) for private manifests, CSP, Trusted Types, and signed manifest guidance.
## Manifest Hosting Rules
Host `mf-manifest.json` with a short cache TTL or `no-cache`, because hosts use it as the deployment
coordination point. Hashed JS and CSS assets referenced by the manifest can use long immutable
caching.
Recommended headers:
```text
mf-manifest.json Cache-Control: no-cache
remoteEntry*.js Cache-Control: public, max-age=31536000, immutable
assets/* Cache-Control: public, max-age=31536000, immutable
```
If the manifest and remote assets are on a different origin, configure CORS for both manifest JSON
and ESM assets. Browser hosts need the manifest, remote entries, shared chunks, CSS, and dynamic
imports to be reachable from the host origin.
## Shared Resolution
Use exact shared keys for package roots and trailing-slash keys for subpaths:
```ts
federation({
name: 'remote',
shared: {
react: {
singleton: true,
requiredVersion: '^19.2.4',
allowNodeModulesSuffixMatch: true,
strictSingleton: true,
},
'react/': {
singleton: true,
requiredVersion: '^19.2.4',
allowNodeModulesSuffixMatch: true,
},
},
});
```
`allowNodeModulesSuffixMatch` lets resolved ids from pnpm, nested `node_modules`, and symlinked
workspace layouts match by the path segment after the final `node_modules/`.
This is useful when a shared package appears at different absolute paths between host and remote
builds.
Generated `mf-stats.json` diagnostics include the flag so production incidents can distinguish
exact, trailing-slash, and suffix-enabled shared rules.
Runtime hosts created through `vite-plugin-federation/runtime` also install a shared diagnostics
plugin.
The runtime records registered shared providers, the active share scope, each async/sync `loadShare`
decision, candidate versions, selected provider, fallback mode, and `MFV-003` diagnostics for shared
misses, fallback selection, and semver range mismatches.
Each graph entry includes `versionSatisfied` when a selected provider can be compared to the
configured `requiredVersion`.
Singleton entries include rejected candidate versions when multiple providers are present.
Generated local providers and internal `loadShare` requests also carry `sourcePath` and
`resolvedImportSource` annotations.
Those annotations let `getFederationDebugInfo()` tie a shared decision back to the exact local import
id or resolved pnpm/workspace file path.
By default singleton conflicts are diagnostics only.
Set `strictSingleton: true` on a shared package to upgrade those conflicts to an `MFV-003` runtime
error while still recording the selected and rejected providers in `getFederationDebugInfo()`.
For generated shared wrappers, `import: false` is treated as host-only.
If the host does not provide the package, the wrapper throws an explicit error that includes
`requiredVersion` and `strictVersion`.
Non-host-only wrappers use the local prebuild source as a fallback when the runtime cannot find any
registered provider.
## Browser Host Loading
Use `loadRemoteFromManifest` when one remote module is needed immediately. It fetches the manifest,
registers the correct remote entry, and delegates loading to the Module Federation runtime.
```ts
import { createFederationInstance, loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
createFederationInstance({
name: 'web-host',
remotes: [],
shared: {},
});
const catalogManifestUrl = 'https://cdn.example.com/catalog/mf-manifest.json';
const remoteModule = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
cacheTtl: 60_000,
retries: 2,
timeout: 5_000,
});
export const CatalogButton = remoteModule.default ?? remoteModule;
```
Use `registerManifestRemotes` when the host wants to register several remotes during boot and load
modules later with `loadRemote`.
```ts
import {
createFederationInstance,
loadRemote,
registerManifestRemotes,
} from 'vite-plugin-federation/runtime';
createFederationInstance({ name: 'shell', remotes: [], shared: {} });
await registerManifestRemotes({
catalog: {
manifestUrl: 'https://cdn.example.com/catalog/mf-manifest.json',
cacheTtl: 60_000,
retries: 2,
timeout: 5_000,
},
checkout: 'https://cdn.example.com/checkout/mf-manifest.json',
});
const checkout = await loadRemote('checkout/App');
```
## Manifest Fetch Controls
`fetchFederationManifest`, `registerManifestRemote`, `registerManifestRemotes`, and `loadRemoteFromManifest`
share the same production fetch controls.
| Option | Default | Use case |
| ---------------------- | ------------------ | ------------------------------------------------------------------------------- |
| `cache` | `true` | Set `false` when every call must hit the manifest URL. |
| `cacheTtl` | no expiry | Expire cached manifests after a fixed number of milliseconds. |
| `fallbackUrls` | `[]` | Try alternate manifest origins when the primary URL fails. |
| `staleWhileRevalidate` | `false` | Return an expired cached manifest immediately and refresh it in the background. |
| `circuitBreaker` | `false` | Stop repeatedly failing manifest requests for a cooldown window. |
| `force` | `false` | Drop cached and pending manifest work before fetching/registering again. |
| `fetch` | `globalThis.fetch` | Provide a platform fetch in tests, legacy Node, or custom runtimes. |
| `fetchInit` | `undefined` | Pass credentials, headers, or request mode to manifest fetches. |
| `hooks` | `undefined` | Observe manifest fetch, register, load, refresh, and telemetry events. |
| `retries` | `0` | Retry network failures, timeouts, `408`, `429`, and `5xx` responses. |
| `retryDelay` | capped backoff | Use a fixed delay or function for retry pacing. |
| `timeout` | no timeout | Abort and reject slow manifest requests. |
Concurrent manifest fetches for the same URL are collapsed into one request. Concurrent manifest
registrations for the same target, alias, and manifest URL are also collapsed.
Example with credentials and custom retry pacing:
```ts
await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
cacheTtl: 30_000,
circuitBreaker: {
cooldownMs: 30_000,
failureThreshold: 3,
},
fallbackUrls: ['https://cdn-backup.example.com/catalog/mf-manifest.json'],
fetchInit: {
credentials: 'include',
headers: {
'x-host-version': import.meta.env.VITE_APP_VERSION,
},
},
hooks: {
telemetry(event) {
console.debug('[federation]', event.kind, event.stage, event.durationMs);
},
},
retries: 3,
retryDelay: (attempt) => attempt * 250,
staleWhileRevalidate: true,
timeout: 4_000,
});
```
When a fallback manifest URL succeeds, relative remote entries and assets are resolved from the
manifest origin that won.
`getFederationDebugInfo()` exposes both the configured `manifestUrl` and the effective `sourceUrl`
in manifest cache and registered manifest remote snapshots.
`circuitBreaker: true` uses `failureThreshold: 3` and `cooldownMs: 30000`. During the open window,
the runtime rejects immediately with `MFV-004` and records a `circuit-open` manifest fetch event.
Hook events use `{ kind, stage, timestamp }` plus contextual fields such as `manifestUrl`,
`sourceUrl`, `remoteId`, `entry`, `target`, `status`, `statusCode`, `durationMs`, and `error`.
Available `kind` values are `manifest-fetch`, `remote-register`, `remote-load`, and `remote-refresh`.
Stages are `before`, `after`, and `error`.
## Refresh And Rollout Behavior
Use `refreshRemote` when a running host needs to pick up a newly deployed manifest or remote entry
without recreating the entire runtime instance.
```ts
import { refreshRemote } from 'vite-plugin-federation/runtime';
await refreshRemote('catalog', {
cacheTtl: 30_000,
force: true,
timeout: 4_000,
});
```
For browser targets, forced refreshes append a timestamp query to the remote entry URL. This avoids
stale remote entry caches while keeping immutable asset caching for hashed chunks.
For route-level fallbacks, wrap non-critical remote loads at the route boundary and log the runtime
snapshot. Keep shell-critical remotes fail-fast during boot.
```ts
import { getFederationDebugInfo, loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
export async function loadCatalogRoute() {
try {
return await loadRemoteFromManifest('catalog/App', catalogManifestUrl, {
cacheTtl: 30_000,
circuitBreaker: true,
fallbackUrls: [catalogBackupManifestUrl],
retries: 2,
staleWhileRevalidate: true,
timeout: 4_000,
});
} catch (error) {
console.error('[federation] catalog route fallback', error, getFederationDebugInfo());
return import('./CatalogUnavailable');
}
}
```
## SSR And Node Hosts
Server hosts should create a server runtime instance and pass `target: 'node'` for manifest loading.
The runtime selects `ssrRemoteEntry` first and falls back to `remoteEntry` only if the SSR entry is
absent.
```ts
import {
createServerFederationInstance,
fetchFederationManifest,
loadRemoteFromManifest,
} from 'vite-plugin-federation/runtime';
createServerFederationInstance({
name: 'ssr-host',
remotes: [],
shared: {},
});
const manifestUrl = process.env.CATALOG_MANIFEST_URL!;
const [manifest, remoteModule] = await Promise.all([
fetchFederationManifest(manifestUrl, {
cacheTtl: 30_000,
retries: 2,
timeout: 5_000,
}),
loadRemoteFromManifest('catalog/Button', manifestUrl, {
cacheTtl: 30_000,
retries: 2,
target: 'node',
timeout: 5_000,
}),
]);
```
Node SSR currently relies on the VM module loader for remote ESM evaluation.
Run SSR examples with `--experimental-vm-modules` until the Node loading strategy is replaced or no
longer needs the flag.
The VM module loader is treated as a temporary Node bridge hidden behind
`createServerFederationInstance()`.
Keep application code on the runtime APIs above so the loader can move to native Node
import/materialized server chunks later without changing host code.
Production SSR hosts should keep the server and browser federation paths explicitly comparable:
- The server should inject the manifest URL it used into the HTML, and the browser should hydrate
with the same value.
- The server target selects `metaData.ssrRemoteEntry` first; the browser target selects
`metaData.remoteEntry` first. The runtime debug snapshot records the selected `entry`, `target`,
`manifestUrl`, and `shareScope` under `registeredManifestRemotes`.
- React, Vue, or other framework runtimes should use the same share scope and compatible singleton
versions on both sides. The React SSR example fails hydration early if the manifest URL, remote id,
share scope, or React version diverges.
- The host SSR build should leave `vite-plugin-federation/runtime` external so Node uses the package
runtime entry and a single debug state. The React remote example keeps React SSR packages under
`ssr.noExternal` and validates the generated `ssrRemoteEntry` through the SSR e2e test.
When SSR remote loading fails, `getFederationDebugInfo().runtime.lastLoadError` includes the error
code, `remoteId`, `remoteAlias`, selected `entry`, `manifestUrl`, `target`, and `shareScope` when
that context is available.
Node entry loader failures are wrapped as `MFV-004` with guidance to check server-side fetch
reachability, ESM SSR output, and the `--experimental-vm-modules` flag.
## SSR Asset Collection
SSR HTML should include the CSS and synchronous JS assets declared for the rendered expose. Use
`collectFederationManifestPreloadLinks` instead of duplicating manifest URL resolution or link
deduplication.
```ts
import {
collectFederationManifestPreloadLinks,
fetchFederationManifest,
} from 'vite-plugin-federation/runtime';
const manifest = await fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
const links = collectFederationManifestPreloadLinks(manifestUrl, manifest, './Button');
const linkTags = links.map((link) => {
if (link.rel === 'stylesheet') {
return ``;
}
const crossorigin = link.crossorigin ? ` crossorigin="${link.crossorigin}"` : '';
return ``;
});
```
The helper resolves relative asset paths against `metaData.publicPath` when it is set and not
`auto`; otherwise it resolves relative to the manifest URL. By default it includes sync CSS, async
CSS, and sync JS. Set `includeAsyncJs: true` when the host wants to eagerly modulepreload async JS
chunks as well. Module preload links default to `crossorigin="anonymous"`; set
`crossorigin: false` to omit the attribute.
For route-aware shells, use `createFederationManifestPreloadPlan` to generate a deduped plan from
route-to-expose usage plus optional manifest `preload` hints.
Use `warmFederationRemotes` to register and preload selected remotes before a critical navigation.
See [preload-performance.md](preload-performance.md) for the full API and performance budget
guidance.
If browser and remote assets are served from different origins, also account for root-relative
dependencies emitted inside remote chunks.
Either configure the remote so those preloads resolve to the remote origin, or proxy the relevant
asset paths from the SSR host.
The React SSR example proxies `/assets/*` to the origin that serves the remote manifest so hydration
can reuse the same manifest URL without asset 404s.
## Observability
Use `getFederationDebugInfo()` to inspect the active runtime. The snapshot includes:
- Last remote load and last load error.
- Registered runtime remotes and registered manifest remotes.
- Registered shared providers, active share scope entries, and shared resolution graph entries.
- Manifest cache entries with `manifestUrl`, `name`, `fetchedAt`, and `expiresAt`.
- Manifest fetch history with `success`, `retry`, `failure`, and `cache-hit` entries.
- Manifest circuit breaker state and fallback `sourceUrl` when configured.
- Remote load metrics with registration, load, and total durations.
- Pending manifest requests and pending remote registrations.
- Package-level diagnostics emitted through the plugin logger.
```ts
import { getFederationDebugInfo } from 'vite-plugin-federation/runtime';
console.table(getFederationDebugInfo().runtime.manifestFetches);
console.table(getFederationDebugInfo().runtime.remoteLoadMetrics);
console.table(getFederationDebugInfo().runtime.sharedResolutionGraph);
```
Browser runtimes also dispatch `vite-plugin-federation:debug` events after runtime mutations.
The devtools sidecar consumes the same event stream.
The stable browser global contract is documented in [devtools-runtime-contract.md](devtools-runtime-contract.md).
## Error Handling
Production hosts should handle these runtime error categories explicitly:
- `MFV-003`: shared package miss, fallback selection, or strict shared resolution failure.
- `MFV-004`: manifest fetch, manifest validation, or runtime remote load failure.
- `MFV-005`: requested expose is missing from a manifest during asset collection.
- `MFV-006`: Node target cannot find a usable SSR remote entry.
Recommended host behavior:
- Render a route-level fallback for failed non-critical remotes.
- Fail fast for shell-critical remotes during boot.
- Log `getFederationDebugInfo()` with the application release id when remote loading fails.
- Use `retries` and `timeout` for startup resilience, not as a substitute for remote health checks.
## Deployment Checklist
- Publish `mf-manifest.json`, `mf-stats.json`, and `mf-debug.json` with each remote deployment.
- Set build metadata variables in CI so manifests can be traced to commits or release ids.
- Serve manifests with short or revalidated caching; serve hashed assets with immutable caching.
- Verify CORS for manifest JSON, remote entries, CSS, and dynamic chunks.
- Use `cacheTtl`, `timeout`, and bounded `retries` in production hosts.
- Use `target: 'node'` for SSR loads and include remote CSS/modulepreload links in rendered HTML.
- Capture `getFederationDebugInfo()` in remote load error logs.
---
# Preload And Performance
Source: docs/preload-performance.md
# Federation Preload And Performance
This guide covers the production APIs for route-aware preload planning, remote warming, and
runtime load metrics.
## Route-Level Preload Plans
Use `createFederationManifestPreloadPlan` when the host knows which exposes are needed for a route.
The helper resolves manifest asset paths, dedupes links across routes, and applies an async chunk
policy.
```ts
import { createFederationManifestPreloadPlan } from 'vite-plugin-federation/runtime';
const plan = createFederationManifestPreloadPlan(manifestUrl, manifest, {
'/checkout': ['./Cart', './PaymentForm'],
'/account': './AccountHome',
});
```
The returned plan contains:
- `routes`: one entry per route with the route-local preload links.
- `links`: all deduped links across every route.
- `remoteName` and `manifestUrl`: useful for logging and SSR diagnostics.
The async chunk policy defaults to `css`: sync JS, sync CSS, and async CSS are preloaded, while async
JS is left lazy. Set `asyncChunkPolicy: 'all'` for critical routes, `js` for JS-only warmups, or
`none` for only sync assets.
```ts
createFederationManifestPreloadPlan(manifestUrl, manifest, routes, {
asyncChunkPolicy: 'all',
crossorigin: 'anonymous',
});
```
## Manifest-Level Preload Hints
Manifests may include optional preload hints for assets that are not tied directly to a single
expose, such as shared runtime chunks or route-critical CSS.
```json
{
"preload": {
"assets": {
"js": ["assets/remote-runtime.js"]
},
"routes": [
{
"route": "/checkout",
"expose": "./Cart",
"assets": {
"css": ["assets/checkout-critical.css"]
}
}
]
}
}
```
Hints are additive. They do not replace expose assets from `exposes[].assets`, and duplicate links
are removed by URL and relation.
## Host-Side Remote Warming
Use `warmFederationRemotes` to register selected manifest remotes and ask the Module Federation
runtime to preload them before user navigation reaches a critical route.
```ts
import { warmFederationRemotes } from 'vite-plugin-federation/runtime';
await warmFederationRemotes({
catalog: {
manifestUrl: catalogManifestUrl,
cacheTtl: 30_000,
preload: {
resourceCategory: 'sync',
},
},
});
```
Set `preload: false` when you only want registration and manifest cache warming.
## Runtime Metrics
`getFederationDebugInfo().runtime.remoteLoadMetrics` records recent remote module load waterfalls.
Entries include:
- `remoteId`, `remoteAlias`, `remoteName`, `manifestUrl`, `sourceUrl`, `entry`, and `target`.
- `registrationDurationMs`, `loadDurationMs`, and total `durationMs`.
- `phase`, `status`, `timestamp`, and `error` for failures.
Use this data in devtools, logs, or synthetic checks to identify slow manifests, CDN propagation
issues, remote entry startup cost, or over-eager preload policies.
## Performance Budget Guidance
Recommended production checks:
- Manifest fetch should hit cache for repeated route transitions.
- Route preload plans should avoid `asyncChunkPolicy: 'all'` outside critical above-the-fold routes.
- Warm only remotes likely to be used by the next navigation step.
- Alert on repeated `remoteLoadMetrics.status === 'failure'` or high `durationMs` for critical
routes.
---
# Multi-Tenant Runtime Isolation
Source: docs/multi-tenant.md
# Multi-Tenant Runtime Isolation
Use `runtimeKey` when one shell hosts multiple tenants, experiments, or isolated runtime scopes on
the same page. The key partitions manifest caches, pending manifest requests, circuit breaker state,
registered manifest remote debug records, and remote load metrics.
## Scoped Runtime API
`createFederationRuntimeScope(runtimeKey)` returns wrappers that automatically pass the same
`runtimeKey` to manifest runtime APIs.
```ts
import { createFederationRuntimeScope } from 'vite-plugin-federation/runtime';
const tenantA = createFederationRuntimeScope('tenant-a');
const tenantB = createFederationRuntimeScope('tenant-b');
await tenantA.registerManifestRemote('catalog', tenantAManifestUrl, {
shareScope: 'tenant-a',
});
await tenantB.registerManifestRemote('catalog', tenantBManifestUrl, {
shareScope: 'tenant-b',
});
```
The scoped object exposes:
- `fetchFederationManifest`
- `registerManifestRemote`
- `loadRemoteFromManifest`
- `refreshRemote`
- `warmFederationRemotes`
- `getFederationDebugInfo`
## Cache And Circuit Breaker Isolation
The same manifest URL can be fetched independently by multiple runtime keys:
```ts
await tenantA.fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
await tenantB.fetchFederationManifest(manifestUrl, { cacheTtl: 30_000 });
```
Each tenant gets its own cache entry and circuit breaker state, so one tenant's failing rollout does
not poison another tenant's manifest cache.
## Share Scopes And Aliases
Use tenant-specific `shareScope` values when tenants may provide different singleton/shared versions.
`runtimeKey` isolates plugin-side manifest state and diagnostics; the underlying Module Federation
runtime still needs remote aliases to be unambiguous for actual module requests. If two tenants must
load different implementations at the same time, prefer tenant-specific request aliases such as
`tenantACatalog` and `tenantBCatalog`, or route requests through a tenant-aware runtime scope.
## Debugging
Global debug snapshots include `runtimeKey` on manifest cache entries, fetch history, circuit
breakers, registered manifest remotes, last load errors, and remote load metrics.
```ts
console.table(tenantA.getFederationDebugInfo().runtime.manifestCache);
console.table(tenantA.getFederationDebugInfo().runtime.remoteLoadMetrics);
```
Use scoped debug snapshots when sending tenant-specific telemetry so unrelated tenants do not leak
into logs.
---
# Security
Source: docs/security.md
# Federation Security And Supply Chain
Security controls are opt-in. The default path stays simple, but production shells can add stronger
verification for enterprise or multi-tenant deployments.
## Integrity Metadata
Generated manifests include `integrity` and `contentHash` for remote entries. The manifest schema
also accepts optional integrity metadata for expose assets and preload hints:
```json
{
"exposes": [
{
"name": "Button",
"path": "./Button",
"assets": {
"js": {
"sync": [
{
"path": "assets",
"name": "Button.js",
"integrity": "sha384-...",
"contentHash": "..."
}
],
"async": []
},
"css": {
"sync": [],
"async": []
}
}
}
]
}
```
String asset entries remain valid for compatibility. Use object asset entries when the producing
pipeline can emit per-asset hashes.
## Runtime Verification
Remote entry verification is available during registration:
```ts
await registerManifestRemote('catalog', catalogManifestUrl, {
integrity: { mode: 'prefer-integrity' },
});
```
Use `verifyFederationManifestAssets()` to verify annotated expose/preload assets before warming a
critical route or during synthetic checks:
```ts
const manifest = await fetchFederationManifest(catalogManifestUrl, { cacheTtl: 30_000 });
await verifyFederationManifestAssets(catalogManifestUrl, manifest, {
integrity: { mode: 'both' },
requireIntegrity: false,
});
```
Modes:
- `prefer-integrity`: use SRI when present, otherwise `contentHash`.
- `integrity`: require SRI.
- `content-hash`: require SHA-256 content hash.
- `both`: require both values to match.
Set `requireIntegrity: true` when every manifest-declared expose/preload asset must carry metadata.
Without it, unannotated string assets are skipped so existing manifests keep working.
## Private Manifests
Authenticated manifests should use `fetchInit` or a custom `fetch` implementation:
```ts
await registerManifestRemote('tenantCatalog', manifestUrl, {
fetchInit: {
credentials: 'include',
headers: {
Authorization: `Bearer ${token}`,
},
},
});
```
Keep private manifests cacheable only by the intended tenant or user. If a CDN is involved, vary the
cache by authorization context and avoid sharing tenant-specific manifests through public cache keys.
## SSR Manifest URLs
Never accept tenant- or user-controlled manifest URLs directly in Node SSR. A manifest remote runs
code in the server runtime, so request-level URL overrides can become SSRF or remote code execution.
The React SSR example keeps query-string manifest overrides disabled by default. Its E2E suite
enables them with `REACT_REMOTE_MANIFEST_QUERY_OVERRIDES=1` and still restricts override URLs to the
configured manifest origins. Add trusted origins through `REACT_REMOTE_MANIFEST_ALLOWED_ORIGINS` only
for controlled test or operator workflows.
## CORS And CSP
Recommended browser policy:
- Serve manifests with explicit `Access-Control-Allow-Origin` for trusted shell origins.
- Serve module remote entries with CORS headers that allow `crossorigin="anonymous"` modulepreload.
- Prefer `script-src` and `connect-src` allowlists for remote manifest and asset origins.
- If remote origins are tenant-specific, generate CSP per tenant rather than using a broad wildcard.
- Avoid `unsafe-eval`; legacy `var` and some third-party SystemJS flows may require looser policy,
so isolate them from the manifest-first path when possible.
## Trusted Types And Script Policies
Manifest-first ESM remotes primarily use module loading instead of direct string script injection.
If the host uses Trusted Types, create a narrow policy for remote URLs and validate the URL against
the same manifest origin allowlist used by CSP. Do not pass tenant-controlled strings directly into
script creation hooks.
## Signed Manifest Design
For high-assurance deployments, sign the manifest body outside this plugin and verify it in a custom
`fetch` wrapper before returning the `Response` to the runtime. Built-in signature verification is
not part of the beta runtime surface; this keeps the default loader key-management-neutral. The
recommended shape is detached:
- Manifest body stays valid JSON and schema-compatible.
- Signature, key id, and algorithm are delivered by headers or a sidecar file.
- The verifier checks signature, schema version, release id, and allowed remote origin before runtime
registration.
This keeps signing independent from the runtime loader and avoids coupling default users to a
specific key-management system.
---
# Dev Remote HMR
Source: docs/dev-hmr.md
# Dev Remote HMR
Remote HMR is enabled with:
```ts
federation({
name: 'remote',
dev: {
remoteHmr: true,
},
});
```
Hosts that configure remotes automatically inject a lightweight HMR client during Vite dev server
usage.
## Configured vs Runtime Remotes
The built-in host bridge connects remotes declared in `federation({ remotes })`, because the dev
server can read those URLs during startup.
Remotes registered later with runtime APIs such as `registerRemotes()` or
`loadRemoteFromManifest()` are only known in the browser. For those dynamic remotes, connect the
runtime-side HMR client after registration:
```ts
import { connectRuntimeRemoteHmr, registerRemotes } from 'vite-plugin-federation/runtime';
registerRemotes([{ name: 'catalog', entry: catalogManifestUrl, type: 'module' }]);
const hmr = connectRuntimeRemoteHmr('catalog', catalogManifestUrl);
if (import.meta.hot) {
import.meta.hot.dispose(() => hmr.close());
}
```
`connectRuntimeRemoteHmr()` reads the remote `/__mf_hmr` metadata, opens the remote Vite WebSocket,
dispatches the same browser events listed below, refreshes remote stylesheet links, and calls
`refreshRemote()` for partial expose updates. The remote still needs `dev.remoteHmr: true`.
Framework-native HMR can still work without the federation bridge when the remote dev server serves
modules that import its own Vite client. This is framework-specific and depends on the framework HMR
runtime being able to find the already mounted component instances. The runtime connector is still
useful for dynamic remote cache invalidation, stylesheet refreshes, type events, and full-reload
fallback decisions.
## Vue Runtime HMR Caveat
`connectRuntimeRemoteHmr()` keeps the federation runtime synchronized with a remote registered in
the browser. It does not merge framework-native HMR runtimes.
Vue dev mode exposes its HMR API on `globalThis.__VUE_HMR_RUNTIME__`, while the component record map
used by that API lives inside the evaluated Vue runtime module. If a host and a remote on different
dev origins both evaluate separate Vue dev runtimes, the later runtime can replace the global
`__VUE_HMR_RUNTIME__` reference. In that state, remote update events may still arrive, but Vue can
miss already mounted component records because they belong to the other runtime instance. A common
symptom is that a remote SFC update only appears after the component is unmounted and mounted again.
Prefer these options for Vue dev setups:
- Share `vue` and `vue/*` as singletons between host and remote.
- Avoid evaluating a second Vue dev runtime in the host page when possible.
- Use `connectRuntimeRemoteHmr()` for remotes added through `registerRemotes()` or
`loadRemoteFromManifest()`.
- Expose components, route modules, or loaders instead of the HTML bootstrap entry such as
`src/main.ts` or `src/main.tsx`.
For vendor-style dev flows where a remote is attached to a running host at runtime and a second Vue
dev runtime is unavoidable, a host can use a dev-only workaround that preserves the host Vue HMR
runtime after Vue is imported and restores it after loading the remote:
```ts
import { loadRemote } from 'vite-plugin-federation/runtime';
import { createApp } from 'vue';
import App from './App.vue';
const hostVueHmrRuntime = import.meta.env.DEV ? (globalThis as any).__VUE_HMR_RUNTIME__ : undefined;
createApp(App).mount('#app');
async function loadVendorRemote() {
const mod = await loadRemote('vendor/Button');
if (import.meta.env.DEV && hostVueHmrRuntime) {
(globalThis as any).__VUE_HMR_RUNTIME__ = hostVueHmrRuntime;
}
return mod;
}
```
Treat this as a development integration workaround, not a production API. Test the exact host and
remote combination because framework-native HMR behavior is owned by the framework runtime.
## Update Strategies
Remote updates are classified before they are broadcast to hosts:
| Strategy | Action | Trigger |
| --------- | ---------------- | ---------------------------------------------------------------------------------- |
| `partial` | `partial-reload` | A changed file is a configured expose entry or is inside an expose importer graph. |
| `style` | `style-update` | A changed stylesheet belongs to a known expose. |
| `types` | `types-update` | A declaration file changed. |
| `full` | `full-reload` | The file is outside the known expose graph or partial reload cannot be applied. |
Every payload includes `reason`, `strategy`, `batchId`, and a small `dependencyGraph` object with
the matched expose, changed file, importer preview, and match mode.
## Batching
Remote file events are batched with a short debounce window. Duplicate changes to the same file are
collapsed before the remote sends `mf:remote-update` messages. This avoids a burst of host reloads
when a framework or compiler writes several files during one save.
## Host Behavior
For `partial-reload`, the host tries to reload the matching virtual remote module with Vite
`server.reloadModule()`. This keeps application state when the framework HMR boundary supports it,
for example React Fast Refresh-compatible component exposes.
Fallback rules:
- If `server.reloadModule()` is unavailable, the host emits a diagnostic update and performs a full
reload.
- If no matching host virtual module is found, the host emits a diagnostic update and performs a
full reload.
- Style updates refresh matching remote stylesheet links. If no stylesheet can be refreshed, the
browser reloads.
- Type updates are forwarded without forcing a page reload.
## Browser Events
Hosts dispatch these browser events for devtools and custom app integrations:
- `vite-plugin-federation:remote-update`
- `vite-plugin-federation:remote-expose-update`
- `vite-plugin-federation:remote-style-update`
- `vite-plugin-federation:remote-types-update`
The built-in devtools overlay records the same events and displays the update reason.
---
# DTS Workflows
Source: docs/dts-workflows.md
# Federation DTS Workflows
This document describes the production type-generation and type-consumption contract for
`vite-plugin-federation`.
## Artifacts
Remote TypeScript builds can emit:
- `@mf-types.zip`: zipped declarations consumed by hosts.
- `@mf-types.d.ts`: API-level declaration hints for the remote.
- `@mf-types/`: the unpacked declaration folder in the remote build output.
- `mf-manifest.json`: includes `metaData.types.path`, `metaData.types.name`, and
`metaData.types.api` so hosts can discover type artifacts from the manifest URL.
The default artifact names are `@mf-types.zip` and `@mf-types.d.ts`. A remote can change the folder
name with `dts.generateTypes.typesFolder`; the manifest metadata follows that setting.
## Recommended Remote Config
```ts
import { defineConfig } from 'vite';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'catalogRemote',
filename: 'remoteEntry.js',
manifest: true,
exposes: {
'./api': './src/api.ts',
},
dts: {
generateTypes: {
abortOnError: true,
generateAPITypes: true,
typesFolder: '@mf-types',
},
consumeTypes: false,
},
}),
],
});
```
Production guidance:
- Use `abortOnError: true` for remotes so CI fails when declarations cannot be generated.
- Keep `generateAPITypes: true` enabled for host tooling and dynamic runtime type APIs.
- Publish `@mf-types.zip`, `@mf-types.d.ts`, and `mf-manifest.json` atomically with the JS assets.
- Serve type artifacts with the same CORS policy as manifests and remote entries.
## Recommended Host Config
Manifest remotes can derive type URLs automatically when the remote entry is an HTTP(S)
`mf-manifest.json` URL and the manifest includes `metaData.types`.
```ts
import { defineConfig } from 'vite';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'catalogHost',
remotes: {
catalogRemote: 'https://cdn.example.com/catalog/mf-manifest.json',
},
dts: {
generateTypes: false,
consumeTypes: {
abortOnError: true,
consumeAPITypes: true,
typesOnBuild: true,
},
},
}),
],
});
```
Explicit `remoteTypeUrls` are still supported and take precedence over manifest-derived URLs:
```ts
dts: {
generateTypes: false,
consumeTypes: {
abortOnError: true,
consumeAPITypes: true,
typesOnBuild: true,
remoteTypeUrls: {
catalogRemote: {
alias: 'catalogRemote',
api: 'https://types.example.com/catalog/@mf-types.d.ts',
zip: 'https://types.example.com/catalog/@mf-types.zip',
},
},
},
}
```
For static remote imports, add a TypeScript path mapping to the consumed declaration folder:
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"catalogRemote/*": ["@mf-types/catalogRemote/*"]
}
},
"include": ["src", "@mf-types/**/*.d.ts"]
}
```
Production guidance:
- Use `typesOnBuild: true` in CI so hosts fail before deployment if remote types are unavailable.
- Use `abortOnError: true` for build-time host consumption failures.
- Keep `consumeAPITypes: true` enabled for typed `loadRemote` APIs.
- If `displayErrorInTerminal: false` is set, logs are suppressed, but `abortOnError: true` still
fails the build.
## Dev Behavior
In dev, the DTS integration can start the upstream DTS worker and dynamic type hints plugin. The
plugin also supports hot type reload events through the dev remote HMR channel.
Remote dev manifests publish live DTS artifacts from `dist/.dev-server.zip` and
`dist/.dev-server.d.ts`, so hosts consume the current dev-generated declarations instead of stale
build-time `@mf-types` outputs during local development.
Relevant dev options:
- `dev.disableDynamicRemoteTypeHints`: disables dynamic remote type hints injection.
- `dev.disableHotTypesReload`: disables hot type reload events.
- `dev.disableLiveReload`: disables broader dev worker live reload behavior.
Use build-time DTS checks in CI even when dev type hints are enabled. Dev type hints improve local
feedback, but CI should still validate generated artifacts and host consumption.
## Repository Verification
This repository includes two focused examples:
- `examples/dts-remote`: proves remote generation creates `@mf-types.zip`, `@mf-types.d.ts`,
manifest metadata, and expose declarations.
- `examples/dts-host`: proves a host can derive type URLs from the remote manifest, consume those
declarations during build, and type-check `dtsRemote/answer`.
- `packages/vite-plugin-federation/e2e/dts-dev-hot-sync.mjs`: proves a host dev server receives
live federated type updates after a remote API change without restarting the host.
Run the full DTS verification:
```bash
pnpm examples:dts:build
```
---
# DevTools Runtime Contract
Source: docs/devtools-runtime-contract.md
# DevTools Runtime Contract
This document defines the browser global used by the optional development overlay and by external
debug tooling.
## Global
When devtools are enabled, the plugin creates or reuses:
```ts
window.__VITE_PLUGIN_FEDERATION_DEVTOOLS__;
```
The current contract version is `1.0.0`.
```ts
interface FederationDevtoolsGlobal {
activeApp?: string;
apps: Record;
contractVersion: '1.0.0';
eventLimit: number;
events: FederationDevtoolsEvent[];
exportSnapshot: () => FederationDevtoolsSnapshot;
copySnapshot?: () => Promise;
lastUpdatedAt?: string;
runtime?: ReturnType;
}
```
The runtime and overlay both retain at most `eventLimit` events. The default is `50`.
## App Payload
The dev server endpoint and injected bootstrap register one payload per configured federation app.
```ts
interface FederationDevtoolsAppPayload {
capabilities: {
copySnapshot: boolean;
manifestTimeline: boolean;
preloadGraph: boolean;
remoteRegistry: boolean;
runtimeErrors: boolean;
sharedGraph: boolean;
};
contractVersion: '1.0.0';
debugUrl: string | null;
endpoint: string;
eventLimit: number;
exposes: Array<{
cssMode: 'head' | 'manual' | string;
exposeName: string;
import: string | string[];
}>;
manifestUrl: string | null;
name: string;
remoteHmrUrl: string | null;
remotes: Array<{
alias: string;
entry: string;
name: string;
type: string;
}>;
role: 'host' | 'remote' | 'hybrid' | 'isolated';
shared: string[];
}
```
The same payload is served as JSON from `//__mf_devtools`.
## Runtime Events
The runtime dispatches `vite-plugin-federation:debug` after relevant state changes. Event details
have this shape:
```ts
interface FederationRuntimeDebugEvent {
event:
| 'create-instance'
| 'register-remotes'
| 'register-shared'
| 'shared-resolution'
| 'load-remote'
| 'refresh-remote'
| 'load-error'
| 'manifest-fetched'
| 'manifest-integrity'
| 'manifest-registered'
| 'clear-caches';
snapshot: ReturnType;
timestamp: string;
}
```
The overlay also listens to:
- `vite-plugin-federation:remote-update`
- `vite-plugin-federation:remote-style-update`
- `vite-plugin-federation:remote-types-update`
## Snapshot Export
`exportSnapshot()` returns a JSON-safe object containing apps, recent events, last runtime snapshot,
contract version, and update timestamp. `copySnapshot()` writes the same object to
`navigator.clipboard` when available and resolves with the JSON string.
External tooling should prefer `exportSnapshot()` over reading individual fields one by one. The
top-level field names are the stable contract; nested runtime data follows
`getFederationDebugInfo()`.
## Overlay Panels
The built-in overlay renders these optional panels from the runtime snapshot:
- Runtime errors from `runtime.lastLoadError`.
- Registered runtime and manifest remotes.
- Manifest fetch timeline from `runtime.manifestFetches`.
- Shared resolution graph from `runtime.sharedResolutionGraph`.
- Last preload call from `runtime.lastPreloadRemote`.
- Recent devtools events.
The overlay is only injected during Vite dev server usage when `dev.devtools !== false`. It is not
included in production builds.
---
# Compiler Adapter Notes
Source: docs/compiler-adapter.md
# Compiler Adapter Notes
The plugin supports Vite/Rollup and Rolldown-backed Vite versions through a small set of adapter
rules.
## Remote Import Transforms
Rolldown does not synthesize named exports from federated remote proxy modules in the same way as
Rollup. The `pluginRemoteNamedExports` transform rewrites named imports and explicit named
re-exports to consume the generated `__moduleExports` namespace.
The transform now emits high-resolution sourcemaps with `source` and `sourcesContent`, so transformed
consumer modules remain debuggable in Vite/Rolldown development and production builds.
Unsupported dynamic import variables still warn and should be replaced with explicit
`loadRemote()` calls.
## Control Chunks
Federation control chunks are protected from user `manualChunks` rules because shared dependency
wrappers can contain top-level await. Grouping those chunks together can create circular async
dependencies and deadlock remote bootstrap.
Sanitizer tests cover:
- Empty Vite preload helper removal.
- Load-share preload helper inlining.
- Remote entry side-effect import removal from control chunks.
- Vite 8-style preload helper output.
## Vite Version Matrix
The package peer range is:
```text
^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
```
`pnpm test:vite-matrix:smoke` packs the package and builds isolated temporary apps against pinned
Vite 5 / 6 / 7 / 8 versions. The smoke app exercises the root export, runtime export, manifest
emission, dynamic imports, and a user `manualChunks` rule. The full Playwright examples still run
against the repository-pinned Vite version because they validate framework and browser behavior,
not every peer major.
## Known `tsup` CJS Warning
`tsup` currently warns that `dist/index.cjs` uses named and default exports together. This is
intentional for compatibility with default plugin imports and named type/runtime exports. The
package smoke test validates the generated tarball exports and a clean Vite app install before
release.
---
# Compatibility Matrix
Source: docs/compatibility-matrix.md
# Compatibility Matrix
This document records the current compatibility contract for projects migrating from OriginJS-style
Vite federation and for hosts consuming mixed remote formats.
## Recommended Path
Use manifest-first Vite remotes whenever possible:
```ts
federation({
name: 'host',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
});
```
Manifest-first remotes are the primary production path. They provide browser and SSR entry
metadata, type artifact metadata, debug artifacts, content hashes, and runtime validation.
## OriginJS Virtual API
The `virtual:__federation__` migration shim is enabled by default through compatibility options.
Supported APIs:
- `__federation_method_setRemote`
- `__federation_method_getRemote`
- `__federation_method_ensure`
- `__federation_method_unwrapDefault`
- `__federation_method_wrapDefault`
The shim registers remotes through `@module-federation/runtime` and normalizes OriginJS component
requests such as `./Button` to `remote/Button`.
Disable the shim when a project wants the manifest-first API only:
```ts
federation({
name: 'host',
compat: {
originjs: false,
virtualFederationShim: false,
},
});
```
## Remote Formats
| Format | Status | Notes |
| ----------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `esm` / `module` | Supported | Preferred for Vite-to-Vite remotes and manifest-first loading. |
| `var` | Partially supported | `varFilename` can emit an additional var-style entry. Browser-only legacy interop should be validated per app. |
| `systemjs` / `system` | Supported with runtime guard | Browser hosts must provide a compatible SystemJS runtime; repository e2e validates a real webpack `library.type = 'system'` remote. |
| Webpack manifest remote | Partially supported | Runtime registration can consume compatible manifest-like entries, but mixed Webpack/Vite deployments need app-level validation. |
| CommonJS remote entry | Unsupported for browser hosts | Use SSR/node-specific loading or expose an ESM/manifest entry instead. |
## `from` Combinations
| Host | Remote | Status | Notes |
| ------- | ------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Vite | Vite | Supported | Primary path. Use manifest-first URLs. |
| Vite | OriginJS Vite | Supported for common migration APIs | Use the virtual shim or migrate to manifest-first config. |
| Vite | Webpack | Supported for validated browser flows | Browser e2e validates a Vite host consuming a webpack `systemjs` remote through the OriginJS compatibility shim. |
| Webpack | Vite | Not a primary target | Vite remotes should publish manifest and module entries; Webpack-host compatibility is not guaranteed by this plugin. |
## CSS Compatibility
OriginJS `dontAppendStylesToHead` maps to the new expose CSS behavior:
- `dontAppendStylesToHead: true` maps to manual CSS injection.
- `css.inject: 'head'` appends CSS to `document.head`.
- `css.inject: 'manual'` leaves CSS collection to the consumer.
- `css.inject: 'none'` disables CSS injection for that expose.
Prefer the explicit `css.inject` option for new code.
Repository coverage:
- `examples/react-remote` publishes `./ManualCssButton` with `dontAppendStylesToHead: true`.
- The compatibility example enables `bundleAllCSS: true` so manual CSS href buckets are emitted at
runtime.
- `examples/originjs-compat-host` verifies the browser receives the manual CSS href bucket and can
inject the stylesheet explicitly from the host.
## Unsupported Or Risky Combinations
The plugin should fail clearly or require app-level validation for:
- Browser hosts consuming CommonJS-only remote entries.
- `systemjs` remotes without a compatible SystemJS runtime.
- Mixed Vite/Webpack shared singleton setups without explicit version and singleton policy.
- Legacy remote entries that do not expose a runtime-compatible container.
- Remote CSS migration paths that rely on implicit global side effects.
Current shim guardrails:
- Unsupported legacy `format` values throw `MFV-005`.
- `systemjs` remotes throw `MFV-005` when `globalThis.System.import` is unavailable.
- Unsupported legacy `from` values emit a one-time `MFV-007` warning.
## Verification Status
Current repository coverage:
- Unit coverage executes the OriginJS virtual APIs.
- Unit coverage validates legacy `from` and `format` normalization.
- Unit coverage validates unsupported legacy `format`, missing `systemjs` runtime support, and
one-time warnings for unsupported `from` values.
- Build examples validate manifest-first browser, SSR, and DTS paths.
- Browser e2e validates `virtual:__federation__` against real `remoteEntry.js` and
`remoteEntry.var.js` assets through `examples/originjs-compat-host`, verifies
manifest-first and remoteEntry-first remotes can coexist on the same host, and loads a real
webpack `systemjs` remote after injecting a local SystemJS runtime asset.
- Browser e2e validates `dontAppendStylesToHead` migration by consuming a manual CSS expose and
injecting its stylesheet from the host.
- `varFilename` generation is supported at build configuration level.
- `examples/webpack-systemjs-remote` validates `format: 'systemjs'` and `from: 'webpack'` against
a real remote built with webpack Module Federation.
The repository now covers the practical migration matrix it documents: Vite `esm`, Vite `var`,
manifest-first Vite remotes, and a webpack `systemjs` remote consumed by a Vite host.
---
# Plugin Comparison
Source: docs/comparison.md
# Vite Module Federation — Plugin Comparison
A feature-by-feature comparison of three Vite Module Federation plugins.
The comparison is based on npm package metadata, published package source, shipped docs, and this
repository's local tests.
For `@module-federation/vite@1.15.4`, this table counts the official Vite adapter plus two runtime
packages: `@module-federation/runtime@2.4.0` and `@module-federation/runtime-core@2.4.0`.
When a feature is available through those lower-level runtime APIs or an optional official runtime
plugin rather than as a turnkey Vite-adapter helper, the cell says so explicitly.
Optional runtime plugins are noted as separate installs; they are not counted as adapter defaults.
npm also publishes `@module-federation/core@1.0.0-canary.1`.
That package is not in `@module-federation/vite@1.15.4`'s dependency graph.
No comparison claim below relies on it.
| Symbol | Meaning |
| ------ | ------------------------------------------- |
| ✅ | First-class support (documented + tested) |
| 🟡 | Partial / lower-level API / optional plugin |
| ❌ | Not supported |
| n/a | Not applicable to that plugin's design |
> Sources were rechecked against the package versions below on 2026-05-13. Footnote markers
> like `[1]` refer to the evidence notes at the bottom of this document.
---
## At a Glance
| | **`vite-plugin-federation`** (this repo) | **`@module-federation/vite@1.15.4`** | **`@originjs/vite-plugin-federation`** |
| ------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| Reviewed version | `1.0.0` [1] | `@module-federation/vite@1.15.4`, `runtime@2.4.0`, `runtime-core@2.4.0`; `@module-federation/core@1.0.0-canary.1` is not in this adapter dependency graph [2] | `1.4.1` [3] |
| Vite peer range | `^5 \|\| ^6 \|\| ^7 \|\| ^8` [1] | `^5 \|\| ^6 \|\| ^7 \|\| ^8` [2] | not declared (devDep `^4.0.5`) [3] |
| Rolldown / Vite 8 | ✅ first-class compiler adapter | ✅ supported | ❌ not targeted |
| Underlying MF runtime | `@module-federation/runtime@2.3.3` (wrapped + extended) | `@module-federation/runtime@2.4.0` + `@module-federation/runtime-core@2.4.0` | hand-rolled (`virtual:__federation__`) |
| Manifest protocol | ✅ `mf-manifest.json` + `mf-stats.json` + `mf-debug.json` | ✅ `mf-manifest.json` + `mf-stats.json`, including `ssrRemoteEntry` meta | ❌ none |
| Curated runtime entry | ✅ `vite-plugin-federation/runtime` [1] | 🟡 import lower-level helpers from `@module-federation/runtime@2.4.0` | ❌ (`virtual:__federation__` only) |
| Dev mode for **remotes** | ✅ classified updates: `partial`, `style`, `types`, `full` | 🟡 React native HMR path + WS/reconnect; full reload fallback [2] | ❌ remote must be `vite build --watch` |
| SSR (Node) | ✅ separate SSR entry + `createServerFederationInstance` | 🟡 `target: 'node'` + `ssrRemoteEntry` metadata; no server helper | ❌ |
| OriginJS migration shim | ✅ `virtual:__federation__` compat layer enabled by default | ❌ | n/a (it _is_ this API) |
---
## 1. Module Federation Core
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------------------------------------------- | :---------------------------------------------: | :------------------------------------------------------------------------------------------: | :--------------------------------: |
| `name` / `filename` | ✅ | ✅ | ✅ |
| `exposes` | ✅ | ✅ | ✅ |
| `remotes` | ✅ (string or object form, manifest-first URLs) | ✅ | ✅ |
| `shared` | ✅ | ✅ | ✅ |
| Custom `shareScope` | ✅ | ✅ | ✅ [3] |
| Multiple scopes / runtime keys | ✅ `createFederationRuntimeScope` [4] | 🟡 `@module-federation/runtime@2.4.0` instances/share scopes; no tenant `runtimeKey` wrapper | ❌ |
| Runtime plugins (`registerPlugins`) | ✅ `runtimePlugins` option + re-exported [5] | ✅ `runtimePlugins` option + `@module-federation/runtime@2.4.0` `registerPlugins` [2] | ❌ (no runtime plugin system) |
| Custom compiler adapter / `compat: { originjs, virtualFederationShim }` | ✅ [6] | ❌ | ❌ |
| `from: 'vite' \| 'webpack'` normalization | ✅ (one-time `MFV-007` warning on unknown) | ❌ | ✅ legacy field |
## 2. Remote Formats
| Format | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------- | :-----------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: | :---------------------------------------: |
| `esm` / `module` | ✅ primary | ✅ primary | ✅ primary |
| `var` (browser global) | ✅ via `varFilename` [6] | ✅ via `varFilename` | ✅ via `loadJS()` script tag |
| `systemjs` / `system` | ✅ runtime-guarded; throws `MFV-005` if `System.import` missing [6] | 🟡 generated bootstrap has `globalThis.System.import` fallback; no OriginJS `format: 'systemjs'` shim [2] | ✅ same code path as `esm` |
| Manifest-first remote URL (`https://.../mf-manifest.json`) | ✅ recommended path | ✅ | ❌ |
| Webpack-built remote consumed by Vite host | ✅ via compat e2e (webpack `library.type: 'system'` remote) [6] | ✅ `vite-webpack-rspack` e2e [12] | ✅ legacy `from: 'webpack'` examples [13] |
| CommonJS browser remote | ❌ unsupported by design (use SSR/ESM instead) | ❌ | ❌ |
## 3. Shared Dependencies
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------- | :-------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | :------------------------------------: |
| `singleton` | ✅ | ✅ | ❌ (commented out in public types) [3] |
| `eager` | ❌ plugin config (`@module-federation/runtime@2.3.3` can register it) | ❌ plugin config (`@module-federation/runtime@2.4.0` can register it) | ❌ (commented out in public types) [3] |
| `strictVersion` | ✅ | ✅ | ❌ |
| `requiredVersion` (semver range) | ✅ | ✅ | ✅ (custom `satisfy` impl) |
| Version negotiation across remotes | ✅ via `@module-federation/runtime@2.3.3` | ✅ via `@module-federation/runtime@2.4.0` | 🟡 first-match only |
| Workspace / scoped shares (`react/`, pnpm symlinks) | ✅ `allowNodeModulesSuffixMatch` [4] | 🟡 workspace e2e; no documented suffix matching [12] | ❌ |
| Host-only shares (`import: false`) | ✅ explicit `MFV-003` on miss [4] | 🟡 supported in types/tests/examples; sparse README docs [12] | ❌ |
| Strict-singleton fallback policy | ✅ recorded in `sharedResolutionGraph` [4] | ❌ | ❌ |
| `bundleAllCSS` option | ✅ option + per-expose CSS modes [4] | ✅ option | ❌ |
| Shared module preload (``) | ✅ via preload plan API | ✅ `@module-federation/runtime@2.4.0` `preloadRemote` + manifest asset analysis [2] | ✅ `modulePreload` flag |
## 4. Exposes & Named Exports
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------- | :------------------------------------------------------------: | :-------------------------------------------------: | :--------------------------------------: |
| Default export | ✅ | ✅ | ✅ |
| Named exports (`import { foo } from 'remote/x'`) | ✅ — Rolldown-aware via `pluginRemoteNamedExports` rewrite [6] | ✅ Rollup `syntheticNamedExports`; Rolldown limited | 🟡 single-import only |
| Per-expose CSS injection mode (`'head' \| 'manual' \| 'none'`) | ✅ [4] | 🟡 single global `bundleAllCSS` | ✅ `dontAppendStylesToHead` (per-expose) |
| Custom expose chunk name (`exposes[].name`) | ❌ | ❌ | ✅ [3] |
| `varFilename` for additional var-style entry | ✅ | ✅ | n/a |
## 5. Dev Experience
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------- | :------------------------------------------------------------: | :-------------------------------------------------------------------------: | :-------------------------------------: |
| Dev server for **host** | ✅ | ✅ | ✅ |
| Dev server for **remote** (no rebuild) | ✅ — sidecar dev runtime [4] | ✅ | ❌ remote must `vite build --watch` [3] |
| Remote-change HMR signal to host | ✅ classified updates: `partial`, `style`, `types`, `full` [4] | 🟡 React native strategy; WS metadata/reconnect; full reload fallback [2] | ❌ |
| Batched / coalesced updates | ✅ [4] | ❌ | ❌ |
| Live `.d.ts` sync (no reload) | ✅ via `dts-plugin` dev worker + WS [4] | 🟡 dev worker present; no local e2e found | ❌ |
| Dynamic remote-type hints injection | ✅ option | ✅ via `@module-federation/dts-plugin` | ❌ |
| Dev top-level-await proxy | ✅ | ✅ | n/a |
| Dev manifest endpoint(s) | ✅ | ✅ served at configured manifest filename (default `/mf-manifest.json`) [2] | ❌ |
## 6. TypeScript / DTS
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------- | :------------------------------------------: | :------------------------------------------: | :--------------------------------: |
| Auto `.d.ts` generation for `exposes` | ✅ via `@module-federation/dts-plugin@2.3.3` | ✅ via `@module-federation/dts-plugin@2.4.0` | ❌ (hand-written types only) |
| `@mf-types.zip` artifact | ✅ | ✅ | ❌ |
| Manifest-derived type URL on host | ✅ + `remoteTypeUrls` override [4] | ✅ | ❌ |
| `typesOnBuild: true` (fail build on missing types) | ✅ | ✅ | ❌ |
| Hot type updates in dev (no reload) | ✅ | 🟡 dev worker exists; no local e2e found | ❌ |
## 7. SSR (Node)
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------- | :-------------------------------------------------: | :-------------------------------------------------------------------------------: | :--------------------------------: |
| `target: 'web' \| 'node'` resolution | ✅ (auto from `build.ssr`, `ENV_TARGET` define) [6] | ✅ same mechanism | ❌ |
| Dedicated `ssrRemoteEntry` artifact | ✅ separate file emitted | 🟡 `metaData.ssrRemoteEntry` emitted; `1.15.4` aliases it to `remoteEntry` [2] | ❌ |
| Server runtime instance API (`createServerFederationInstance`) | ✅ [4] | 🟡 `@module-federation/runtime@2.4.0` `createInstance`; no server-specific helper | ❌ |
| Asset-preload collection for streaming HTML | ✅ `collectFederationManifestPreloadLinks` [4] | 🟡 manifest assetAnalysis + runtime preload; no streaming link collector | ❌ |
| Hydration-safe module-preload helper | ✅ short-circuits when `document` is undefined [6] | ✅ similar patch | ❌ |
| Required Node flags | `--experimental-vm-modules` [4] | n/a | n/a |
## 8. Production Runtime (host loader)
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------------------- | :-------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Manifest remote loading API | ✅ `loadRemoteFromManifest()` [4] | ✅ manifest remotes via `@module-federation/runtime@2.4.0` `loadRemote` [2] | ❌ |
| Cache TTL / `staleWhileRevalidate` / `force` refresh | ✅ [4] | 🟡 `@module-federation/runtime-core@2.4.0` manifest cache exists; no TTL/SWR wrapper [2] | ❌ |
| Retry with jitter + timeout | ✅ [4] | 🟡 separately installable official `@module-federation/retry-plugin@2.4.0`; no adapter default [2] | ❌ |
| Fallback URLs | ✅ records effective `sourceUrl` | 🟡 separately installable `@module-federation/retry-plugin@2.4.0` domain / manifest-domain rotation [2] | ❌ |
| Circuit breaker (`failureThreshold`, `cooldownMs`) | ✅ [4] | ❌ | ❌ |
| Concurrent-fetch request collapsing | ✅ [4] | 🟡 `@module-federation/runtime-core@2.4.0` global loading / manifest loading maps [2] | ❌ |
| Integrity verification (SRI / SHA-256, multi-mode) | ✅ `verifyFederationManifestAssets` [7] | ❌ | ❌ |
| Private / authenticated manifests (`fetchInit`, custom `fetch`) | ✅ [7] | 🟡 `@module-federation/runtime-core@2.4.0` `loaderHook.fetch`; optional retry plugin `fetchOptions` requires separate install [2] | ❌ |
| Signed manifest workflow | ✅ documented + custom-fetch hook [7] | ❌ | ❌ |
| CSP / Trusted Types guidance | ✅ [7] | ❌ | ❌ |
## 9. Multi-Tenant / Scoping
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------------------- | :-----------------------------------------------: | :-----------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Per-tenant `runtimeKey` scope | ✅ `createFederationRuntimeScope(runtimeKey)` [8] | ❌ | ❌ |
| Tenant-isolated manifest cache | ✅ | 🟡 possible with separate `@module-federation/runtime@2.4.0` instances; no `runtimeKey` cache partition | ❌ |
| Tenant-isolated circuit breaker / debug records | ✅ | ❌ | ❌ |
| Per-tenant `shareScope` | ✅ | ✅ (one global scope per plugin instance) | ✅ (same) |
## 10. Preload & Performance
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| --------------------------------------------------------------- | :------------------------------------------: | :-----------------------------------------------------------------------------------: | :--------------------------------: |
| Route-aware preload plan | ✅ `createFederationManifestPreloadPlan` [9] | ❌ | ❌ |
| Async-chunk preload policy (`'css' \| 'none' \| 'js' \| 'all'`) | ✅ [9] | 🟡 `@module-federation/runtime@2.4.0` `preloadRemote` filters/resource categories [2] | ❌ |
| Remote warming (`warmFederationRemotes`) | ✅ [9] | ✅ `@module-federation/runtime@2.4.0` `preloadRemote` [2] | ❌ |
| Per-remote load metrics (registration / load / total) | ✅ [9] | ❌ | ❌ |
| Manifest-declared route hints | ✅ | ❌ | ❌ |
## 11. Observability & DevTools
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------: |
| Dev devtools panel + browser events | ✅ `pluginDevtools.ts` [4] | ❌ | ❌ |
| `getFederationDebugInfo()` snapshot | ✅ (instance, manifests, fetch timeline, integrity, breaker, metrics, shared graph) [4] | 🟡 `@module-federation/runtime-core@2.4.0` exposes `globalThis.__FEDERATION__`, snapshots, and debug constructor; no same helper [2] | ❌ |
| Telemetry hooks (`manifestFetch`, `remoteRegister`, `remoteLoad`, `remoteRefresh`) | ✅ [4] | 🟡 runtime hooks such as `loaderHook.fetch`, `registerRemote`, `onLoad`, `errorLoadRemote`; no same named telemetry API / `remoteRefresh` [2] | ❌ |
| Stable error codes (`MFV-001` … `MFV-007`) | ✅ [10] | 🟡 `@module-federation/runtime@2.4.0` has its own error codes; not `MFV-*` | ❌ generic strings |
| `mf-debug.json` build artifact | ✅ | ❌ | ❌ |
## 12. Security
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ----------------------------------- | :---------------------------------------------------------------------: | :------------------------------: | :------------------------------------: |
| Subresource integrity in manifest | ✅ [7] | ❌ | ❌ |
| Content-hash verification | ✅ multi-mode (`prefer-integrity`, `integrity`, `content-hash`, `both`) | ❌ | ❌ |
| CSP / nonce friendly browser builds | ✅ documented; browser target avoids inline scripts [7] | 🟡 similar target tree-shaking | 🟡 script-tag legacy paths need review |
| Trusted Types support | ✅ documented | ❌ | ❌ |
| Signed manifest pattern | ✅ via `fetch` wrapper | ❌ | ❌ |
## 13. Compiler / Build Adapter
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------: | :--------------------------------: | :--------------------------------: |
| Rollup output handling | ✅ | ✅ | ✅ |
| Rolldown output handling (Vite 8) | ✅ separate `rolldownOptions` path + `getRolldownOptions` rewrite [6] | ✅ separate `rolldownOptions` path | ❌ |
| Top-level-await deadlock prevention (CJS-proxy helper inlining) | ✅ two-pass `generateBundle` [6] | ✅ same algorithm (forked here) | n/a |
| `runtimeInit` + `loadShare` chunk isolation | ✅ enforced (rejects user `manualChunks` that conflict, with warning) [6] | ✅ same | n/a |
| Module-preload helper rewrite to `import.meta.url` (so cross-origin remote chunks resolve correctly) | ✅ covers Vite 8, esbuild, terser, arrow + function form [6] | ✅ same | 🟡 partial |
| `commonjsOptions.strictRequires: 'auto'` defaulting | ✅ | ✅ | ❌ |
| `output.codeSplitting.groups` guard (Vite 8) | ✅ | ✅ | n/a |
## 14. OriginJS Compatibility
| Capability | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| -------------------------------------------------------------------- | :--------------------------------------------------------------------------------------: | :------------------------------: | :--------------------------------: |
| `virtual:__federation__` shim | ✅ enabled by default; toggle via `compat.virtualFederationShim` [11] | ❌ | n/a (it _is_ the API) |
| `__federation_method_setRemote / getRemote / ensure / unwrap / wrap` | ✅ all five | ❌ | ✅ |
| Normalizes OriginJS `./Button` → `remote/Button` | ✅ | ❌ | n/a |
| `dontAppendStylesToHead` → `css.inject: 'manual'` mapping | ✅ | ❌ | ✅ native |
| Drop-in adoption from OriginJS apps | ✅ + e2e validates a Vite host loading a webpack `systemjs` remote through the shim [11] | ❌ requires rewrite | n/a |
## 15. Public API Surface
### `vite-plugin-federation` (this repo)
```ts
// Build-time plugin
import federation, { type ModuleFederationOptions } from 'vite-plugin-federation';
// Production runtime helpers (curated subpath export)
import {
createFederationInstance,
createServerFederationInstance,
createFederationRuntimeScope,
fetchFederationManifest,
registerManifestRemote,
registerManifestRemotes,
loadRemoteFromManifest,
refreshRemote,
warmFederationRemotes,
preloadRemote,
createFederationManifestPreloadPlan,
collectFederationManifestPreloadLinks,
verifyFederationManifestAssets,
getFederationDebugInfo,
clearFederationRuntimeCaches,
// re-exports of @module-federation/runtime@2.3.3
registerPlugins,
registerRemotes,
registerShared,
loadRemote,
loadShare,
loadShareSync,
} from 'vite-plugin-federation/runtime';
```
### `@module-federation/vite@1.15.4`
```ts
import { federation } from '@module-federation/vite';
// Reviewed runtime API package: @module-federation/runtime@2.4.0
import { loadRemote, registerRemotes } from '@module-federation/runtime';
```
### `@originjs/vite-plugin-federation`
```ts
import federation from '@originjs/vite-plugin-federation';
import {
__federation_method_ensure,
__federation_method_getRemote,
__federation_method_setRemote,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';
```
## 16. Test & Example Surface
| Surface | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------- | :----------------------------------------------------------------------: | :--------------------------------------------------------------------------: | :--------------------------------: |
| Unit tests | ✅ vitest | ✅ vitest | ✅ vitest |
| Integration tests (build pipeline) | ✅ | ✅ (`integration/`) | 🟡 |
| Browser e2e (Playwright) | ✅ 6 configs: default, compat, multi-remote, shared, ssr, browser-matrix | ✅ vite-vite + vite-webpack-rspack | ✅ |
| SSR e2e | ✅ | ❌ | ❌ |
| Browser matrix | ✅ Chromium / Firefox / WebKit | ❌ Chromium only | ❌ |
| Vite peer-version smoke | ✅ packed tarball builds and runs against Vite 5 / 6 / 7 / 8 | ✅ CI integration matrix Vite 5 / 6 / 7 / 8; examples include 6 / 7 / 8 [12] | ❌ Vite 4-focused |
| Webpack-remote interop e2e | ✅ `examples/webpack-systemjs-remote` through compat shim | ✅ `vite-webpack-rspack` [12] | ✅ webpack/SystemJS examples [13] |
| Workspace / pnpm-symlink shared | ✅ `examples/workspace-shared-*` | ✅ `@vite-vite/shared-lib` e2e [12] | ❌ |
| DTS hot-sync e2e | ✅ `e2e/dts-dev-hot-sync.mjs` | ❌ | ❌ |
---
## 17. Ecosystem, Options & Governance
| Surface | `vite-plugin-federation` | `@module-federation/vite@1.15.4` | `@originjs/vite-plugin-federation` |
| ---------------------------------------- | :--------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------: |
| Framework examples | ✅ React / Vue / Svelte / Lit / SSR / DTS / workspace [4] | 🟡 local examples cover Vite/Vite, Vite/Webpack/Rspack, runtime-register, Rust/Rsbuild; broader ecosystem examples are not bundled here [12] | ✅ Vue / React / Rollup / Webpack examples [13] |
| `@module-federation/vite@1.15.4` options | ✅ baseline options plus runtime / SSR / compat extensions | ✅ baseline adapter options [12] | ❌ legacy option model |
| Migration docs | ✅ OriginJS + `@module-federation/vite@1.15.4` guides [4] | 🟡 README/examples, no migration guide in `1.15.4` package [12] | 🟡 legacy docs, no MF-runtime guide [13] |
| License | MIT [14] | MIT [14] | MulanPSL-2.0 [14] |
---
## When to Choose Which
### Pick `vite-plugin-federation` (this repo) when…
- You need **production-oriented host loading** with cache TTL, retries, circuit breaker, fallback
URLs, and integrity verification.
- You ship to **multiple tenants on the same page** and need scoped manifest caches and share
scopes.
- You serve **SSR** (React, Vinext, etc.) and need a server-aware federation instance with
asset-preload collection.
- You want **stable error codes** (`MFV-NNN`) and a debug snapshot for ops.
- You're **migrating off OriginJS** and want the legacy `virtual:__federation__` API to keep
working while you move to manifest-first config.
- You target **Vite 8 + Rolldown** today.
### Pick `@module-federation/vite@1.15.4` when…
- You're already in the `@module-federation/*` ecosystem and want a thin, official Vite adapter
whose runtime semantics exactly match `@module-federation/runtime@2.4.0` defaults.
- You value the broader `@module-federation/*` ecosystem examples and are comfortable importing
runtime helpers directly from `@module-federation/runtime@2.4.0`.
- You're comfortable composing lower-level `@module-federation/runtime@2.4.0` /
`@module-federation/runtime-core@2.4.0` APIs and optional official runtime plugins for
resilience instead of using this repo's built-in policy wrapper.
- You want the **smallest MF-runtime-based adapter surface** that still produces a manifest.
### Pick `@originjs/vite-plugin-federation` when…
- You have an existing app already on OriginJS and **don't yet need dev mode for remotes**
(e.g., remotes are deployed as built artifacts and the host is the only thing under active
dev).
- You rely on OriginJS's legacy `format` / `from` / `externalType` model or its webpack/SystemJS
examples.
- You can't add the `@module-federation/runtime@2.3.3` dependency (e.g., the project disallows new
runtime deps), and the lack of singleton / strictVersion negotiation is acceptable.
- You're targeting **Vite 4** specifically.
> If you're starting a new micro-frontend in 2026 on Vite 5+, the OriginJS plugin's
> "remote must be `vite build --watch`" constraint and lack of a shared-version negotiator
> make it the wrong default for new code.
---
## Migration Notes
- **From `@originjs/vite-plugin-federation` → `vite-plugin-federation`:** the `virtual:__federation__`
API works unchanged; map `dontAppendStylesToHead: true` to `css.inject: 'manual'` (or leave
it — the shim normalizes both). See `docs/originjs-migration.md` and
`examples/originjs-compat-host`.
- **From `@module-federation/vite@1.15.4` → `vite-plugin-federation`:** the plugin options shape is a
superset; replace `import { federation } from '@module-federation/vite'` with
`import federation from 'vite-plugin-federation'`. Direct
`@module-federation/runtime@2.4.0` imports already expose official helpers such as
`registerPlugins`, `preloadRemote`, `registerRemotes`, and `loadRemote`; switch selected call
sites to `vite-plugin-federation/runtime` when you want this repo's
cached/retried/integrity-checked manifest helpers and debug snapshot wrapper.
---
## Evidence Notes
- [1] `packages/vite-plugin-federation/package.json`, README, docs, unit tests, and e2e suites in this repository.
- [2] npm package metadata and tarballs rechecked on 2026-05-13:
`@module-federation/vite@1.15.4` depends on `@module-federation/runtime@2.4.0`,
`@module-federation/sdk@2.4.0`, and `@module-federation/dts-plugin@2.4.0`;
`@module-federation/runtime@2.4.0` depends on `@module-federation/runtime-core@2.4.0`.
`@module-federation/core@1.0.0-canary.1` was also checked, but it is not in
`@module-federation/vite@1.15.4`'s dependency graph, so feature claims above are based on
`@module-federation/vite@1.15.4`, `@module-federation/runtime@2.4.0`, and
`@module-federation/runtime-core@2.4.0`. Optional retry/fallback rows reference the separately
installable official `@module-federation/retry-plugin@2.4.0`; it is not in this adapter
dependency graph.
Relevant published/local APIs include `remoteHmr`, `runtimePlugins`, `preloadRemote`,
`registerPlugins`, `loaderHook.fetch`, `registerRemote`, `onLoad`, `errorLoadRemote`,
manifest cache/loading state, `globalThis.__FEDERATION__`, `globalThis.System.import`
bootstrap fallback, and manifest `metaData.ssrRemoteEntry`.
- [3] `@originjs/vite-plugin-federation@1.4.1` npm metadata, public types, and examples.
- [4] Local docs/tests for this repository's runtime wrapper, dev HMR, SSR, DTS, examples, and e2e suites.
- [5] `vite-plugin-federation/runtime` re-exports selected `@module-federation/runtime@2.3.3` helpers.
- [6] Local compiler adapter and OriginJS compatibility implementation/tests.
- [7] Local production-runtime and security docs/tests.
- [8] Local multi-tenant docs/tests.
- [9] Local preload/performance docs/tests.
- [10] Local logger and public API contract docs/tests.
- [11] Local OriginJS migration shim docs/tests.
- [12] Local `@module-federation/vite@1.15.4` source checkout: README, types, integration tests, Playwright config, bundled examples, and CI workflow metadata. Broader ecosystem examples outside this checkout were not revalidated here.
- [13] OriginJS public README/examples.
- [14] Package licenses in the reviewed npm packages.
---
# Migrate From @module-federation/vite
Source: docs/migrate-from-module-federation-vite.md
# Migrating from `@module-federation/vite`
`vite-plugin-federation` is API-compatible with `@module-federation/vite` for the build-time plugin
shape and exact-pins its Module Federation runtime stack at `2.3.3`.
Current `@module-federation/vite` releases may pin a newer MF runtime, so check your lockfile when
your application imports `@module-federation/runtime` directly.
Most applications can still switch with a one-line dependency change.
The net gains are a curated runtime entry, manifest-first host loading policy, multi-tenant scoping,
integrity verification, SSR helpers, classified dev remote HMR, and stable error codes.
This guide does not treat official runtime/core features as missing. `@module-federation/vite`
already exposes official runtime behavior through `@module-federation/runtime`, including
helpers such as `registerPlugins`, `registerRemotes`, `preloadRemote`, `loadRemote`, runtime
hooks, manifest cache state, debug globals, and optional official plugins such as
`@module-federation/retry-plugin`.
The migration value here is the higher-level policy wrapper and compatibility surface this package
adds around those primitives.
A full feature comparison lives in [comparison.md](comparison.md).
---
## 1. Replace the dependency
```diff
- "@module-federation/vite": "^1.15.4"
+ "vite-plugin-federation": "^1.0.0"
```
The `@module-federation/runtime`, `@module-federation/sdk`, and `@module-federation/dts-plugin`
versions are all exact-pinned to `2.3.3` inside this package. If you import
`@module-federation/runtime` directly elsewhere in your app, prefer switching those runtime calls
to `vite-plugin-federation/runtime`; the plugin also aliases runtime imports during Vite builds so
federation state stays shared through the configured bridge.
## 2. Update the plugin import
```diff
- import { federation } from '@module-federation/vite';
+ import federation from 'vite-plugin-federation';
```
`vite-plugin-federation` exports a default function. Named import via `import { federation }`
also works.
## 3. Plugin options — what carries over verbatim
These options have identical names and semantics:
| Option | Notes |
| ---------------------------------------------- | ----------------------------------------------------------------- |
| `name` | unchanged |
| `filename`, `varFilename` | unchanged |
| `exposes`, `remotes`, `shared` | unchanged |
| `manifest: boolean \| object` | superset — see §4 |
| `dts: boolean \| object` | superset; this package uses `@module-federation/dts-plugin@2.3.3` |
| `dev: boolean \| object` | superset — see §5 |
| `target: 'web' \| 'node'` | unchanged (auto-detect from `build.ssr`) |
| `runtimePlugins` | unchanged |
| `publicPath`, `getPublicPath` | unchanged |
| `shareStrategy`, `shareScope` | unchanged |
| `virtualModuleDir` | unchanged |
| `hostInitInjectLocation` | unchanged |
| `moduleParseTimeout`, `moduleParseIdleTimeout` | unchanged |
## 4. Manifest options — additive
`vite-plugin-federation` emits a third artifact, `mf-debug.json`, in addition to `mf-manifest.json`
and `mf-stats.json`.
No config change is required; it just shows up in your build output.
The host can ignore it, and CI can use it for build diagnostics.
See `docs/manifest-protocol.md`.
## 5. Dev — remote HMR with classification
`@module-federation/vite`'s `dev: { remoteHmr: true }` has websocket metadata, reconnect
handling, a React native HMR strategy, and a full-reload fallback. `vite-plugin-federation`
keeps remote HMR opt-in, but when you enable `dev: { remoteHmr: true }`, it classifies each
update so the host can react proportionally:
- **`partial`** — try `server.reloadModule()` for the affected expose.
- **`style`** — refresh the affected stylesheet only.
- **`types`** — sync `.d.ts` to the host without reload.
- **`full`** — fall back to a full reload.
Add `dev: { remoteHmr: true }` to opt in.
See `docs/dev-hmr.md` for the strategy table and the disable flags (`disableLiveReload`,
`disableHotTypesReload`, `disableDynamicRemoteTypeHints`).
## 6. Runtime API — new curated entry
If you previously imported runtime helpers directly:
```ts
import { loadRemote, registerRemotes } from '@module-federation/runtime';
```
…you can keep that import working unchanged (the plugin aliases it). Official runtime imports
also already expose helpers such as `registerPlugins` and `preloadRemote`. To get this
package's manifest-first loading wrapper with cache TTL, retries, circuit breaker state, scoped
debug records, and integrity checks, switch selected call sites to:
```ts
import {
registerManifestRemote,
loadRemoteFromManifest,
fetchFederationManifest,
warmFederationRemotes,
createFederationManifestPreloadPlan,
verifyFederationManifestAssets,
createFederationRuntimeScope,
getFederationDebugInfo,
} from 'vite-plugin-federation/runtime';
```
The full surface and option shape is documented in `docs/runtime-api.md`.
There is no behavioral conflict with continuing to use `@module-federation/runtime` directly for
non-manifest paths.
## 7. SSR — opt-in helpers
`@module-federation/vite@1.15.4` emits `metaData.ssrRemoteEntry` in the manifest metadata; in
that package version it points at the same entry as `remoteEntry`. `vite-plugin-federation`
emits a separate `ssrRemoteEntry.js` per remote and exposes:
```ts
import {
createServerFederationInstance,
collectFederationManifestPreloadLinks,
} from 'vite-plugin-federation/runtime';
```
See `docs/production-runtime.md` for entry selection rules.
See `docs/runtime-api.md` for the preload-link collector.
Node currently needs `--experimental-vm-modules`.
## 8. Multi-tenant deployments
If you serve multiple tenants on the same page (e.g., per-customer remote sets), wrap each
tenant in its own runtime scope:
```ts
import { createFederationRuntimeScope } from 'vite-plugin-federation/runtime';
const tenant = createFederationRuntimeScope(tenantId);
await tenant.registerManifestRemote('catalog', tenantCatalogManifestUrl, {
shareScope: tenantId,
});
```
This isolates the manifest cache, circuit breaker, and debug records per tenant. The official
runtime has lower-level instances and share scopes, but not this `runtimeKey` cache/breaker/debug
partitioning wrapper. See `docs/multi-tenant.md`.
## 9. Things that change behavior on switch (read carefully)
- **Default build artifacts** include `mf-debug.json` (extra file, harmless if ignored).
- **`runtimeInit` / `loadShare` chunk isolation** is enforced on both Rolldown and Rollup;
if your build sets `output.manualChunks`, the plugin replaces it with federation-specific
chunk rules and prints a one-time warning. `output.codeSplitting.groups` is removed because
it can group federation control chunks with app code.
- **Dev `remoteHmr` is opt-in.** Add `dev: { remoteHmr: true }` when you want host/remote HMR
wiring during local development.
- **`compat.virtualFederationShim` is on by default** (the `virtual:__federation__` API
works). It costs nothing if unused. Disable with `compat: { originjs: false,
virtualFederationShim: false }` if you want a strict manifest-only surface.
## 10. Verification checklist
After switching:
1. `pnpm build` — confirm `dist/mf-manifest.json` and `dist/mf-stats.json` look the same.
2. Smoke-test `loadRemote('alias/expose')` (existing call sites keep working).
3. If you load remotes from a URL, change the URL to point at `mf-manifest.json` and call
`loadRemoteFromManifest('alias/expose')` to pick up retries and cache TTL.
4. Check `getFederationDebugInfo()` in dev to confirm registered remotes match your config.
---
_See [comparison.md](comparison.md) for the full plugin-by-plugin feature matrix._
---
# OriginJS Migration
Source: docs/originjs-migration.md
# OriginJS Migration Guide
This guide covers migration from `@originjs/vite-plugin-federation` style hosts to this
manifest-first plugin.
## Recommended Path
1. Enable this plugin with the same public remote aliases.
2. Keep `compat` enabled while migrating host code.
3. Move new remotes to `mf-manifest.json` URLs.
4. Keep legacy `remoteEntry.js` remotes as object configs only where needed.
5. Remove compatibility APIs after imports have moved to manifest-first runtime APIs.
## Compatibility Defaults
Compatibility is enabled by default:
```ts
federation({
name: 'shell',
compat: true,
});
```
Equivalent explicit config:
```ts
compat: {
originjs: true,
virtualFederationShim: true,
}
```
Disable compatibility for new manifest-only applications:
```ts
compat: false;
```
## Supported Virtual APIs
The plugin provides `virtual:__federation__` with these OriginJS-style APIs:
- `__federation_method_setRemote`
- `__federation_method_getRemote`
- `__federation_method_ensure`
- `__federation_method_unwrapDefault`
- `__federation_method_wrapDefault`
Example:
```ts
import {
__federation_method_getRemote,
__federation_method_setRemote,
} from 'virtual:__federation__';
__federation_method_setRemote('catalog', {
url: 'https://cdn.example.com/catalog/mf-manifest.json',
format: 'esm',
from: 'vite',
});
const module = await __federation_method_getRemote('catalog', './Button');
```
## Remote Config Mapping
OriginJS style:
```ts
remotes: {
catalog: 'https://cdn.example.com/catalog/assets/remoteEntry.js',
}
```
Preferred manifest-first config:
```ts
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
}
```
Legacy remote entry config:
```ts
remotes: {
catalog: {
name: 'catalog',
entry: 'https://cdn.example.com/catalog/remoteEntry.js',
type: 'module',
format: 'esm',
from: 'vite',
},
}
```
## CSS Migration
OriginJS `dontAppendStylesToHead` maps to manual CSS mode:
```ts
exposes: {
'./Widget': {
import: './src/Widget.tsx',
dontAppendStylesToHead: true,
},
}
```
Equivalent explicit config:
```ts
exposes: {
'./Widget': {
import: './src/Widget.tsx',
css: {
inject: 'manual',
},
},
}
```
Manual CSS hrefs are exposed through the compatibility CSS bucket so hosts can inject them into
Shadow DOM or controlled style containers.
## Webpack And SystemJS Remotes
Webpack remotes built with `library.type = 'system'` can be consumed through the compatibility
shim when a SystemJS runtime is loaded by the host. Configure:
```ts
__federation_method_setRemote('legacySystem', {
url: 'https://cdn.example.com/legacy/remoteEntry.js',
format: 'systemjs',
from: 'webpack',
entryGlobalName: 'legacySystem',
});
```
Unsupported `format` values throw `MFV-005`. Unsupported `from` values emit `MFV-007` warnings.
## Migration Checklist
- Use manifest URLs for all new Vite remotes.
- Keep legacy `remoteEntry.js` only for remotes that cannot emit `mf-manifest.json` yet.
- Use `varFilename` only when a var-style host still needs it.
- Keep `compat` enabled until all `virtual:__federation__` imports are removed.
- Run `pnpm --filter vite-plugin-federation test:e2e:compat` after compatibility config changes.
See [compatibility-matrix.md](compatibility-matrix.md) for the exact supported matrix.
---
# Troubleshooting
Source: docs/troubleshooting.md
# Troubleshooting
This guide is organized by runtime/build error and warning code.
## `MFV-001` Configuration Errors
Typical causes:
- Missing `name`.
- Invalid `virtualModuleDir`.
- Reserved internal name prefix used in public names.
Actions:
- Add a stable `name` to every host and remote.
- Use a single directory name for `virtualModuleDir`; do not include `/`.
- Avoid names starting with `__mfe_internal__`.
## `MFV-002` Alias Conflict Warnings
Typical causes:
- A configured Vite alias targets the same package key as a federated shared dependency.
- A shared package import is rewritten to a different file path before federation share resolution.
- Host and remote builds use different alias targets for a package that is expected to stay shared.
Actions:
- Inspect the `MFV-002` warning for the shared module, alias, and target path.
- Remove the alias for shared packages when the package should be resolved through federation.
- If the alias is intentional, align host and remote `shared` keys, versions, and alias targets.
- Use `allowNodeModulesSuffixMatch` only for package-layout differences, not to mask incompatible
alias targets.
## `MFV-003` Shared Resolution Errors
Typical causes:
- No host provider exists for `import: false`.
- Selected shared version does not satisfy `requiredVersion`.
- `strictSingleton` detected a singleton conflict.
- pnpm or symlinked paths do not match shared keys.
Actions:
- Inspect `getFederationDebugInfo().runtime.sharedResolutionGraph`.
- Add root and trailing-slash shared keys, for example `react` and `react/`.
- Enable `allowNodeModulesSuffixMatch` for pnpm or workspace layouts.
- Use `strictSingleton: true` only when conflicting providers must fail fast.
- For host-only modules, ensure the host provides the shared package before remote code loads.
## `MFV-004` Manifest, Runtime Load, Or Integrity Errors
Typical causes:
- Manifest URL is unavailable, slow, or blocked by CORS.
- Manifest shape is invalid.
- Runtime remote loading failed after registration.
- Remote entry integrity or content hash verification failed.
- Manifest circuit breaker is open.
- SSR Node entry failed to fetch or evaluate.
Actions:
- Check `getFederationDebugInfo().runtime.manifestFetches`.
- Check `runtime.lastLoadError` for `manifestUrl`, selected `entry`, `target`, and `shareScope`.
- Add `timeout`, bounded `retries`, `fallbackUrls`, and `circuitBreaker` for production hosts.
- Enable `staleWhileRevalidate` when serving the last known good manifest is acceptable.
- Verify CORS for `mf-manifest.json`, remote entries, chunks, and CSS.
- For SSR, run the server with `--experimental-vm-modules` while the VM module bridge is required.
- If integrity fails, compare the deployed remote entry with the manifest that references it.
## `MFV-005` Missing Exposes Or Unsupported Compatibility Format
Typical causes:
- `collectFederationManifestPreloadLinks()` asked for an expose not present in the manifest.
- OriginJS compatibility requested an unsupported `format`.
- SystemJS remote was used without a SystemJS runtime.
Actions:
- Confirm expose names include the expected `./` prefix.
- Inspect the deployed `mf-manifest.json`.
- Use supported compatibility formats from [compatibility-matrix.md](compatibility-matrix.md).
- Load SystemJS before consuming `format: 'systemjs'` remotes.
## `MFV-006` SSR Entry Selection Errors
Typical causes:
- Node target cannot find a usable `ssrRemoteEntry`.
- Manifest has a malformed `ssrRemoteEntry`.
- Remote SSR build was not merged into the deployed client artifact directory.
Actions:
- Build the remote SSR output and publish it next to the browser manifest.
- Confirm `metaData.ssrRemoteEntry.name` exists and points to an ESM SSR entry.
- Run `pnpm --filter vite-plugin-federation test:e2e:ssr`.
- Compare server and browser debug payloads in the React SSR example.
## `MFV-007` Compatibility Warnings
Typical causes:
- Legacy `from` value is not fully supported.
- Migration config depends on behavior outside the supported compatibility matrix.
Actions:
- Prefer manifest-first remotes for new Vite applications.
- Keep unsupported compatibility combinations isolated behind migration code.
- See [originjs-migration.md](originjs-migration.md).
## Vue Remote SFC HMR Updates Only After Remount
Typical causes:
- The host and a runtime-added remote evaluated separate Vue dev runtimes in the same browser page.
- A remote Vue runtime replaced `globalThis.__VUE_HMR_RUNTIME__`, while already mounted component
records still belong to the host Vue runtime instance.
- The remote is registered only through runtime APIs, so the configured-remotes HMR bridge was not
injected during host dev server startup.
- The remote exposes an HTML bootstrap entry such as `src/main.ts` or `src/main.tsx` instead of a
component, route module, or loader.
Actions:
- Share `vue` and `vue/*` as singletons between host and remote.
- For runtime-added remotes, call `connectRuntimeRemoteHmr()` after `registerRemotes()` or
`loadRemoteFromManifest()`.
- Confirm whether `globalThis.__VUE_HMR_RUNTIME__` changes after the remote component is loaded.
- If a second Vue dev runtime is unavoidable, preserve the host Vue HMR runtime after importing Vue
and restore it after loading the remote. Keep this workaround dev-only.
- See [dev-hmr.md](dev-hmr.md#vue-runtime-hmr-caveat).
## Debug Snapshot Checklist
Capture this when reporting production incidents:
- Application release id and host commit.
- Remote manifest URL and remote release id.
- `getFederationDebugInfo()` output.
- Browser console errors and network status for manifest/remote entry requests.
- SSR server logs when `target: 'node'` is involved.
---
# Release Checklist
Source: docs/release-checklist.md
# Release Checklist
This repository publishes `vite-plugin-federation` from `packages/vite-plugin-federation`.
Releases are tag-driven or manually dispatched through `.github/workflows/release.yml`.
## Versioning Policy
- Use Changesets for every user-facing package change: runtime behavior, plugin options, emitted
artifacts, public types, package exports, or documented defaults.
- Test-only, CI-only, and documentation-only changes do not require a Changeset unless they change
published package behavior.
- For `1.x`, use patch versions for fixes and compatible internal hardening, minor versions for
additive public capabilities, and major versions for breaking changes to the public API contract.
- The release tag must exactly match the package version as `v`.
- Review [public-api-contract.md](public-api-contract.md) before every stable release. Changes to the
plugin option surface, `vite-plugin-federation/runtime` exports, manifest schema, devtools
contract, `MFV-*` meanings, Node floor, or Vite peer range must be additive for `1.x`.
## Package Quality Gates
Run these locally before creating a release tag:
```bash
pnpm install --frozen-lockfile
pnpm check
pnpm test:package:smoke
P0_P1_SMOKE_PACKAGE_SPEC=1.0.0-rc.0 pnpm test:p0-p1:smoke
pnpm test:vite-matrix:smoke
npm pack ./packages/vite-plugin-federation --dry-run --json \
| node scripts/validate-package-tarball.mjs --package-dir=packages/vite-plugin-federation
```
The package smoke test validates:
- The workspace package builds ESM, CJS, and declaration outputs.
- A real `pnpm pack` tarball contains root and runtime exports.
- The tarball installs in a clean Vite app and builds successfully.
- `package.json` declares `sideEffects: false`.
- Production output does not include the devtools overlay bootstrap.
The Vite peer matrix smoke test validates that the packed tarball installs, builds, previews, and
loads a manifest remote in Chromium using pinned Vite 5, 6, 7, and 8 versions. It is a compact
packaging/compiler-adapter runtime gate; the Playwright examples remain the broader browser
behavior gate.
The P0/P1 smoke test validates an installed package spec, not the local workspace source. By
default it installs the npm `latest` dist-tag for published-version regression checks. Before
publishing a release candidate or stable release, set `P0_P1_SMOKE_PACKAGE_SPEC` to the exact
published prerelease version, a dist-tag, or a packed tarball path. It creates temporary Vite
example workspaces that install `vite-plugin-federation` from that spec and exercise the
production-critical P0/P1 surface: manifest runtime loading, shared diagnostics, DTS
generation/consumption, SSR target selection, OriginJS ESM/VAR compatibility, runtime rollout
controls, integrity checks, devtools, dev remote loading, and compiler chunk/named export behavior.
No-reload dev remote HMR remains guarded by the Playwright e2e suite.
For a local tarball candidate, run the smoke with a concrete tarball path, for example
`P0_P1_SMOKE_PACKAGE_SPEC=./vite-plugin-federation-1.0.0.tgz pnpm test:p0-p1:smoke`.
Run the manual `Extended E2E` workflow before publishing a release candidate or stable release when
changes touched runtime loading, shared resolution, DTS, SSR, or compatibility behavior.
The Extended E2E workflow runs on Node 20 and 22. Local runs use fixed default ports but support
`MF_E2E__PORT` overrides; the package scripts run a port preflight before Playwright starts so
port collisions fail with a direct override hint.
## Release Steps
1. Confirm the worktree is clean.
2. Add or review Changesets with `pnpm changeset`; document the user-visible change in
`CHANGELOG.md` or the generated package changelog during versioning.
3. Version packages with `pnpm version-packages`.
4. Run the package quality gates above.
5. Commit the version and changelog changes.
6. Create and push a tag that matches `packages/vite-plugin-federation/package.json`, for example
`v1.0.0`.
7. Let the Release workflow validate the tag, dry-run the package tarball, and publish through npm
Trusted Publisher with provenance.
## Rollback Notes
- Do not republish the same version.
- If a release is broken, publish a new patch or prerelease with a Changeset that explains the
rollback/fix.
- Keep `release.id`, `metaData.buildInfo.releaseId`, and CI commit metadata in remote manifests so
production incidents can identify the exact remote artifact.
---
# Production Readiness Status
Source: docs/remaining-production-todos.md
# Production Readiness Status
This document tracks the work completed to move the plugin from a usable beta-quality
implementation to a production-grade, GA-ready "best-in-class" Vite Module Federation plugin.
## Current Baseline
Already implemented:
- Manifest-first remote consumption through `mf-manifest.json`.
- Runtime bridge over `@module-federation/runtime`.
- Manifest fetch cache, TTL, retries, timeout, force refresh, and request collapsing.
- Runtime manifest registration, refresh, debug snapshots, and SSR target selection.
- Manifest, stats, debug artifacts, and traceable build metadata.
- SSR React host/remote example with manifest-driven preload link collection.
- Dev remote live reload coverage and basic devtools overlay/event stream.
- OriginJS-style compatibility shim for `virtual:__federation__`.
- DTS plugin integration at the build/dev plugin level.
- Core monorepo tooling, CI, format/lint/typecheck/test/build checks.
No concrete production-readiness gaps are currently tracked. The notes below record implemented
coverage and future maintenance areas to keep guarded as the public API evolves.
## P0 - GA Blockers
These items are completed and remain part of the GA regression bar.
### 1. Shared Runtime Semantics And Diagnostics
Current state:
- Shared config is normalized and passed into generated runtime code.
- `singleton`, `requiredVersion`, and `strictVersion` are present in generated share metadata.
- Local fallback and `import: false` paths exist.
- Several shared prebuild and subpath tests exist.
- `allowNodeModulesSuffixMatch` is normalized, passed to generated runtime share config, matched
against pnpm/nested `node_modules` suffixes in the proxy path, and exposed in manifest
diagnostics.
- `vite-plugin-federation/runtime` installs a shared diagnostics runtime plugin and wraps
`loadShare`/`loadShareSync`.
- `getFederationDebugInfo()` exposes registered shared providers, the active share scope, and a
shared resolution graph with candidate versions, selected provider, fallback source, and decision
reason.
- `MFV-003` diagnostics are emitted for shared misses, local fallback selection, semver range
mismatches, and strict shared resolution failures observed by the runtime wrapper/plugin.
- Shared resolution graph entries include deterministic candidate ordering, selected strategy, and
`versionSatisfied` for `requiredVersion` comparisons.
- Singleton conflicts emit `MFV-003` with the selected provider/version and rejected candidate
versions.
- Shared packages can opt into `strictSingleton: true` to upgrade singleton conflicts from
diagnostic-only warnings to `MFV-003` runtime errors.
- Generated local shared providers and internal `loadShare` requests include `sourcePath` and
`resolvedImportSource` annotations so runtime debug snapshots can identify the exact local import
or resolved file path involved in shared resolution.
- Runtime/unit coverage validates package-root, trailing-slash subpath, and pnpm
`node_modules`-suffix shared matching for Vue-style subpath consumers, scoped workspace packages,
and pnpm-resolved package paths.
- `examples/shared-negotiation-host` and `examples/shared-negotiation-remote` validate
deterministic `loaded-first` and `version-first` provider selection in real production
build/preview flows.
- `examples/shared-strict-fallback-app`, `examples/shared-host-only-host`, and
`examples/shared-host-only-remote` validate browser-side `strictVersion` local fallback and
actionable `import: false` host-only shared errors in production build/preview flows.
- Generated shared wrappers throw an actionable host-only error when `import: false` is configured
and no host provider is available, and normal shared wrappers fall back to the local prebuild
source when runtime share resolution returns `false`.
Maintenance guardrails:
- No further shared runtime P0 gaps are currently tracked beyond keeping unit and e2e shared
diagnostics coverage green.
Acceptance criteria:
- A host can explain why every shared package was resolved from host, remote, or local fallback.
- A singleton/version conflict creates an actionable diagnostic.
- pnpm workspace and symlinked dependency layouts pass both unit and e2e tests.
- `getFederationDebugInfo()` exposes enough shared graph data to debug production incidents.
Implemented commit themes:
- `feat: add shared resolution graph diagnostics`
- `feat: support node_modules suffix shared matching`
- `test: add shared version negotiation coverage`
### 2. Type System End-to-End Coverage
Current state:
- `@module-federation/dts-plugin` is integrated.
- Manifest metadata can describe type artifact names.
- Dev worker and dynamic remote type hints are wired.
- `examples/dts-remote` validates build-time remote type generation, manifest type metadata, API
declaration output, and zipped expose declarations.
- `examples/dts-host` validates build-time host type consumption from manifest-derived
`remoteTypeUrls`, `typesOnBuild`, `consumeAPITypes`, and `abortOnError` happy-path behavior.
- `packages/vite-plugin-federation/e2e/dts-dev-hot-sync.mjs` validates dev-time live DTS serving,
remote type regeneration, host hot type sync, and no-restart host type-checking after a remote
API expansion.
- Build-time DTS generation/consumption errors are rethrown when the corresponding scoped
`abortOnError` option is enabled.
- `examples/dts-host` validates unavailable remote type artifacts fail build-time consumption with
a clear missing-types diagnostic when `abortOnError` is enabled.
- `examples/dts-host` validates corrupt remote type archives fail build-time consumption with the
same deterministic missing-types diagnostic.
- `examples/dts-remote` validates invalid TypeScript exposes fail build-time generation when
`generateTypes.abortOnError` is enabled.
- Unit coverage validates `displayErrorInTerminal`, disabled `generateTypes`/`consumeTypes`, and
fully disabled DTS behavior.
- Exact DTS workflows, defaults, and recommended production settings are documented in
`docs/dts-workflows.md`.
- Framework runtime examples are covered separately by Vue/Svelte/Lit production builds; the
TypeScript/DTS contract is covered by the dedicated TS producer/consumer examples.
Maintenance guardrails:
- No further Type System P0 gaps are currently tracked beyond adding framework-specific DTS
fixtures if a concrete Vue/Svelte/Lit type-generation requirement is introduced later.
Acceptance criteria:
- A host can type-check imports from a remote without manual type copying.
- Dev type changes are reflected without restarting the host.
- DTS failures have clear diagnostics and predictable `abortOnError` behavior.
- Type artifacts are covered in CI, not only by unit-level plugin smoke tests.
Implemented commit themes:
- `test: add federation dts generation e2e`
- `test: add federation dts consumption e2e`
- `docs: document federation dts workflows`
### 3. SSR Production Hardening
Current state:
- Node target selection exists.
- `ssrRemoteEntry` is emitted and consumed.
- React SSR host/remote example builds.
- Manifest-driven asset/preload helper exists.
- A dedicated SSR Playwright config validates manifest remote server rendering and browser
hydration.
- Runtime SSR diagnostics preserve manifest URL, selected remote entry, target, share scope, and
remote alias/name on `lastLoadError`.
- Node SSR entry loader failures are wrapped with actionable `MFV-004` guidance for server-side
fetch reachability, ESM SSR output, and the current `--experimental-vm-modules` requirement.
- The React SSR example injects server-side federation debug data into the HTML, recreates matching
browser-side hydration debug data, and fails hydration early if manifest URL, remote id, share
scope, or React version diverges.
- SSR e2e validates request-level HTML, server/client manifest consistency, React singleton version
consistency, selected node/web entries, remote preload links, host `ssr.external`, and remote
`ssrRemoteEntry` output.
- The production runtime guide documents SSR deployment patterns, public path/proxy behavior,
`ssr.external`/`ssr.noExternal` expectations, and the VM module loader migration strategy.
Maintenance guardrails:
- No further SSR-specific P0 gaps are currently tracked beyond keeping the SSR example and e2e
matrix green in CI.
Acceptance criteria:
- CI proves SSR host can consume remote and hydrate successfully.
- Server and browser use consistent share scopes and remote manifests.
- SSR failure modes produce actionable error codes and debug snapshots.
- Users have a documented deployment recipe for SSR remotes.
Implemented commit themes:
- `test: add ssr manifest consumption e2e`
- `feat: improve ssr remote diagnostics`
- `docs: document ssr deployment patterns`
### 4. Manifest Protocol And Schema Freezing
Current state:
- `mf-manifest.json`, `mf-stats.json`, and `mf-debug.json` are emitted.
- Runtime validates a minimal manifest shape.
- Build metadata and plugin version are traceable.
- `schemaVersion` is emitted for manifest, stats, and debug artifacts.
- JSON Schemas are committed under `docs/schemas`.
- Runtime accepts missing legacy schema versions and same-major future versions, while rejecting
unsupported major versions.
- Runtime validates malformed known manifest fields and enforces target-specific entry requirements
during manifest remote registration.
- Schema compatibility fixtures cover exact `1.0.0`, legacy, same-major, and future-major manifest
cases.
- Generated manifests include `release.id`, `metaData.buildInfo.releaseId`, and entry-level
`integrity`/`contentHash` metadata for remote entries.
- Manifest remotes can opt into runtime remote-entry verification through
`registerManifestRemote(..., { integrity: true | { mode } })`, with debug snapshots recording
successful and failed integrity checks.
- Backward/forward compatibility rules and manifest hosting/cache rules are documented as part of
the manifest protocol.
Maintenance guardrails:
- No further manifest protocol gaps are currently tracked beyond keeping schema and integrity
checks covered in CI.
Acceptance criteria:
- Every generated manifest validates against a committed schema.
- Runtime errors distinguish malformed manifests from unavailable manifests.
- Future schema changes can be tested against fixtures.
- External tools can safely consume the manifest and stats artifacts.
Implemented commit themes:
- `test: add manifest schema compatibility fixtures`
- `feat: add target-specific manifest validation`
- `feat: add manifest integrity metadata`
### 5. Compatibility Matrix
Current state:
- Basic OriginJS-style `virtual:__federation__` shim exists.
- `from` and `format` are normalized enough for the main migration path.
- `varFilename` can generate an additional var-style remote entry.
- Unit coverage executes the OriginJS shim APIs for static and dynamic remotes:
`__federation_method_setRemote`, `__federation_method_getRemote`,
`__federation_method_ensure`, `__federation_method_unwrapDefault`, and
`__federation_method_wrapDefault`.
- `examples/originjs-compat-host` and `packages/vite-plugin-federation/e2e/originjs-compat.spec.ts`
validate browser-side OriginJS migration APIs against real `remoteEntry.js` and
`remoteEntry.var.js` assets, while also proving manifest-first and remoteEntry-first remotes can
coexist on the same host.
- The OriginJS compatibility shim now throws `MFV-005` for unsupported legacy `format` values and
missing `systemjs` runtimes, and emits a one-time `MFV-007` warning for unsupported `from`
values.
- `examples/react-remote` exposes `./ManualCssButton` with `dontAppendStylesToHead: true`, and
enables `bundleAllCSS: true` so `examples/originjs-compat-host` can verify the host receives the
manual CSS href bucket and injects the stylesheet explicitly.
- `examples/webpack-systemjs-remote` gives the repository a real webpack Module Federation remote
built with `library.type = 'system'`, and `examples/originjs-compat-host` now injects a local
SystemJS runtime asset before consuming that remote through `virtual:__federation__`.
- A compatibility matrix documents supported, partially supported, and unsupported remote formats,
`from` combinations, OriginJS APIs, and CSS migration behavior.
Maintenance guardrails:
- No further compatibility-specific gaps are currently tracked beyond keeping the matrix covered in CI.
Acceptance criteria:
- Common OriginJS migration paths work without rewriting app code.
- Unsupported compatibility combinations fail with clear messages.
- Compatibility logic stays isolated from the main manifest-first runtime path.
Implemented commit themes:
- `test: add originjs compatibility e2e`
- `feat: harden legacy remote format adapters`
- `docs: add compatibility matrix`
## P1 - Production Quality Improvements
These production-quality improvements are completed and should stay covered as the plugin evolves.
### 6. Dev HMR Beyond Full Reload
Current state:
- Remote dev updates can trigger host-side refresh behavior.
- CSS and type update events exist.
- Unit and one Playwright e2e cover the current dev remote HMR path.
- Remote update payloads include strategy, reason, batch id, and a small expose dependency graph so
hosts/devtools can explain why partial reload, style update, type update, or full reload was used.
- Remote file events are debounced and duplicate file changes are batched before broadcasting.
- Host partial reload uses Vite `server.reloadModule()` for matching remote virtual modules and
falls back to full reload when the module cannot be matched or Vite cannot reload it.
- Style and type updates remain page-state preserving when the host can refresh stylesheet links or
type data without reloading.
- `docs/dev-hmr.md` documents strategies, batching, browser events, and fallback rules.
Maintenance guardrails:
- No further Dev HMR P1 gaps are currently tracked beyond adding framework-specific HMR e2e if a
framework-specific HMR contract is introduced later.
Acceptance criteria:
- Remote component edits update host without full page reload when the framework supports it.
- Unsupported HMR cases fall back predictably.
- Devtools or debug output explains the chosen reload strategy.
Implemented commit themes:
- `feat: add remote expose partial hmr graph`
- `test: add react remote partial hmr e2e`
- `docs: document dev hmr fallback behavior`
### 7. DevTools And Observability
Current state:
- `mf-debug.json` exists.
- `getFederationDebugInfo()` exposes runtime state.
- Browser runtime dispatches debug events.
- The browser global `window.__VITE_PLUGIN_FEDERATION_DEVTOOLS__` now exposes a documented
`contractVersion`, bounded event retention, app payloads, the latest runtime snapshot, and
`exportSnapshot()`/`copySnapshot()` helpers.
- The devtools endpoint advertises overlay capabilities and serves the same payload shape injected
into the page.
- The built-in overlay renders runtime errors, registered remotes, manifest fetch timeline, shared
resolution graph, preload state, and recent devtools events.
- Unit coverage validates endpoint payload shape, injected overlay panels, runtime event publishing,
and event retention behavior.
- `docs/devtools-runtime-contract.md` documents the stable global contract for external tooling.
Maintenance guardrails:
- No further DevTools-specific P1 gaps are currently tracked beyond keeping the overlay optional and
the contract backward compatible.
Acceptance criteria:
- A developer can inspect remotes, shared modules, manifest fetches, and recent errors in one place.
- Runtime debug information is stable enough for external tooling.
- DevTools stays optional and has no production overhead when disabled.
Implemented commit themes:
- `feat: expand federation devtools overlay`
- `docs: document devtools runtime contract`
- `test: cover devtools debug snapshots`
### 8. Runtime Resilience And Rollout Controls
Current state:
- Manifest fetch has timeout, retry, cache TTL, force refresh, and request collapsing.
- Remote refresh can invalidate manifest cache and update registered remotes.
- Manifest fetch can try an ordered `fallbackUrls` list, and relative remote entries resolve from
the manifest origin that actually succeeded.
- `staleWhileRevalidate` can serve an expired cached manifest immediately while a background
refresh updates cache and debug snapshots.
- Manifest circuit breaker controls can stop repeatedly failing remote manifests for a bounded
cooldown window, with `circuit-open` events in the manifest fetch timeline.
- Runtime hooks/telemetry cover manifest fetch, manifest remote registration, manifest remote load,
and remote refresh with `before`/`after`/`error` stages and latency fields.
- Runtime debug snapshots expose fallback `sourceUrl` and manifest circuit breaker state.
- Remote-entry integrity checks are available when manifest entry hashes are emitted and runtime
verification is enabled.
- Route-level fallback recipes are documented in the production runtime guide.
Maintenance guardrails:
- No further runtime resilience P1 gaps are currently tracked beyond keeping rollout controls
covered by unit tests and docs.
Acceptance criteria:
- Hosts can safely roll out remotes across CDN propagation windows.
- Repeated remote failures can be observed and throttled.
- Runtime controls are documented and covered by unit tests.
Implemented commit themes:
- `feat: add manifest fallback urls`
- `feat: add runtime telemetry hooks`
- `docs: add remote rollout recipes`
### 9. Compiler Adapter Robustness
Current state:
- Vite/Rolldown differences are handled in several places.
- Control chunks, TLA placeholders, and manual chunk protection exist.
- Remote import named export transforms are covered by unit tests.
- Remote named export transforms now emit sourcemaps with `source` and `sourcesContent`, and unit
tests validate those fields.
- Control chunk sanitizer tests snapshot key output shapes for preload helper removal,
load-share preload helper inlining, side-effect import removal, and Vite 8-style helpers.
- `docs/compiler-adapter.md` documents Vite/Rolldown adapter rules, control chunk constraints, Vite
version policy, and the intentional `tsup` CJS named/default export warning.
Maintenance guardrails:
- No further compiler adapter P1 gaps are currently tracked beyond adding explicit multi-version
Vite matrix jobs when the example matrix is expanded.
Acceptance criteria:
- Compiler behavior is deterministic across supported Vite versions.
- Manual chunk customization cannot break federation bootstrap ordering.
- Transforms preserve useful source maps.
Implemented commit themes:
- `test: add vite version compatibility matrix`
- `test: add control chunk output snapshots`
- `fix: stabilize federation transform sourcemaps`
## P2 - Ecosystem And Documentation Completeness
These adoption and maintenance improvements are completed and should stay aligned with real
projects.
### 10. Example And E2E Matrix
Current state:
- React host/remote examples exist.
- Vue host/remote examples validate SFC remotes, manifest consumption, and shared Vue singleton
metadata in production builds.
- Svelte host/remote examples validate Svelte component remotes, manifest consumption, and shared
Svelte singleton metadata in production builds.
- Lit host/remote examples validate web component remotes, manifest consumption, and shared Lit
singleton metadata in production builds.
- `examples/multi-remote-host` validates one host consuming React and Lit manifest remotes in the
same runtime.
- A dedicated multi-remote Playwright e2e covers the React + Lit manifest remote combination and is
included in the scheduled/manual Extended E2E workflow.
- `examples/workspace-shared-lib`, `examples/workspace-shared-remote`, and
`examples/workspace-shared-host` validate pnpm workspace symlink shared packages, exact subpath
matching, `allowNodeModulesSuffixMatch`, and workspace package diagnostics in production
build/preview flows.
- React SSR host exists.
- Dev remote HMR e2e exists.
- `test:e2e:browser-matrix` reuses the multi-remote manifest scenario across Chromium, Firefox,
and WebKit in the scheduled/manual Extended E2E workflow.
Maintenance guardrails:
- No further example/e2e matrix gaps are currently tracked beyond keeping the scheduled browser
matrix within CI time budgets.
Acceptance criteria:
- Examples cover the main framework and deployment patterns users will copy.
- E2E tests cover at least one path for every production-critical feature.
- Example commands are documented and run through CI or a scheduled workflow.
Implemented commit themes:
- `example: add vue federation example`
- `example: add dts federation example`
- `test: add multi-remote federation e2e`
### 11. API Reference And Migration Documentation
Current state:
- README includes quick start and production runtime entry.
- `docs/production-runtime.md` documents manifest runtime controls.
- `docs/plugin-api.md` documents plugin options for remotes, hosts, shared packages, manifest
output, devtools, DTS, and compatibility.
- `docs/runtime-api.md` documents `vite-plugin-federation/runtime`, manifest loading, rollout
controls, SSR target selection, integrity, hooks, preload helpers, and debug APIs.
- `docs/manifest-protocol.md` documents manifest schema rules, compatibility fixtures, and hosting
guidance.
- `docs/originjs-migration.md` documents the OriginJS migration path, virtual API mapping, CSS
migration, and webpack SystemJS compatibility.
- `docs/troubleshooting.md` is organized by `MFV-*` error code and links errors to debug snapshot
fields and recommended actions.
- Existing DTS, compatibility matrix, production SSR, devtools contract, and release checklist docs
are linked from README.
Maintenance guardrails:
- No further API/migration documentation gaps are currently tracked beyond keeping references in
sync with public API changes.
Acceptance criteria:
- A new user can configure host, remote, shared, DTS, SSR, and compat modes from docs alone.
- Error codes link to troubleshooting steps.
- Migration users can predict required code/config changes.
Implemented commit themes:
- `docs: add plugin api reference`
- `docs: add originjs migration guide`
- `docs: add troubleshooting guide`
### 12. CI, Release, And Package Quality
Current state:
- CI runs `pnpm check`.
- CI now installs a clean temporary Vite app from a real `pnpm pack` tarball and verifies the
package can build there.
- Package smoke also verifies `sideEffects: false` and checks that production output does not
include the devtools overlay bootstrap.
- Release workflow validates `pnpm pack --dry-run` before publishing.
- Release workflow is manual-only.
- A separate manual/scheduled Extended E2E workflow runs compatibility, shared runtime, SSR, and DTS
dev e2e suites on Node 22 without adding release workflow pressure to normal pushes.
- `docs/release-checklist.md` documents Changeset policy, pre-`1.0.0` versioning policy, package
quality gates, release steps, and rollback notes.
- Package exports include root and runtime entry.
- Build produces ESM, CJS, and DTS.
Maintenance guardrails:
- No further CI/release P1 gaps are currently tracked beyond keeping release docs and workflows
aligned with package policy.
Acceptance criteria:
- A generated package tarball can be installed and used in a clean Vite app.
- CI catches broken exports, missing files, and invalid generated types.
- Release steps are reproducible and documented.
Implemented commit themes:
- `ci: add package smoke test`
- `ci: add extended e2e workflow`
- `docs: add release checklist`
## P3 - Advanced Differentiators
These differentiators are implemented and remain optional extension points for future concrete
requirements.
### 13. Advanced Preload And Performance Optimization
Current state:
- `createFederationManifestPreloadPlan()` generates route-level preload plans from actual
route-to-expose usage.
- Preload plans support `asyncChunkPolicy` values for no async chunks, async CSS, async JS, or all
async chunks.
- The manifest schema accepts optional `preload` hints for route-critical assets and shared runtime
assets that are not directly tied to one expose.
- `warmFederationRemotes()` registers selected manifest remotes and optionally calls runtime
`preloadRemote` for host-side warmups.
- `getFederationDebugInfo().runtime.remoteLoadMetrics` records registration, load, and total
durations for remote module load waterfalls.
- Unit coverage validates route preload plans, warm remote behavior, and remote load metrics.
- `docs/preload-performance.md` documents the API and performance budget guidance.
Maintenance guardrails:
- No further advanced preload/performance P3 gaps are currently tracked beyond adding broader
browser-level budget checks when the example matrix is expanded.
Acceptance criteria:
- Hosts can intentionally warm critical remotes without overfetching.
- Preload behavior is observable and tunable.
### 14. Security And Supply Chain Controls
Current state:
- Manifest schema defines optional `integrity` and `contentHash` fields for remote entries.
- Manifest schema also accepts object-form expose/preload asset references with optional
`integrity` and `contentHash` metadata.
- `registerManifestRemote(..., { integrity })` verifies manifest-declared remote entries before
registration.
- `verifyFederationManifestAssets()` verifies annotated expose and preload assets, supports
`prefer-integrity`, `integrity`, `content-hash`, and `both` modes, and can require integrity
metadata for every declared asset.
- Debug snapshots record successful and failed integrity checks in
`runtime.manifestIntegrityChecks`.
- `docs/security.md` documents CORS, CSP, Trusted Types, remote script policy, private manifests,
authenticated loading, and signed manifest design notes.
Maintenance guardrails:
- No further security/supply-chain P3 gaps are currently tracked beyond integrating signed manifest
verification if a concrete key-management requirement is added later.
Acceptance criteria:
- Enterprise users have a clear security story for remote manifests and assets.
- Security controls are optional and do not complicate the default path.
### 15. Multi-Tenant And Runtime Instance Isolation
Current state:
- Manifest runtime options accept `runtimeKey` for tenant or experiment partitioning.
- `runtimeKey` isolates manifest caches, pending manifest requests, manifest source URL tracking,
circuit breaker state, registered manifest remote debug records, and remote load metrics.
- `createFederationRuntimeScope(runtimeKey)` exposes scoped wrappers for manifest fetch,
registration, remote loading, refresh, warmups, and filtered debug snapshots.
- Runtime hooks and debug timeline entries include `runtimeKey` when a scoped runtime is used.
- Unit coverage validates same-URL manifest cache isolation and same-alias registered manifest
remote diagnostics across separate runtime keys/share scopes.
- `docs/multi-tenant.md` documents scoped runtime usage and tenant-specific share scope guidance.
Maintenance guardrails:
- No further multi-tenant P3 gaps are currently tracked beyond adding a browser example if a
concrete tenant shell scenario is added to the example matrix.
Acceptance criteria:
- A shell can host multiple tenants or experiments without remote registry collisions.
- Debug snapshots identify the relevant runtime instance.
---
# 1.0 Article
Source: docs/articles/vite-plugin-federation-v1.0.md
# vite-plugin-federation 1.0: Bringing Module Federation Into the Production Era for Vite
> A manifest-first, SSR-aware, multi-tenant, observable Module Federation 2.x runtime wrapper for Vite 5 / 6 / 7 / 8, including Rolldown.
> Published package status: as of the npm registry check on 2026-05-13, `vite-plugin-federation` is published as `1.0.0` and the npm `latest` dist-tag points to `1.0.0`.
```bash
pnpm add -D vite-plugin-federation
```
---
## 1. Why Micro-Frontends Still Have Production Gaps in the Vite Ecosystem
Module Federation became the de facto micro-frontend standard with Webpack 5. Anyone who has tried to land a micro-frontend system in the Vite ecosystem has likely run into the same set of constraints:
- **Vite does not implement Webpack's container protocol natively.** The two mainstream community options take different approaches: `@originjs/vite-plugin-federation` ships its own runtime around `virtual:__federation__`, while `@module-federation/vite` connects directly to `@module-federation/runtime`. Both are useful, but production governance often still has to be implemented in application code.
- **Production systems need a long list of resilience controls:** manifest caching and TTL, stale-while-revalidate, timeouts, jittered retries, circuit breakers, fallback URLs, request coalescing, SRI verification, multi-tenant isolation, CSP / Trusted Types, signed manifests, SSR, and observability. Without a unified runtime wrapper, these concerns tend to be scattered across the host application, gateway, CDN, and monitoring code.
- **Remote development experience has historically required tradeoffs.** With `@originjs`, remote development commonly relies on `vite build --watch`. In `@module-federation/vite@1.15.4`, `remoteHmr: true` includes websocket metadata, reconnect handling, and a React Fast Refresh path, while non-native HMR cases still fall back to a full page reload. Shared-dependency diagnostics and singleton conflict reporting often still need project-side conventions.
- **Vite 8 plus Rolldown introduces another compatibility surface.** Bundle behavior, `modulePreload` helper rewriting, `output.codeSplitting.groups`, and top-level await all require federation plugins to handle another layer of compiler-specific behavior.
`vite-plugin-federation` 1.0 is designed to address that full checklist at once: **manifest-first loading, native SSR support, multi-tenant isolation, security and governance controls, deep observability, and one API across Vite 5 through Vite 8, including Rolldown.**
---
## 2. Positioning and Core Differences
| Dimension | `vite-plugin-federation` | `@module-federation/vite` | `@originjs/vite-plugin-federation` |
| ---------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| Vite versions | `^5 \|\| ^6 \|\| ^7 \|\| ^8` | `^5 \|\| ^6 \|\| ^7 \|\| ^8` | No peer constraint; repository dev dependency is `vite@^4.0.5` |
| Runtime | `@module-federation/runtime@2.3.3` pinned exactly, plus a production-oriented wrapper layer | Direct official runtime/core APIs; current npm `1.15.4` uses runtime stack `2.4.0` | Custom runtime |
| Manifest protocol | `mf-manifest.json` + `mf-stats.json` + `mf-debug.json` | `mf-manifest.json` + `mf-stats.json`, including `metaData.ssrRemoteEntry` | None |
| Production loading API | `loadRemoteFromManifest`, with cache TTL, SWR, retries, timeout, circuit breaker, fallback, and SRI | Manifest remotes via `loadRemote`; retry/fallback via official runtime plugins or hooks | Not supported |
| Multi-tenant isolation | `createFederationRuntimeScope(runtimeKey)`, with isolated cache and circuit breaker state | Lower-level runtime instances/share scopes; no tenant `runtimeKey` wrapper | None |
| Remote dev HMR | Opt-in, classified as `partial` / `style` / `types` / `full` | `remoteHmr: true` has WS/reconnect plumbing, React native strategy, and reload fallback | Remote development commonly depends on build watch |
| SSR | Separate SSR entry + `createServerFederationInstance` + asset preload collection | `target: 'node'` plus `ssrRemoteEntry` metadata; no server-specific helper | Not supported |
| OriginJS compatibility | Default `virtual:__federation__` compatibility bridge | Not supported | Native API |
| Error codes | Stable `MFV-001` through `MFV-007` | Runtime has its own error codes/logging | None |
| Browser e2e | `browser-matrix` covers Chromium / Firefox / WebKit; other e2e suites run according to their own configs | Depends on that repository's CI | - |
The repository's `COMPARISON.md` records the structured comparison and evidence notes. External package versions change quickly, so exact version claims should be checked against the npm registry and published package source. The versions in this article, `vite-plugin-federation@1.0.0`, `@module-federation/vite@1.15.4`, `@module-federation/runtime@2.4.0`, and `@originjs/vite-plugin-federation@1.4.1`, were verified against the npm registry on 2026-05-13.
---
## 3. Architecture: Build-Time Plugins, Virtual Modules, Runtime, and Protocol
Structurally, `vite-plugin-federation` is a **plugin composition**, not a single monolithic plugin. Calling `federation()` injects a set of cooperating Vite plugins and pushes the core capability into four orthogonal layers:
```text
+-------------------------------------------------------------+
| 1. Build-time plugin layer (packages/.../src/plugins/*.ts) |
| - early-init / proxy-remote-entry / proxy-remotes |
| - originjs-compat / remote-named-exports / dev-remote-hmr |
| - devtools / dts / mf-manifest / module-parse-end ... |
| | |
| 2. Virtual module layer (src/virtualModules/*.ts) |
| - remoteEntry / ssrRemoteEntry / hostAutoInit |
| - localSharedImportMap / loadShare / preBuildLib / exposes |
| | |
| 3. Runtime layer (src/runtime/index.ts, 4,366 lines) |
| - createFederationInstance / createServerFederationInstance |
| - fetchFederationManifest / loadRemoteFromManifest ... |
| - createFederationRuntimeScope / verifyFederationManifest...|
| | |
| 4. Manifest protocol (docs/manifest-protocol.md, v1.0.0) |
| - mf-manifest.json / mf-stats.json / mf-debug.json |
| - JSON Schema + fixtures |
+-------------------------------------------------------------+
```
The reason for this split is simple: **the hard part of production Module Federation is not importing code.** The hard part is allowing three different surfaces to evolve independently:
- **Build artifacts** are the public contract: manifest, entry files, assets, and type archives.
- **Runtime behavior** can evolve internally: caching policy, circuit breakers, SRI, and multi-tenant scope isolation.
- **Developer experience** can evolve separately: HMR, DevTools, and DTS synchronization.
This separation lets the plugin handle manifest tolerance independently from Vite compiler adaptation. It also allows users to import only the runtime subpath, `vite-plugin-federation/runtime`, for SSR, CDN warming, or tenant-isolated loading.
---
## 4. Manifest Protocol: Turning a Remote Into a Governable Deployment Unit
The manifest is the most important design choice in this project. Every remote build with `manifest: true` emits three files:
| File | Role |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `mf-manifest.json` | Runtime contract with the host: `name`, `exposes`, `shared`, `remoteEntry`, `ssrRemoteEntry`, `preload`, `integrity`, and `buildInfo` |
| `mf-stats.json` | Asset and shared-dependency analysis for external tooling |
| `mf-debug.json` | Snapshot for CI and diagnostics, including plugin options, capability flags, and normalized results |
The manifest schema includes `schemaVersion: "1.0.0"`. The runtime guarantees the following behavior:
- Missing version: treated as a legacy manifest.
- Same major version, for example `1.x.y`: accepted.
- Different major version: rejected with **`MFV-004`**.
- Field deserialization failure: also reported as `MFV-004`.
The manifest also carries deployment semantics. The repository recommends the following cache policy:
```text
mf-manifest.json Cache-Control: no-cache
remoteEntry*.js Cache-Control: public, max-age=31536000, immutable
assets/* Cache-Control: public, max-age=31536000, immutable
@mf-types* Cache-Control: public, max-age=31536000, immutable
```
The manifest is short-lived; all referenced assets are immutable. That makes rollback a matter of switching the manifest back to a previous version, rather than purging the CDN or fighting ETags inside a worker. The paired `release.id` and `contentHash` fields also make incident analysis precise: you can identify exactly which build produced the failing artifact.
The JSON Schemas live in `docs/schemas/`. CI validates fixtures for three compatibility paths: legacy manifests, same-major manifests, and next-major manifests. Protocol changes therefore cannot silently break existing hosts.
---
## 5. Runtime: Production Behavior Built Into the SDK
`vite-plugin-federation/runtime` is the other half of the project. It wraps `@module-federation/runtime` with production behavior that most real micro-frontend systems need, but few teams want to reimplement for every host.
### 5.1 One `loadRemoteFromManifest` Call Covers the Production Parameters
```ts
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
const mod = await loadRemoteFromManifest('catalog/Button', catalogManifestUrl, {
cacheTtl: 30_000, // 30s in-memory cache
staleWhileRevalidate: true, // serve stale data first, refresh silently in the background
fallbackUrls: [backupManifestUrl], // fall back automatically if the primary source fails
retries: 2, // jittered retry on failure
retryDelay: 200,
timeout: 4_000, // per-request timeout
circuitBreaker: { failureThreshold: 3, cooldownMs: 30_000 }, // open for 30s after 3 failures
integrity: { mode: 'prefer-integrity' }, // SRI / content-hash verification
fetchInit: { credentials: 'include' }, // private manifests
hooks: {
manifestFetch: log,
remoteRegister: log,
remoteLoad: log,
remoteRefresh: log,
},
});
```
That call does far more than `fetch + import`:
- **Request coalescing:** concurrent requests for the same manifest share one in-flight Promise.
- **Source tracking:** cache entries record `sourceUrl`, `fetchedAt`, `expiresAt`, and `runtimeKey`, so you know whether the primary or fallback URL was used.
- **Metrics collection:** every success and failure is written to `remoteLoadMetrics`, including `registrationDurationMs`, `loadDurationMs`, and total `durationMs`.
- **Circuit breaker state and last error context:** `lastLoadError` includes the entry, target, and share scope, making it easier to route into Sentry, Grafana, or another telemetry system.
- **SRI / content-hash verification:** four modes are supported: `prefer-integrity`, `integrity`, `content-hash`, and `both`. `verifyFederationManifestAssets()` can also validate all expose and preload assets during warmup.
### 5.2 Multi-Tenant Isolation With `createFederationRuntimeScope`
This capability is not directly provided by either of the two mainstream Vite Module Federation plugins compared in this article. If a shell runs multiple tenant-specific remote sets, A/B experiment groups, or white-label products in the same page, each tenant can get its own runtime scope:
```ts
import { createFederationRuntimeScope } from 'vite-plugin-federation/runtime';
const tenantA = createFederationRuntimeScope('tenant-a');
const tenantB = createFederationRuntimeScope('tenant-b');
await tenantA.registerManifestRemote('shop', tenantAManifestUrl, { shareScope: 'tenant-a' });
await tenantB.registerManifestRemote('shop', tenantBManifestUrl, { shareScope: 'tenant-b' });
const PageA = await tenantA.loadRemoteFromManifest('shop/Page', tenantAManifestUrl);
```
Each scope has its own manifest cache, in-flight request map, circuit breakers, debug records, and load metrics. A failed remote in one tenant cannot poison another tenant's failure budget or cache key. `getFederationRuntimeScopeDebugInfo(runtimeKey)` returns a snapshot for one tenant only, which keeps telemetry isolated as well.
### 5.3 Route-Aware Preloading
```ts
import { createFederationManifestPreloadPlan } from 'vite-plugin-federation/runtime';
const plan = createFederationManifestPreloadPlan(
catalogManifestUrl,
manifest,
{
'/checkout': ['./Cart', './PaymentForm'],
'/account': './AccountHome',
},
{
asyncChunkPolicy: 'css', // 'none' | 'js' | 'css' | 'all'
},
);
// plan.links -> [{ rel: 'modulepreload', href }, { rel: 'stylesheet', href }, ...]
```
The default policy, `'css'`, adds synchronous JS, synchronous CSS, and asynchronous CSS to `` tags, while leaving asynchronous JS to the framework's lazy-loading behavior. That is a pragmatic default for first-paint and LCP-sensitive routes. `warmFederationRemotes` can register and warm multiple remotes at application startup; for critical routes, `asyncChunkPolicy: 'all'` can then push the core chunks into the document head.
### 5.4 Stable Error Codes and Observability
Most diagnosable runtime, build-time, and compatibility-layer failures carry an `MFV-NNN` code:
| Code | Meaning |
| --------- | -------------------------------------------------------------------------------- |
| `MFV-001` | Plugin configuration error |
| `MFV-002` | Shared key and alias conflict |
| `MFV-003` | Missing shared dependency, fallback, or strict-singleton conflict |
| `MFV-004` | Manifest fetch or validation failure, including `sourceUrl` |
| `MFV-005` | Missing expose or unsupported legacy `format` |
| `MFV-006` | `target: 'node'` cannot find a usable `ssrRemoteEntry` or `remoteEntry` fallback |
| `MFV-007` | Unsupported legacy `from` value or dynamic import rewrite warning |
`getFederationDebugInfo()` returns the current instance, registered remotes, manifest cache, fetch timeline, SRI check records, circuit breaker state, load metrics, and the shared-resolution graph, including rejected candidate versions. That makes it possible to inspect shared resolution with `console.table` instead of reverse-engineering why React, or any other singleton, was loaded twice.
---
## 6. Build-Time Behavior: Fixing the Sharp Edges in Bundle Output
Manifest and runtime behavior ultimately depend on the shape of the compiled output. This is the most compiler-sensitive part of the project, and it is also where federation plugins tend to fail in subtle ways.
### 6.1 The `early-init` Plugin and Virtual Modules
`createEarlyVirtualModulesPlugin` starts in the `config` hook with `enforce: 'pre'`:
- It creates the `__mf__virtual` directory early, avoiding Vite optimizer 504 errors such as "Outdated Optimize Dep".
- It writes `usedRemotes` and `usedShares` into `localSharedImportMap` early, so build-time tracing of `remoteEntry` does not encounter an empty `usedRemotes` array and produce surprising chunks.
- It handles `optimizeDeps.include / exclude` carefully under Vite 8 / Rolldown. A bare-specifier remote must not be prebundled into `_vite/deps/remote_x.js`, because that breaks named exports in frameworks such as Angular. At the same time, a local npm package with the same name must still be preserved, so the plugin probes with `getInstalledPackageJson` before deciding.
- Shared IDs such as Lit, which need to remain ESM-shaped in dev, are routed through `optimizeDeps.exclude`. Under Vite 8 / Rolldown, the plugin also avoids handing TLA-bearing virtual modules such as `loadShare` to the dependency optimizer for CJS conversion. Otherwise, shared modules can become unresolved Promises, which breaks plugins such as Pinia that depend on shared initialization order.
### 6.2 Chunk Isolation and Rolldown Adaptation
Module Federation's `runtimeInit` and `loadShare` modules are top-level-await control chunks. They must not be merged with business code. The plugin handles both Rollup and Rolldown inside `module-federation-esm-shims`:
```ts
const mfManualChunks = function (id) {
if (id.includes(runtimeInitId)) return 'runtimeInit';
if (id.includes(LOAD_SHARE_TAG))
return id.match(/([^/\\]+__loadShare__[^/\\]+)/)?.[1] ?? 'loadShare';
};
```
If the user configures `output.manualChunks`, the plugin prints a one-time warning and replaces it with federation-specific `mfManualChunks` rules. Non-federation modules return to the bundler's default chunking rather than continuing to use the user's custom vendor grouping. That tradeoff protects `runtimeInit` and `loadShare` from being merged into application chunks and breaking their top-level-await semantics.
`output.codeSplitting.groups`, introduced in Vite 8, is also removed. It can bind share-init wrappers and dependency modules together, causing standalone remotes to fail before mounting. The full compiler-adapter notes are documented in `docs/compiler-adapter.md`.
### 6.3 `pluginRemoteNamedExports`: Named Remote Imports Under Rolldown
Rollup-era builds could rely on `syntheticNamedExports` for named exports from remote modules. Rolldown, used by Vite 8, does not support that hook. This plugin rewrites consumer code instead:
```ts
import { foo } from 'remote/x';
// becomes
import { __moduleExports as __mf_ns_0 } from 'remote/x';
const { foo } = __mf_ns_0;
```
Dynamic `import('remote/x')` is wrapped with a `then` handler that expands `__moduleExports` into a namespace while preserving `default`. This allows Vite 8 + Rolldown users to keep writing ordinary ES named imports instead of changing application code for remote modules.
### 6.4 Cross-Origin `modulePreload` Helper Rewriting
Vite's default `modulePreload` helper computes dependencies from relative paths. When a remote is served from a cross-origin CDN, that base calculation is wrong. The plugin rewrites the helper across esbuild, terser, arrow-function, and regular-function output forms so that the base becomes `import.meta.url`. It also short-circuits `document` / `window` branches during SSR, preventing the helper itself from crashing during hydration.
### 6.5 SSR: `ssrRemoteEntry` as a First-Class Artifact
The plugin generates an additional SSR entry for remotes with `exposes`. On the host side, server loading chooses the Node-oriented entry when `target: 'node'` is passed, or when the runtime infers a non-browser environment from the absence of `window`:
- Remote builds emit an additional `*.ssr.js`, the `ssrRemoteEntry`. Build-side `target` / `build.ssr` affects the `ENV_TARGET` define, making browser or Node branches easier to tree-shake.
- The manifest records the artifact in `metaData.ssrRemoteEntry`. A Node host prefers the SSR entry when registering a remote. If the manifest does not declare an SSR entry, `remoteEntry` is used as a compatibility fallback. `MFV-006` is thrown only when no usable entry exists or the SSR entry structure is invalid.
- The runtime provides `createServerFederationInstance({ inBrowser: false })` and `collectFederationManifestPreloadLinks()`. The former evaluates ESM through Node's `vm` module; the latter returns deduplicated `` descriptors so streaming SSR can write critical remote assets into the document head.
- If Vite's `modulePreload` helper is evaluated during SSR, missing `document` / `window` globals no longer crash the helper itself. Browser global access inside business exposes still has to be written in an SSR-safe way.
The cost is that Node currently still needs `--experimental-vm-modules`. That requirement comes from the `SourceTextModule` bridge used for server-side entry loading, and the repository README and examples document it explicitly.
---
## 7. Developer Experience: Classified HMR, DevTools, and Live Type Synchronization
### 7.1 Classified HMR
`@module-federation/vite` already has remote-HMR websocket metadata, reconnect handling, a React native strategy, and a full-reload fallback. The difference here is that, after explicitly enabling `dev: { remoteHmr: true }`, this plugin classifies remote updates so the host can pick a narrower action when possible:
| Category | Trigger | Behavior |
| --------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `partial` | Changed file hits an expose or its dependency graph | Host calls `server.reloadModule()`; when the framework HMR boundary supports it, component state can be preserved, for example with a React Fast Refresh-compatible expose |
| `style` | CSS, SCSS, or Less file changed | Refresh only the matching stylesheet link |
| `types` | `.d.ts` file changed | Push through WebSocket to the host; no reload required |
| `full` | No expose graph or valid boundary is found, or partial update is not applicable | Fall back to full page reload |
Every update includes a `reason`, `batchId`, and a compact `dependencyGraph` with matched exposes, files, importer previews, and match patterns. Multiple file writes from one save are coalesced with a 25 ms debounce to avoid a reload storm.
### 7.2 DevTools
When `dev.devtools !== false`, which is the default, the plugin starts a `/__mf_devtools` endpoint and injects a browser panel. The panel reads from the contract-stable `window.__VITE_PLUGIN_FEDERATION_DEVTOOLS__` global and displays:
- Registered runtime and manifest remotes.
- Manifest fetch timeline.
- Shared-resolution graph, including rejected candidates.
- Last preload and last error.
- The latest 50 DevTools events.
Capacity, field names, and event names such as `vite-plugin-federation:debug` and `vite-plugin-federation:remote-update` are stable within the `1.x` contract, so external tools can integrate against them.
### 7.3 Live Type Synchronization
DTS output is no longer only a zip written after build. During development, the remote publishes `dist/.dev-server.zip` and `dist/.dev-server.d.ts`. The host syncs the latest declarations over the dev WebSocket, allowing it to type-check the current remote API without restarting the host dev server. This updates TypeScript's view of `loadRemote` hints and host-side type checking; it does not change runtime module values. In production builds, `typesOnBuild: true` fails the build when required types are unavailable, preventing a release with incompatible host and remote types.
---
## 8. Security and Supply Chain: From SRI to Signed Manifests
### 8.1 Built-In SRI and Content Hashes
During build, the plugin calculates `integrity` and `contentHash` for `remoteEntry` and `ssrRemoteEntry`, then writes them into the manifest. Expose and preload assets can also use object-form records with hashes. The runtime's four verification modes, `prefer-integrity`, `integrity`, `content-hash`, and `both`, cover different deployment combinations, while `verifyFederationManifestAssets()` can validate the full asset set during warmup.
### 8.2 Private and Signed Manifests
`fetchInit` plus a custom `fetch` function is the standard path for Bearer or cookie authentication. For high-assurance deployments, the repository's signed-manifest recipe intentionally keeps signature verification inside a custom `fetch` wrapper. The signature, key ID, and algorithm can come from headers or a sidecar file; after verification, the wrapper returns the `Response` to the runtime. This decouples the runtime from enterprise key-management systems and avoids embedding a KMS concern into the federation loader itself.
### 8.3 Explicit SSR Risk Boundaries
One warning in the documentation is especially important: **never allow tenant-controlled or user-controlled strings to become the SSR Node manifest URL directly.** A manifest remote runs server-side code; URL injection can become RCE or SSRF. The React SSR example disables query-based overrides by default. The e2e path enables `REACT_REMOTE_MANIFEST_QUERY_OVERRIDES=1` only with an allowlist.
The CSP / Trusted Types guidance is similarly direct. The manifest-first ESM path does not need inline scripts, and tenant-level CSP is safer than wildcard policies.
---
## 9. Compatibility: Smooth Migration From OriginJS and @module-federation/vite
### 9.1 OriginJS Users: The Default Shim Keeps Existing Code Running
`compat.virtualFederationShim` is enabled by default. The five most commonly used `virtual:__federation__` methods in OriginJS migrations have corresponding shims:
```ts
import {
__federation_method_setRemote,
__federation_method_getRemote,
__federation_method_ensure,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';
```
Legacy OriginJS paths such as `./Button` are normalized to `remote/Button` and then handled by `@module-federation/runtime`. `dontAppendStylesToHead: true` remains valid and maps to `css.inject: 'manual'`.
The repository includes `examples/originjs-compat-host` and `examples/webpack-systemjs-remote`, demonstrating a Vite host that uses the shim to load a real webpack remote with `library.type: 'system'`. The compat e2e suite covers that path. The three-browser `browser-matrix` covers cross-browser behavior for React and Lit manifest remotes; it does not claim that the compat case itself is part of the three-browser matrix.
### 9.2 @module-federation/vite Users: One Dependency Switch
```diff
- "@module-federation/vite": "^1.15.4"
+ "vite-plugin-federation": "^1.0.0"
```
```diff
- import { federation } from '@module-federation/vite';
+ import federation from 'vite-plugin-federation';
```
This package pins `@module-federation/runtime@2.3.3`, `@module-federation/sdk@2.3.3`, and `@module-federation/dts-plugin@2.3.3` exactly to one version set. It also aliases `@module-federation/runtime` imports in Vite config to the same bridge to reduce the chance of multiple federation states in one application.
One caveat matters: the current npm `@module-federation/vite@1.15.4` already uses MF runtime `2.4.0`. If your application directly depends on `@module-federation/runtime@2.4.x`, check the lockfile during migration. Existing direct runtime imports such as `registerPlugins`, `preloadRemote`, `registerRemotes`, and `loadRemote` are official runtime APIs; switch selected call sites to `vite-plugin-federation/runtime` when you want this package's cached/retried/integrity-checked manifest helpers and debug snapshot wrapper. The core configuration surfaces are close, and existing `manifest`, `dts`, `dev`, `runtimePlugins`, `shareStrategy`, and `shareScope` settings can usually be carried over. Custom options and lockfile differences should still be reviewed one by one.
The differences are explicit: `mf-debug.json` is new and can be ignored safely; `runtimeInit` / `loadShare` chunk isolation is enforced and replaces user `manualChunks` with a one-time warning; dev `remoteHmr` is opt-in and requires `dev: { remoteHmr: true }`. The full migration checklist is in `docs/migrate-from-module-federation-vite.md`.
---
## 10. Engineering Quality: Six Playwright Suites, Multi-Version Vite, and JSON Schema Validation
The 1.0 label is backed by executable quality gates:
- **Six Playwright e2e configs:** default, `compat`, `shared`, `multi-remote`, `ssr`, and `browser-matrix` with Chromium / Firefox / WebKit.
- **Vite peer matrix smoke tests:** the release tarball is built and run against Vite 5 / 6 / 7 / 8.
- **DTS hot-sync e2e:** `e2e/dts-dev-hot-sync.mjs` verifies type synchronization during dev hot updates.
- **Twenty-three examples:** React, Vue, Svelte, Lit, SSR, DTS, multi-remote, workspace sharing, shared negotiation, host-only, strict-singleton, OriginJS compatibility, and webpack/SystemJS interop.
- **JSON Schema plus fixtures:** `docs/schemas/` and `docs/fixtures/manifest-protocol/` cover legacy, same-major, and next-major manifests.
- **Public API contracts:** `docs/public-api-contract.md` fixes plugin options, runtime exports, and error codes; `docs/devtools-runtime-contract.md` fixes DevTools fields and event contracts.
- **GitHub Actions:** `ci.yml` validates pull requests, `extended-e2e.yml` runs the full e2e set on Mondays and manually, and `release.yml` is triggered by `v*.*.*` tags with `provenance: true`.
The repository also provides a local verification path:
```bash
pnpm install
pnpm check # format check + lint + typecheck + unit tests + build
pnpm examples:build # React + SSR + DTS smoke
pnpm test:vite-matrix:smoke # multi-major Vite smoke tests
pnpm --filter vite-plugin-federation test:e2e:browser-matrix
```
These commands answer the practical production-readiness question with executable evidence rather than README claims.
---
## 11. Getting Started in Three Steps
### Step 1: Remote
```ts
// remote/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'catalog',
filename: 'remoteEntry.js',
exposes: { './Button': './src/Button.tsx' },
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
'react/': { singleton: true, requiredVersion: '^19.2.4' },
'react-dom/': { singleton: true, requiredVersion: '^19.2.4' },
},
// manifest defaults to true and emits all three manifest artifacts
}),
],
build: { target: 'esnext', cssCodeSplit: true },
});
```
### Step 2: Host
```ts
// host/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'shell',
remotes: { catalog: 'https://cdn.example.com/catalog/mf-manifest.json' },
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
'react/': { singleton: true, requiredVersion: '^19.2.4' },
'react-dom/': { singleton: true, requiredVersion: '^19.2.4' },
},
}),
],
});
```
### Step 3: Load the Remote in Application Code
```tsx
import { lazy, Suspense, type ComponentType } from 'react';
import { loadRemoteFromManifest } from 'vite-plugin-federation/runtime';
type RemoteButtonModule = { default: ComponentType };
const CatalogButton = lazy(async () => {
const mod = await loadRemoteFromManifest(
'catalog/Button',
'https://cdn.example.com/catalog/mf-manifest.json',
{
cacheTtl: 30_000,
integrity: true,
retries: 2,
timeout: 4_000,
},
);
return { default: mod.default };
});
export const App = () => (
);
```
At this point the host already has 30-second manifest caching, automatic retries, entry SRI / content-hash verification, remote loading metrics, and stable error codes. DevTools are enabled by default in the dev server. Add `dev: { remoteHmr: true }` for classified remote HMR. Pass `target: 'node'` to `loadRemoteFromManifest` for SSR-first entry selection. Wrap calls with `createFederationRuntimeScope(tenantId)` to get multi-tenant isolation.
---
## 12. When to Choose It, and When Not To
Evaluate or adopt this repository's `vite-plugin-federation` 1.0.0 if you:
- Need a **production-grade host loader** with cache TTL, SWR, jittered retries, fallback URLs, circuit breakers, SRI, and request coalescing.
- Serve **multiple tenants, A/B experiment groups, or white-label products** in the same page. Among the two mainstream Vite MF plugins compared in this article, only this repository provides a `runtimeKey`-level runtime isolation API.
- Use **Node SSR** and need a server-aware federation instance plus asset preloading. The repository validates this path with the React SSR example and e2e suite. Vue, Svelte, Vinext, and similar frameworks can integrate through the same runtime path, but each project should still add its own SSR validation.
- Want **stable error codes** (`MFV-NNN`) and observable debug snapshots.
- Are **migrating away from OriginJS** and want existing `virtual:__federation__` code to keep running while you move gradually to manifest-first loading.
- Are adopting **Vite 8 / Rolldown** and are willing to validate against this repository's 1.0.0 snapshot before release.
Another option may be a better fit if:
- Your product wants the thinnest official Vite adapter and your team is comfortable composing lower-level `@module-federation/runtime` APIs, runtime hooks, and optional official plugins such as `@module-federation/retry-plugin` instead of using this repo's bundled policy wrapper.
- You are **still on Vite 4** and do not plan to upgrade. `@originjs/vite-plugin-federation` is closer to that historical target, but it comes with weaker shared-dependency governance and a remote development path that commonly depends on build watch.
---
## Closing
The real micro-frontend problem is not whether code can be split into another build. The production question is whether that split code can be released, rolled back, diagnosed, and monitored reliably. `vite-plugin-federation` 1.0 is designed to complete that production checklist and make Module Federation a governable capability in the Vite era.
- GitHub: [jskits/vite-plugin-federation](https://github.com/jskits/vite-plugin-federation)
- Full comparison and migration guides: [comparison.md](../comparison.md), [docs/migrate-from-module-federation-vite.md](../migrate-from-module-federation-vite.md), [docs/originjs-migration.md](../originjs-migration.md)
- Public API contract: [docs/public-api-contract.md](../public-api-contract.md)
If you are starting a new micro-frontend project, or preparing to move Vite 5+ federation into production, install `vite-plugin-federation@1.0.0`, validate it against your deployment topology, open issues, and contribute cases.