Skip to content

Repository files navigation

Vite plugin for Module Federation

npm

Vite and VoidZero recommend this plugin

Read the announcement.


Sponsors

Become a sponsor

Support this project on GitHub Sponsors

Reason why πŸ€”

Microservices nowadays is a well-known concept and maybe you are using it in your current company. Do you know that now you can apply similar ideas on the Frontend? With Module Federation you can load separately compiled and deployed code into a unique application. This plugin makes Module Federation work together with Vite.

Working implementations

Examples live in gioboa/module-federation-vite-examples:

Example Host Remote Framework
Alpine alpine-host alpine-remote Alpine.js
Angular angular-host angular-remote Angular
Astro astro-host astro-remote Astro 7
Ember ember-host ember-remote Ember 7
Lit lit-host lit-remote Lit
Nuxt nuxt-host nuxt-remote Nuxt 4
Nx host remote React + Nx
Preact preact-host preact-remote Preact 10
React react-host react-remote React 19
Solid solid-host solid-remote Solid
Svelte svelte-host svelte-remote Svelte 5
TanStack tanstack-host tanstack-remote TanStack Router + React 19
Turborepo host remote React + Turborepo
Vinext vinext-host vinext-remote Vinext + Next 16 + React 19
Vue vue-host vue-remote Vue 3

Try this crazy example with all these bundlers together

pnpm install
pnpm run build
pnpm run multi-example

Getting started πŸš€

https://module-federation.io/integrations/build-tool/vite

With @module-federation/vite, the process becomes delightfully simple, you will only find the differences from a normal Vite configuration.

This example is with Vue.js
The @module-federation/vite configuration remains the same for different frameworks.

Migrating from OriginJS

Migrating from @originjs/vite-plugin-federation requires an explicit remote-entry format and runtime review, particularly for dynamic remotes, shared dependencies, and mixed bundlers. See the OriginJS migration guide for step-by-step migration and incremental deployment guidance.

Dedicated configuration file

You can keep Module Federation options in module-federation.config.ts.

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

export default createModuleFederationConfig({
  name: "remote",
  filename: "remoteEntry.js",
  exposes: {
    "./remote-app": "./src/App.vue",
  },
  shared: ["vue"],
});
import { defineConfig } from "vite";
import { federation } from "@module-federation/vite";
import moduleFederationConfig from "./module-federation.config";

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

The Remote Application configuration

file: remote/vite.config.ts

import { defineConfig } from 'vite';
import { federation } from '@module-federation/vite'; πŸ‘ˆ

export default defineConfig({
  [...]
  plugins: [
    [...]
    federation({ πŸ‘ˆ
      name: "remote",
      filename: "remoteEntry.js",
      // optional: additional "var" remoteEntry file
      // needed only for legacy hosts with "var" usage (remote.type = 'var')
      varFilename: "varRemoteEntry.js",
      exposes: {
        "./remote-app": "./src/App.vue",
      },
      shared: ["vue"],
    }),
  ],
  server: {
    origin: "http://localhost:{Your port}"
  },
  [...]
});

In this remote app configuration, we define a remoteEntry.js file that will expose the App component. The shared property ensures that both host and remote applications use the same vue library.

Host-only shared dependencies

Use import: false when a dependency must be provided by the host and should not be bundled as a local fallback. If it is intentionally not installed locally, the named-export detection warning can be suppressed:

shared: {
  "@pos-dashboard/host": {
    singleton: true,
    import: false,
    suppressMissingImportWarning: true,
  },
},

suppressMissingImportWarning defaults to false. Enable it only when the host is guaranteed to provide the dependency and local named-export detection is unnecessary.

The Host Application configuration

file host/vite.config.ts

import { defineConfig } from 'vite';
import { federation } from '@module-federation/vite'; πŸ‘ˆ

export default defineConfig({
  [...]
  plugins: [
    [...]
    federation({ πŸ‘ˆ
      name: "host",
      remotes: {
        remote: {
          type: "module", // type "var" (default) for vite remote is supported with remote's `varFilename` option
          name: "remote",
          entry: "https://[...]/remoteEntry.js",
          entryGlobalName: "remote",
          shareScope: "default",
        },
      },
      filename: "remoteEntry.js",
      shared: ["vue"],
      // Optional parameter that controls where the host initialization script is injected.
      // By default, it is injected into the index.html file.
      // You can set this to "entry" to inject it into the entry script instead.
      // Recommended for SSR hosts without index.html (Nitro, TanStack Start) so
      // initHost() completes before hydrateRoot and @module-federation/bridge-react
      // remotes render on first paint.
      hostInitInjectLocation: "html", // or "entry"
      // Controls whether all CSS assets from the bundle should be added to every exposed module.
      // When false (default), the plugin will not process any CSS assets.
      // When true, all CSS assets are bundled into every exposed module.
      bundleAllCSS: false, // or true
      // Timeout for parsing modules in seconds.
      // Defaults to 10 seconds.
      moduleParseTimeout: 10,
      // Idle timeout for parsing modules in seconds. When set, the timeout
      // resets on every parsed module and only fires when there has been no
      // module activity for the configured duration. Prefer this over
      // moduleParseTimeout for large codebases where total build time may
      // exceed the fixed timeout value.
      moduleParseIdleTimeout: 10,
      // Controls whether module federation manifest artifacts are generated.
      // Type: boolean | object
      // - false/undefined: no manifest generated
      // - true: generates mf-manifest.json + mf-stats.json (default names)
      // - object: overrides fileName/filePath and asset analysis behavior
      manifest: {
        // Optional output file name for runtime manifest.
        // Default: "mf-manifest.json"
        fileName: "mf-manifest.json",
        // Optional output directory/path for both artifacts.
        // Example: "dist/" -> dist/mf-manifest.json + dist/mf-stats.json
        filePath: "dist/",
        // If true, skips asset analysis.
        // Effect: shared/exposes are omitted from manifest and assetAnalysis is omitted from stats.
        // It also disables the preload-helper patch used for remotes.
        // In serve for consumer-only apps, this defaults to true unless explicitly set.
        disableAssetsAnalyze: false,
        // Optional hook to mutate generated manifest/stats data.
        additionalData: ({ stats }) => {
          stats.metaData.deployEnv = process.env.NODE_ENV;
          stats.metaData.region = "eu";
          stats.custom = {
            buildId: process.env.BUILD_ID,
          };
        },
        // Or return a replacement/merged object.
        // additionalData: ({ stats }) => ({
        //   ...stats,
        //   custom: { buildId: process.env.BUILD_ID },
        // }),
      },
    }),
  ],
  server: {
    origin: "http://localhost:{Your port}"
  },
  [...]
});

The host app configuration specifies its name, the filename of its exposed remote entry remoteEntry.js, and importantly, the configuration of the remote application to load. You can specify the place the host initialization file is injected with the hostInitInjectLocation option, which is described in the example code above. The moduleParseTimeout option allows you to configure the maximum time to wait for module parsing during the build process. The moduleParseIdleTimeout option is an alternative that resets the timer on every parsed module. It only fires when there has been no module activity for the configured duration, making it suitable for large codebases where the total build time exceeds the fixed timeout.

Runtime capability optimization

Runtime features that a build never uses can be removed at build time:

federation({
  name: "remote",
  exposes: {
    "./remote-app": "./src/App.vue",
  },
  disableRemote: true,
  disableShared: true,
  disableSnapshot: true,
});
  • disableRemote removes remote-consumption support. Do not enable it when remotes are configured.
  • disableShared removes shared-dependency support. Do not enable it when shared dependencies are configured.
  • disableSnapshot removes snapshot support, including manifest-based remotes, preload, dynamic type hints, HMR, and devtools integration.

All three options default to false, except disableSnapshot, which defaults to true for Node/SSR builds. An explicitly configured Vite define value takes precedence over the corresponding option.

Load the Remote App

In your host app, you can now import and use the remote app with defineAsyncComponent

file host/src/App.vue

<script setup lang="ts">
import { defineAsyncComponent } from "vue";
const RemoteMFE = defineAsyncComponent( πŸ‘ˆ
  () => import("remote/remote-app")
);
</script>

<template>
  <RemoteMFE v-if="!!RemoteMFE" /> πŸ‘ˆ
</template>

Shared Tree Shaking

Shared Tree Shaking reduces shared dependency bundles to the exports used by the application.

federation({
  shared: {
    antd: {
      singleton: true,
      treeShaking: {
        mode: "runtime-infer", // or "server-calc"
        usedExports: ["Button", "Input"],
      },
    },
  },
  treeShakingDir: "independent-packages",
});

runtime-infer is useful for local development and falls back to the full dependency when the required exports are not available. server-calc is recommended for deployments because it can use aggregated export metadata from all consumers.

Do not combine eager: true with treeShaking; eager shared dependencies are bundled into the initial entry and cannot use the on-demand tree-shaking path. Choose eager loading for small dependencies, or tree shaking for larger dependencies such as component libraries.

With server-calc, the Vite build records the exports used by each application in its generated Module Federation metadata. A deployment service must then collect that metadata for all applications that share the same dependency and version, merge their usedExports lists, and use the resulting union to create one optimized secondary shared artifact. For example, if one application uses Button and another uses Input, the secondary artifact must contain both exports.

After creating the secondary artifact, the deployment service must publish it to a location accessible by consumers, such as a CDN, and update the remote Snapshot with the artifact's URL, unique name, and available tree-shaking status. Consumers use those Snapshot fields to decide whether the optimized artifact can satisfy their required exports and version.

This deployment step is separate from the local Vite build; the plugin only emits the metadata and runtime support needed by the service. If the Snapshot is not updated, the artifact cannot be fetched, the versions do not match, or the artifact does not provide all required exports, the runtime ignores the optimized artifact and safely loads the complete shared dependency instead.

External runtime (experiments)

Share one @module-federation/runtime-core instance from a pure consumer host so remotes do not bundle their own copy. Pair the flags β€” remotes with externalRuntime require a host that provides the global.

Host (pure consumer, no exposes):

federation({
  name: "host",
  remotes: {
    remote: {
      type: "module",
      name: "remote",
      entry: "http://localhost:5176/remoteEntry.js",
    },
  },
  experiments: {
    provideExternalRuntime: true,
  },
});

Remote:

federation({
  name: "remote",
  filename: "remoteEntry.js",
  exposes: {
    "./App": "./src/App.tsx",
  },
  experiments: {
    externalRuntime: true,
  },
});

provideExternalRuntime injects a local runtime plugin that publishes runtime-core on globalThis._FEDERATION_RUNTIME_CORE. externalRuntime rewrites imports of @module-federation/runtime-core to read that global. Using provideExternalRuntime together with exposes throws β€” only pure consumers may provide the runtime. The externalRuntime rewrite applies to the browser remote graph; SSR remote entries continue to resolve @module-federation/runtime-core from Node so they do not depend on the browser global.

⚠️ codeSplitting is managed by the plugin

Do not set build.rollupOptions.output.codeSplitting or build.rolldownOptions.output.codeSplitting to false β€” it will be ignored (with a warning). Module Federation requires chunk splitting so loadShare and runtimeInitStatus stay isolated for correct bootstrap order.

codeSplitting.groups (Vite 8+ / Rolldown)

User groups are now preserved. The plugin installs its own federation groups at the highest priority and appends your groups below them, so your groups can only claim modules the federation groups didn't.

  • No warning is emitted just for keeping your groups.
  • If one of your existing groups sets a priority high enough to outrank the federation groups, it is clamped below them and the plugin warns once. This prevents those groups from capturing a runtimeInit/loadShare wrapper or the preload helper.

⚠️ manualChunks behavior depends on your Vite version

Setting Vite 5–7 (Rollup) Vite 8+ (Rolldown)
manualChunks (function) Composed as a fallback β€” federation modules are claimed first, everything else falls through to your function Ignored (warns) β€” move grouping to codeSplitting.groups
manualChunks (object) Ignored (warns) β€” use the function form to compose Ignored (warns) β€” move grouping to codeSplitting.groups

On Vite 5–7, Rollup doesn't support codeSplitting, so the plugin isolates runtimeInitStatus, loadShare, and the preload helper via manualChunks. A user-provided function is called for any module the plugin doesn't claim; the object form isn't composed by the plugin and is ignored.

On Vite 8+, chunking is managed through codeSplitting.groups (see above), so manualChunks is removed β€” express your grouping as codeSplitting.groups instead, where user groups are preserved below the federation groups.

So far so good πŸŽ‰

Now you are ready to use Module Federation in Vite!

About

Vite Plugin for Module Federation

Resources

Stars

864 stars

Watchers

11 watching

Forks

Releases

Packages

Used by

Contributors

Languages