rollup-plugin-federation: Module Federation for Rollup

Configure the Rollup plugin that builds FynApps: what to expose, what to share, and how version ranges decide which copy of a library each app gets.

Updated Oct 25, 2026 Edit this page on GitHub

Module federation for Rollup. Build a bundle that shares modules with, and exposes modules to, other independently built and deployed bundles.

FynMesh: Website · Demo · Shell demo

The plugin generates a container entry for your build: it collects the shared modules and their importers, emits the container runtime code, and writes federation metadata describing what the bundle exposes and shares. At runtime those containers are wired together by federation-js.

Install

npm install --save-dev rollup-plugin-federation

Usage

import federation from "rollup-plugin-federation";

export default {
  input: ["src/index.js", "federation-entry.js"],
  output: { dir: "dist", format: "esm" },
  plugins: [
    federation({
      name: "plugin_1",
      // must also appear in the input array above
      filename: "federation-entry.js",
      exposes: {
        "./bootstrap": "./src/bootstrap.js",
      },
      shared: {
        react: { singleton: true, semver: "^19.0.0" },
      },
    }),
  ],
};

filename names the container entry the plugin generates — it has to be listed in Rollup's input as well, or the entry is never emitted.

Options

Option Type Description
name string Required. Name of this container.
filename string Required. Output filename for the container entry. Also add it to input.
exposes Record<string, string> Modules this container exposes to others.
shared Record<string, SharedConfig> Modules shared with other containers.
shareScope string Namespace for sharing. Defaults to the built-in scope.
version string Version of this container. Defaults to the host package's version.
emitFederationJson boolean Emit federation info as JSON. Defaults to true.
debugging boolean Enable debug logging and output.
renderDynamicImport object Dynamic import rendering per import type.
entry.header / entry.footer string Code inserted at the start/end of the entry file.
emitMeta, emitFederationMeta, enrichManifest function Hooks to emit or enrich metadata.

Shared modules

shared: {
  react: {
    singleton: true,     // only one instance across all containers
    semver: "^19.0.0",   // range this container accepts
    eager: false,        // load at startup rather than lazily
    import: true,        // bundle a copy, or treat as external
  },
}

A dependency consumed only transitively still has to appear in shared with its semver range — listing it as an external alone is not enough, and a mismatch shows up at runtime as a duplicate-instance error rather than a build failure.

Metadata the build emits

Alongside the container entry and its chunks, the plugin writes metadata describing the build. Two files, plus one copy embedded in the entry itself.

federation.json — a record of what this build produced: name, filename, shareScope, each expose mapped to { path, chunks }, each shared module's config, and a timestamp. Emitted unless emitFederationJson: false, in which case the same info object is still handed to emitFederationMeta.

Nothing requires it. It is build and serving metadata for your own tooling — the runtime never needs it, since the entry file carries the container, its exposes and its shares. federation-combine keeps its bundles record current when the file is there, but works without it.

An embedded manifest, if you supply enrichManifest. Because dynamic imports are not discovered until every module has been processed, the manifest cannot exist when the container code is generated — so the entry gets a { __placeholder: true } stub, and generateBundle splices the enriched manifest into the emitted code afterwards, matching both the development and minified forms. The result is exported as __FYNAPP_MANIFEST__ and assigned onto the container, which lets a runtime read a bundle's metadata without a second request.

That substitution is a text patch over generated code, and a build where it fails to match still succeeds — it warns and ships the placeholder. If a consumer of yours reads the embedded manifest without a fallback, check for it in CI.

Whatever emitFederationMeta emits. The hook receives the federation info, the runtime, the plugin context and the bundle, so it can emit metadata in whatever shape its consumers want. In the fynmesh colorepo, this pair of hooks is what produces a FynApp's fynapp.manifest.json; see notes/BUILD-ARTIFACTS.md there for a worked example of both hooks and how a runtime consumes their output.

Combining small chunks — federation-combine

One chunk is emitted per expose, so a container with many small exposes ships many tiny files whose cost is almost entirely request overhead. federation-combine folds them into one physical file per group, while each module keeps its own identity and stays importable by its own filename.

It is a post-build step over a finished dist directory, not part of the plugin's pipeline — so it can be re-run with a different policy, or applied at deploy time to artifacts someone else built.

federation-combine demo/fynapp-1/dist --dry-run
federation-combine demo/*/dist
--max-module <bytes>   only chunks at or below this size are candidates (2048)
--max-bundle <bytes>   stop adding to a group at this size (32768)
--min-group <count>    a group needs at least this many chunks (2)
--exclude <a,b>        fileNames or stems to leave in their own file
--group <name=a,b>     an explicit group, bypassing the size rules; repeatable
--drop-individual      delete the members' own files (removes the fallback)
--dry-run              report what would change, write nothing

What combining leaves behind

The runtime is told which file carries which module by one statement appended to the container entry, so an ordinary import works with no further help — a member's url resolves to its carrier and the carrier is fetched once, however many of its members are asked for.

That statement runs when the entry runs, which is too late for anything that has to decide what to fetch — a preload hint naming a member url names a file the runtime will never request. So the map is also written out as federation.bundles.json, the map on its own:

{ "combo-R7IaSAnm.js": ["hello-B9dQ6FmL.js", "getInfo-CH7m3ozA.js"] }

A host can read that without executing anything, and hand it to the runtime up front with Federation.declareBundles(map, distBase) — which is what makes carriers known before the first entry loads. Present only when a run actually combined something, and removed again when one does not, so its presence is itself the signal that a dist has bundles to offer.

Nothing here needs federation.json: the container entry is the one file that calls _mfContainer, and that call names the container and its version. An app that builds with emitFederationJson: false combines exactly the same.

Or from a build script:

import { combineDist } from "rollup-plugin-federation";

combineDist("dist", { groups: { startup: ["index", "react-esm-19.production"] } });

Explicit groups are taken exactly as given, which is how a known startup set is kept together and a cold chunk is kept out of a hot one. Everything else is packed smallest-first.

The members' individual files are kept by default: if the map and the files ever disagree, the runtime says so and loads the module's own file, so a stale map costs one request rather than a broken page. --drop-individual removes that fallback, and makes the step non-repeatable.

Requires chunks built by this plugin version or newer — each chunk records the physical file it lives in, which is what lets a combined module tell its own url apart from the file it arrived in. Chunks without it are reported and left alone. Combined files carry no sourcemap; the individual files keep theirs, so debug builds should simply not be combined.

Also exported

DependencyAnalyzer and DependencyUtils for inspecting a build's dependency graph, combineDist (above), plus the plugin's TypeScript types.

License

Apache-2.0