Architecture Overview
A bird's-eye view of Native Federation — how the Core, Adapters and Runtime fit together to enable framework-agnostic Micro Frontends.
Native Federation is organized into three cooperating layers. Each has a single responsibility and a narrow contract with its neighbors — so you can replace any one of them (your framework, your bundler, or your runtime) without touching the others.
The Three Layers
| Layer | Lives at | Runs | Responsibility |
|---|---|---|---|
| Core | @softarc/native-federation | build time | Normalizes the federation config, bundles shared dependencies and exposed modules, and emits remoteEntry.json + importmap.json. Bundler-agnostic. |
| Adapters | Angular / esbuild / Vite … | build time | Plug a specific bundler or framework into the Core via the BuildAdapter contract. Often ship a higher-level API, schematics or CLI integration on top. |
| Runtime | in the browser | run time | Reads remoteEntry.json files, constructs a combined import map, and loads remote modules on demand. Small and framework-agnostic. |
A fourth piece exists but is optional here: the Orchestrator, v4's browser runtime. It reads the same remoteEntry.json contract, so a v3 host can swap it in for semver-range resolution and persistent caching.
How They Fit Together
┌─────────────────────┐ plugs in ┌────────────┐
│ Core │ ◄───────────────── │ Adapter │
│ (federationBuilder) │ │ (esbuild, │
│ │ ──────────────────►│ Angular) │
└──────────┬──────────┘ delegates build └────────────┘
│
│ emits remoteEntry.json + exposed and shared bundles
│
▼
┌──────────────────────────────────────┐
│ Runtime │
│ (initFederation, loadRemoteModule) │
└──────────────────────────────────────┘
in the browser
Each layer gets its own section elsewhere in these docs. The short version:
- Core — bundler-agnostic builder. Normalizes the federation config, computes the externals your own bundler must leave unresolved, bundles shared dependencies and exposed modules, and writes
remoteEntry.json+importmap.json. - Adapters — framework-/bundler-specific glue implementing the
BuildAdaptercontract. First-party adapters exist for Angular and esbuild; there is a community Vite plugin, and you can build your own. - Runtime — the browser library. Exposes
initFederationandloadRemoteModule.
Build Steps
At a very high level, building a Native Federation micro frontend goes through five stages. The Core orchestrates; the Adapter does the actual bundling.
- Init & normalize — the Core loads
federation.config.js, merges it with the project'sFederationOptionsandpackage.json, and registers the Adapter. - Compute externals — derive the list of shared packages and
tsconfigpath mappings that the host bundler must leave unresolved, so the browser can later wire them through the import map. - Bundle exposed modules & mapped paths — compile every
exposesentry plus any monorepo-internal libraries referenced viapaths, via the Adapter. - Bundle shared dependencies — split shared entries by platform (
browser/node) and bundling strategy (default/separate), then hand each group to the Adapter. Results are checksum-cached so unchanged externals are reused on the next build. - Emit federation artifacts — write
remoteEntry.json(name, shared metadata, exposes) andimportmap.jsoninto the project's output folder. These two files are the full contract the Runtime consumes.
A Full Build-to-Runtime Trace
- Your build pipeline invokes the Native Federation build for
shellandmfe1, each with their ownFederationOptions. - For each project, the Core reads
federation.config.js, normalizes it, and asks the Adapter to bundle exposed modules, mapped paths and shared externals. - The Core writes
dist/<project>/remoteEntry.jsonanddist/<project>/importmap.json. - The
distfolders are published behind stable URLs. - At startup, the Runtime in
shellfetchesmfe1'sremoteEntry.json, merges the shared-dependency metadata, and injects a combined import map into the document. - When the user navigates to a route backed by a remote,
loadRemoteModuledynamically imports the exposed module — the browser resolves it through the injected import map.
If you only remember one thing: the Core and Runtime speak a simple contract — remoteEntry.json plus an import map. Everything else (which bundler, which framework, which runtime) is swappable.
Where to Go Next
- The Mental Model — why the pieces are shaped this way.
- Terminology — canonical glossary for the terms used above.
- Coming from Module Federation? — the working reference implementation, ported from the webpack Module Federation example.