The Mental Model

Native Federation inherits the proven Module Federation model — host, remote, shared — and re-expresses it on top of plain ESM and Import Maps. This page is about the why: what problem each concept solves and how the pieces fit together at runtime. For precise definitions of every term used below, see Terminology.

Runtime Integration, Not Build-Time Coupling

Traditional lazy loading stitches every async boundary at build time — the bundler needs to know, up front, which chunks might show up later. Federation flips that: the host loads code it has never seen during compilation. A separately built, separately deployed remote can be wired in at runtime, so teams keep their own release cadence without needing to rebuild anyone else's bundle.

That single shift is what enables Micro Frontends and plugin-style extensions. Everything else in the mental model follows from it.

Why Import Maps Are the Natural Fit

If the host must resolve a remote's import '@angular/core' at runtime — with no help from the bundler — the browser needs a standard way to be told "when you see this specifier, fetch from this URL." That's exactly what an Import Map does.

So the build step collects, per project, two artifacts: an importmap.json (this project's view of where its externals live) and a remoteEntry.json (what this project publishes and expects). At startup the runtime reads each listed remote's remoteEntry.json, merges the maps, resolves version conflicts, and injects a single import map into the document. From that point on the browser itself resolves every subsequent import — no bundler, no custom module loader.

That's also why Native Federation is not tied to a specific bundler: Import Maps are a browser feature, not a webpack feature.

Sharing Is Always a Negotiation

If the host needs Angular 20.1.4 and a remote says it wants Angular 20.1.2, somebody has to decide which copy actually loads. That negotiation — "here is what I expect, here is what I can tolerate" — is what the flags on a shared dependency describe:

Version negotiation is the part most people discover the hard way, so it's worth internalizing up front: "shared" doesn't mean "the same version everywhere." It means "the federation runtime picks one copy that everyone agrees to use."

How the Pieces Line Up at Runtime

  1. Each project builds and publishes its remoteEntry.json + shared bundles to a URL.
  2. The host's manifest lists those URLs. The runtime (or orchestrator) fetches each one at startup.
  3. It merges the shared-dependency declarations, resolves version conflicts using the flags above, and injects the combined import map.
  4. When the user hits a route backed by a remote, loadRemoteModule triggers a plain dynamic import() — resolved through the injected map.

That's the whole runtime story. The Architecture Overview maps these steps to the layers that implement them.

Further Reading