diff --git a/apps/website-new/docs/en/integrations/build-tool/vite.mdx b/apps/website-new/docs/en/integrations/build-tool/vite.mdx index d061792dd8e..351efb26af9 100644 --- a/apps/website-new/docs/en/integrations/build-tool/vite.mdx +++ b/apps/website-new/docs/en/integrations/build-tool/vite.mdx @@ -45,6 +45,264 @@ import RegisterPlugin from '@docs/_snippets/vite/register-plugin'; +## Migrate from OriginJS + +This guide helps migrate Vite hosts and remotes from +[`@originjs/vite-plugin-federation`](https://github.com/originjs/vite-plugin-federation) +to `@module-federation/vite`. + +Both plugins use remote entry files, but their host-remote configuration differs: + +- An OriginJS URL-string remote, or a remote object without `format`, is an ESM remote by default. +- In `@module-federation/vite`, the URL-string shorthand represents a `var` remote. Declare Vite ESM remotes as an object with `type: 'module'`. +- Replace OriginJS's `virtual:__federation__` API with the Module Federation runtime API when registering remotes dynamically. + +Keep remote aliases and expose keys unchanged for the initial migration. Existing imports, such as `import('catalog/Product')`, can then remain unchanged. + +### Requirements + +Before migrating, verify that every application uses a version supported by `@module-federation/vite`: + +- Node.js `^20.19.0` or `>=22.12.0` +- Vite 5, 6, 7, or 8 + +Upgrade the application toolchain before migration if it does not meet these requirements. + +### 1. Replace the package + +Install `@module-federation/vite` in every host and remote that uses build-time federation, then remove OriginJS after its configuration is no longer in use. A host managed entirely through the runtime does not need the Vite plugin; see [migrating dynamic remotes](#4-migrate-dynamic-remotes). + + + +### 2. Migrate a Vite remote + +Migrate one remote and validate it with one host before migrating the remaining applications. The `name`, `filename`, and expose keys can remain the same: + +#### OriginJS remote + +```ts +import federation from '@originjs/vite-plugin-federation'; + +export default { + plugins: [ + federation({ + name: 'catalog', + filename: 'remoteEntry.js', + exposes: { + './Product': './src/Product.tsx', + }, + shared: ['react', 'react-dom'], + }), + ], +}; +``` + +#### `@module-federation/vite` remote + +```ts +import { defineConfig } from 'vite'; +import { federation } from '@module-federation/vite'; + +export default defineConfig({ + plugins: [ + federation({ + name: 'catalog', + filename: 'remoteEntry.js', + exposes: { + './Product': './src/Product.tsx', + }, + shared: ['react', 'react-dom'], + }), + ], +}); +``` + +`name` and expose keys remain unchanged, so existing consumer imports continue to work. + +With Vite's default build settings, the remote entry output path changes: + +- OriginJS with `filename: 'remoteEntry.js'`: `dist/assets/remoteEntry.js` +- `@module-federation/vite` with `filename: 'remoteEntry.js'`: `dist/remoteEntry.js` + +To keep the existing `/assets/remoteEntry.js` URL, set `filename: 'assets/remoteEntry.js'`. Otherwise, update the host's `entry` URL to the new location. + +If an OriginJS expose uses object form, carry over only its `import` value. The OriginJS `name` and `dontAppendStylesToHead` options have no direct equivalents and cannot be copied unchanged. If you use `dontAppendStylesToHead`, follow the CSS guidance in [Step 5](#5-review-shared-dependencies-and-css). + +### 3. Migrate a Vite host + +Configure Vite-built ESM remotes with an explicit `type: 'module'`. + +#### OriginJS host + +```ts +import federation from '@originjs/vite-plugin-federation'; + +export default { + plugins: [ + federation({ + name: 'storefront', + remotes: { + catalog: 'https://cdn.example.com/catalog/remoteEntry.js', + }, + shared: ['react', 'react-dom'], + }), + ], +}; +``` + +#### `@module-federation/vite` host + +```ts +import { defineConfig } from 'vite'; +import { federation } from '@module-federation/vite'; + +export default defineConfig({ + plugins: [ + federation({ + name: 'storefront', + remotes: { + catalog: { + name: 'catalog', + entry: 'https://cdn.example.com/catalog/remoteEntry.js', + type: 'module', + }, + }, + shared: ['react', 'react-dom'], + }), + ], +}); +``` + +Keep the array-form `shared` configuration for the initial host migration. Introduce singleton policies separately after reviewing [shared dependencies and CSS](#5-review-shared-dependencies-and-css). + +Do not use the string shorthand for a Vite ESM remote; it is interpreted as `var`: + +```ts +// Incorrect for a Vite ESM remote +remotes: { + catalog: 'https://cdn.example.com/catalog/remoteEntry.js', +} +``` + +Use an object remote with `type: 'module'` instead: + +```ts +remotes: { + catalog: { + name: 'catalog', + entry: 'https://cdn.example.com/catalog/remoteEntry.js', + type: 'module', + }, +} +``` + +Select `type` from the deployed remote-entry container format, rather than the bundler that produced it. The static consumer import does not change: + +```ts +const Product = await import('catalog/Product'); +``` + +| OriginJS configuration | Migration action | +| --- | --- | +| URL string or `format: 'esm'` | Use `{ name, entry, type: 'module' }` after verifying that the deployed entry is an ESM container. | +| `format: 'var'` | Use `{ name, entry, type: 'var' }`. Add `entryGlobalName` when the container global differs from the remote alias. | +| `externalType: 'promise'` | Resolve the URL asynchronously and register it with the runtime API. | +| `shareScope` | Preserve it as `shareScope` on the remote object. | +| `from` | Do not map it directly; choose `type` from the deployed remote-entry format. | + +`systemjs` remotes require a separate proof of concept before migration. + +### 4. Migrate dynamic remotes + +This step is required only for hosts that dynamically register or load remotes. Install `@module-federation/enhanced` before replacing `virtual:__federation__` with the runtime API: + + + +#### OriginJS dynamic remote + +```ts +import { + __federation_method_getRemote as getRemote, + __federation_method_setRemote as setRemote, + __federation_method_unwrapDefault as unwrapDefault, +} from 'virtual:__federation__'; + +setRemote('catalog', { + url: () => Promise.resolve(remoteUrl), + format: 'esm', + from: 'vite', +}); + +const module = await getRemote('catalog', './Product'); +const Product = await unwrapDefault(module); +``` + +#### Host using the Vite plugin + +```ts +import { loadRemote, registerRemotes } from '@module-federation/enhanced/runtime'; + +registerRemotes([ + { + name: 'catalog', + entry: remoteUrl, + type: 'module', + }, +]); + +const module = await loadRemote('catalog/Product'); +const Product = module?.default ?? module; +``` + +#### Host using the pure runtime + +Use this path only when the host is intentionally managed entirely through the runtime, rather than a Vite federation plugin. Create an instance, register the shared dependencies the host provides, and then register remotes. Shared registration is application-specific, so use the [Runtime API](/guide/runtime/runtime-api.html) instead of copying a partial configuration. + +### 5. Review shared dependencies and CSS + +Start by retaining simple shared arrays such as `shared: ['react', 'react-dom']`. Review complex OriginJS settings rather than copying them mechanically: + +| OriginJS option | Migration guidance | +| --- | --- | +| `requiredVersion`, `shareScope` | Supported by the shared configuration; verify both host and remote settings. | +| `version: false`, `packagePath`, `generate: false`, `modulePreload` | No direct equivalent; review the behavior manually. | +| `import: false` | Supported, but the remote has no local fallback. Ensure the host provides a compatible dependency in the same share scope. | +| `dontAppendStylesToHead` | No direct equivalent. For Shadow DOM, explicitly manage and inject stylesheet URLs or styles into the `ShadowRoot`. | + +When host and remote render React in the same boundary, verify compatible versions and configure `react` and `react-dom` as singletons where required. Also check cross-boundary subpath imports such as `react/jsx-runtime` and `react-dom/client`. + +### 6. Deploy incrementally + +1. Publish the migrated remote at a versioned URL, keeping the OriginJS remote entry and its chunks available. +2. Point one host to the new URL with an explicit remote `type`. +3. Verify remote modules, shared dependencies, CSS, and assets in a production-like environment. +4. Migrate remaining static and dynamic hosts incrementally. +5. Remove OriginJS only after no host consumes the old remote and the rollback retention period has ended. + +Deploy each remote entry together with every chunk it references. Use immutable, content-hashed URLs for child chunks and retain prior chunks long enough for cached remote entries and rollback. + +Before removing OriginJS, confirm that: + +- [ ] Every consumed remote expose loads successfully. +- [ ] The deployed remote-entry URL and referenced chunks are available. +- [ ] Static and dynamic (where used) remote loading works. +- [ ] Shared-dependency selection, CSS, and other assets behave as intended. +- [ ] The production build and federation integration tests pass. + ## Configure the Build Plugin - Type: `ModuleFederationPlugin(options: ModuleFederationOptions)`