vite-plugin-federation
v1.0.0
Published
A Vite plugin for Module Federation.
Maintainers
Readme
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.
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
pnpm add -D vite-plugin-federationnpm install -D vite-plugin-federationyarn add -D vite-plugin-federationRequirements:
- 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
// 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].jsmf-manifest.jsonmf-stats.jsonmf-debug.json
Use a fixed remote entry name when your deployment needs one:
federation({
name: 'catalog',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.tsx',
},
});Browser 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.0.0' },
'react/': { singleton: true, requiredVersion: '^19.0.0' },
},
}),
],
build: {
target: 'esnext',
},
});Load a manifest remote explicitly:
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 (
<Suspense fallback={null}>
<CatalogButton />
</Suspense>
);
}You can also use the classic Module Federation runtime style after remotes are registered:
import { loadRemote } from 'vite-plugin-federation/runtime';
const mod = await loadRemote('catalog/Button');Node SSR Host
// 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',
},
});// 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:
node --experimental-vm-modules server.jsDo 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
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
federation({
name: 'catalog',
exposes: {
'./Button': './src/Button.tsx',
'./ManualCssButton': {
import: './src/ManualCssButton.tsx',
css: { inject: 'manual' },
},
},
});CSS modes:
head: append CSS todocument.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:
federation({
name: 'shell',
remotes: {
catalog: 'https://cdn.example.com/catalog/mf-manifest.json',
},
});Use object remotes for compatibility paths:
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
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
allowNodeModulesSuffixMatchfor pnpm or symlinked workspace layouts. - Use
import: falsefor host-only shared modules. The host must provide the share; the remote will not bundle a fallback. - Use
strictSingletonwhen singleton conflicts should fail instead of silently falling back.
DTS
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
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.jsonor your configured manifest path./mf-debug.json./remoteEntry.jsor the configuredfilename./__mf_hmrwhendev.remoteHmr: true./__mf_devtoolsunlessdev.devtools: false.
Runtime API
Import runtime helpers from vite-plugin-federation/runtime:
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():
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:
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
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
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 usecontentHash.integrity: require SRI.content-hash: requirecontentHash.both: require both values when verifying annotated assets.
Use verifyFederationManifestAssets() when you need to verify expose and preload assets declared in
the manifest:
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
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:
import {
__federation_method_ensure,
__federation_method_getRemote,
__federation_method_setRemote,
__federation_method_unwrapDefault,
__federation_method_wrapDefault,
} from 'virtual:__federation__';Disable it after migration:
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
fetchInitor customfetchfor 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/
varcompatibility 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
- Full repository README
- Public API contract
- Plugin API
- Runtime API
- Manifest protocol
- Production runtime
- DTS workflows
- Dev HMR
- Security
- OriginJS migration
- 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
