# The Vite plugin for Module Federation is two functions and a config file

> @module-federation/vite lets a Vite build produce and consume federated modules, so separately built frontends can load each other at runtime. It suits teams splitting a large frontend into independently deployed pieces, and it is a plugin rather than a framework, so the surrounding Vite setup is yours to own.

**module-federation/vite** — Vite Plugin for Module Federation 

- Repository: https://github.com/module-federation/vite
- Stars: 889 · Forks: 172
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/module-federation-vite

## Federation is a runtime contract between two builds

The plugin's reason for existing is stated in the README's own terms. Microservices are a well-known concept, the note says, and with Module Federation you can apply similar ideas on the frontend, loading separately compiled and deployed code into a unique application. This plugin makes Module Federation work together with Vite.

That definition matters, because federation is not a build-time optimisation and it is not code splitting. Code splitting keeps one deployment. Federation is a runtime contract: a host application asks a remote for a module by name, the remote is a separately built artefact at a separately chosen URL, and the two share dependencies through an agreed negotiation. The build produces a remote entry file, and the host loads it at runtime.

So the question a team has to answer first is not how to configure the plugin but whether it wants the coupling. Federation buys independent deployment of separate features, and it costs a runtime boundary that did not exist before, with its own failure modes, its own version negotiation for shared libraries, and its own debugging story.

The plugin is published as @module-federation/vite on npm, and the README carries an announcement that Vite and VoidZero recommend it, with a link to the original post and a sponsors section. The repository is written in TypeScript and packaged with tsdown, and the exported entry points include not just the plugin itself but a shared runtime loader and an external runtime core plugin, which is a hint that runtime behaviour is a first-class part of this project rather than an afterthought.

## A separate config file keeps the plugin out of your Vite config

The configuration story is the clearest part of the documentation, and it is worth following exactly because the plugin is deliberately low-ceremony.

The dedicated configuration file is module-federation.config.ts, and the plugin exposes a helper to build it:

```ts
import { createModuleFederationConfig } from "@module-federation/vite";

export default createModuleFederationConfig({
  name: "remote",
  filename: "remoteEntry.js",
  exposes: {
    "./remote-app": "./src/App.vue",
  },
  shared: ["vue"],
});
```

The Vite config then imports it and wraps it in the federation plugin, and the README is explicit that this is the only difference from a normal Vite configuration:

```ts
import { defineConfig } from "vite";
import { federation } from "@module-federation/vite";
import moduleFederationConfig from "./module-federation.config";

export default defineConfig({
  plugins: [federation(moduleFederationConfig)],
});
```

The reason for splitting it out is not stylistic. The federation options describe what a remote exposes and what it shares with hosts, and that same description is consumed by the host build as well, in a different project, potentially on a different schedule. Keeping it in one file that both sides can import means the contract between them is versioned as data rather than duplicated as configuration.

The example in the README is written for Vue.js, and the note under it is the one to remember: the @module-federation/vite configuration remains the same for different frameworks. The plugin is not a Vue tool with a Vite wrapper, it is a Vite plugin that happens to be demonstrated with Vue.

## The remote entry file has three names, and each is optional

The remote application's configuration is where the plugin's real options live, and the README is careful to say which of them are optional.

The core of it is the federation call with a name, a filename and an exposes map, and then three filename variants, each with a stated purpose:

```ts
      name: "remote",
      filename: "remoteEntry.js",
      // optional: additional "var" remoteEntry file
      // needed only for legacy hosts with "var" usage (remote.type = 'var')
      varFilename: "varRemoteEntry.js",
      // optional: exact name of the SSR remote entry
      // default: "<filename base>.ssr.js" (e.g. remoteEntry.ssr.js)
      ssrFilename: "remoteEntry.ssr.js",
      exposes: {
        "./remote-app": "./src/App.vue",
      },
      shared: ["vue"],
```

So there is a normal remote entry, a variant entry for legacy hosts that use the var remote type, and a server-side rendering entry whose name defaults to the base filename with an ssr suffix. The server block in the same configuration sets the origin, which is how the host is told where the remote lives at runtime:

```ts
  server: {
    origin: "http://localhost:{Your port}"
  }
```

The shared property is the part that causes trouble later, and the README explains it in one sentence: it ensures that both host and remote applications use the same vue library. That is the whole point of shared, but it is also the point where two independently deployed pieces can end up with two copies of a library in one page, which is the classic federation bug. The exposes map is what the host will request, and the key is the name the host asks for while the value is the file in the remote that satisfies it.

## import: false is the escape hatch for a real singleton

The README documents a host-only shared dependency option that is worth understanding before you ship, because it changes the failure mode of a federation setup from subtle to loud.

The rule is to use import: false when a dependency must be provided by the host and should not be bundled as a local fallback. The default behaviour in a federated build is to include a local fallback copy of a shared dependency in each remote, so that a remote can still load if the host does not provide it. That is convenient, and it is also how you end up with two instances of the same library in a page, with the state in one of them unreachable from the other.

With import: false, the remote does not carry a fallback. If the host does not supply the dependency, the remote fails to resolve it. The README notes that if the dependency is intentionally not installed locally, the configuration has to account for that, which is the trade: a loud failure instead of a quiet duplication.

That is a per-dependency decision, and the right answer differs by dependency. A framework runtime is usually fine with a version-tolerant duplicate if the remote and host are on compatible versions, but a stateful singleton, a store, an event bus or anything holding a connection is not.

This is the point in a federation project where the abstraction stops helping. Once shared dependencies are negotiated by the runtime, the versions of your libraries stop being a private matter of each build and become part of the contract between them, and the plugin gives you the tool to make that contract strict rather than letting it drift.

## Fourteen working examples, one per framework

The most persuasive thing about this project is not the README prose but the example repository, gioboa/module-federation-vite-examples, which carries a host and a remote for each of a large set of frameworks.

The table lists them: Alpine with alpine-host and alpine-remote, Angular, Astro 7, Ember 7, Lit, Nuxt 4, Preact 10, React 19, Solid, Svelte 5, TanStack Router with React 19, Vinext with Next 16 and React 19, and Vue 3. Two further examples sit outside that repository: an Nx example with React, and a Turborepo example with React, both hosted in the react-nx-microfrontend-demo and react-turborepo-microfrontend-demo repositories.

So the plugin is not tied to a framework ecosystem, and the claim that the configuration is the same across frameworks is backed by fourteen separate implementations rather than by assertion.

There is also a combined example, described in the README as a crazy example with all these bundlers together, and the three images beside it name the bundlers: vite, webpack and rspack. That is a more interesting test than the individual examples, because mixing bundlers is where a federation implementation usually breaks. The commands to run it are short:

```bash
pnpm install
pnpm run build
pnpm run multi-example
```

The examples in this repository's own tree tell the same story from the other direction. There are directories for vite-vite, vite-webpack-rspack, vite-runtime-register and rust-vite, and the package scripts give each of them a dev and a preview target, including variants that run the Vite to Vite example with an external runtime.

The rust-vite example is the most unusual of them, since it suggests a Rust based build participating in the federation, and the vite-runtime-register example suggests the runtime can be registered from outside the bundler rather than injected by it.

## Migrating from OriginJS is a documented project, not a note

Anyone arriving from the previous generation of this plugin is told to expect work, and the README is unusually direct about it.

Migrating from @originjs/vite-plugin-federation requires an explicit remote-entry format and runtime review, particularly for dynamic remotes, shared dependencies, and mixed bundlers. The project provides a migration guide at docs/MIGRATING_FROM_ORIGINJS.md with step-by-step migration and incremental deployment guidance.

Three areas are named as the ones that need attention. The remote-entry format, because the two plugins did not agree on how a remote entry is produced and consumed. Dynamic remotes, which is where a remote is not known at build time. Shared dependencies, which is the negotiation layer and the place where the import: false option lives. And mixed bundlers, which is the case the combined example exists to cover.

The mention of incremental deployment guidance is the detail that tells you the audience. A migration that requires a flag day, where the host and the remotes switch over together, is a different proposition from one that lets a host start consuming a new remote while the old one is still live. For a large frontend split across several teams, that difference decides whether the migration is a sprint or a quarter.

The repository layout supports the claim that this is a maintained project rather than a spike. There is a docs directory, an e2e directory, an integration directory, a scripts directory, and four Playwright configurations, one of them dedicated to an external runtime. There is also a pnpm workspace with a lock file, husky for git hooks, and oxfmt for formatting.

## A fast release line, one package, one engine requirement

The maintenance picture is straightforward. The repository is not archived, the last push was on 2026-10-19, and the recent releases are 1.23.5 on 2026-10-13, 1.23.4 on 2026-10-08 and 1.23.3 on 2026-10-07. Three releases in a week on a patch line is a project that ships continuously, and the version numbering is a single line rather than a matrix.

The published package is @module-federation/vite at version 1.23.5, marked as private false in the manifest with the type module, and its main and module fields both point at lib/index.js. The exports map is worth reading, because it tells you the public surface: the root export, an ./ssrEntryLoader subpath for the server-side entry loader, an ./injectExternalRuntimeCorePlugin subpath for injecting the external runtime core, and ./package.json.

The engine requirement is Node ^20.19.0 or >=22.12.0, which is a real constraint for CI images that have not been updated in a while. And the published files list is just lib/**/*, so the package ships the build output and nothing else, which is the right shape for a plugin that consumers compile against rather than inspect.

The licence is MIT, with a LICENSE file at the top of the repository, so there is no complication in using it in a commercial product. The licence field on the repository carries a custom classification that GitHub cannot map, and the terms to read are in that file.

One structural point is easy to miss and matters for anyone planning an upgrade. The plugin is versioned independently of Vite itself, and the peer relationship between a plugin version and a Vite version is not described in the documentation available here. A team that upgrades Vite aggressively will want to check the plugin's own release notes rather than assume compatibility.

## Conclusion

The Vite plugin for Module Federation suits a team that has decided to split a frontend into separately built and separately deployed pieces and wants to keep Vite as the build tool. It is the wrong choice for a codebase that has not yet outgrown a single bundle, because federation adds a runtime contract between builds, and it is the wrong choice for a team that cannot accept depending on a Vite plugin version alongside Vite itself. Before you adopt it, read the OriginJS migration guide if you are coming from that plugin, decide whether your shared dependencies are true singletons or version-tolerant, since the import: false option exists for the first case, and confirm the Node engine requirement of ^20.19.0 or >=22.12.0 matches your CI images.

## FAQ

### What does @module-federation/vite do?

It is a Vite plugin that makes Module Federation work with Vite, so a host application can load separately compiled and deployed code from a remote at runtime. It is a build plugin rather than a framework, and the surrounding Vite configuration stays yours.

### How do I configure Module Federation in a Vite project?

Keep the federation options in a module-federation.config.ts file built with createModuleFederationConfig, then import that file in the Vite config and pass it to the federation plugin. The README says this is the only difference from a normal Vite configuration, and the configuration is the same across frameworks.

### What is the import: false option for shared dependencies?

Use import: false when a dependency must be provided by the host and should not be bundled as a local fallback. Without it, a remote carries its own copy of a shared library, which can leave two instances of that library in one page.

### How do I migrate from the OriginJS Vite plugin?

The project provides a migration guide at docs/MIGRATING_FROM_ORIGINJS.md. The README notes that migrating requires an explicit remote-entry format and a runtime review, particularly for dynamic remotes, shared dependencies and mixed bundlers, with incremental deployment guidance included.

## Sources

- [Issues](https://github.com/module-federation/vite/issues)
- [License: MIT](https://github.com/module-federation/vite/blob/main/LICENSE)
- [module-federation/vite on GitHub](https://github.com/module-federation/vite)
- [README](https://github.com/module-federation/vite/blob/main/README.md)
- [Releases](https://github.com/module-federation/vite/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/module-federation-vite
