DEV Community

Sakthikumaran Navakumar
Sakthikumaran Navakumar

Posted on AI-assisted

Native Federation - Deep Dive - Part 1/8 - The Mental Model, Why Native Federation Exists and What Changed in v4

Part 1 of an 8-part series on Native Federation - A deep dive.

🤖 A note on this article: I used Claude to help reformat and structure the content to make it clearer and more presentable for publication.

If you've shipped a production micro-frontend system on webpack Module Federation, you already know the mental model: a host application lazy-loads code it never saw at compile time, from a remote it doesn't own, and the two negotiate shared dependencies at runtime so you're not shipping three copies of React down the wire. Native Federation didn't invent that model — it inherited it deliberately, because the model was never the problem. The implementation was.

This article isn't going to re-teach you what a remote is. It's going to explain why an entire second implementation of the same mental model was worth building, what structural debt it paid off, and what the v4 rewrite tells us about where the project — and the pattern — is heading. Everything that follows in this series (Core, Adapters, the Orchestrator, version drift, migration) is downstream of the argument in this article, so it's worth getting precise about it before we go deep on any one subsystem.

The compromise nobody talked about

Webpack Module Federation is, technically, remarkable. It solved cross-application dynamic linking inside a tool that was never designed for it, wrapping every federated module in a runtime container and negotiation layer that webpack manages on your behalf. It works. Thousands of production systems prove it works.

But it works inside webpack's universe. Every remote, every host, every build step in that architecture is mediated by webpack's own module runtime — its own resolution logic, its own chunk-loading machinery, its own container format. That's not a criticism of the implementation; it's an acknowledgment of what problem webpack was actually solving when it introduced Module Federation: how do you retrofit dynamic, runtime remote loading onto a bundler whose entire value proposition is static dependency analysis at build time? The container pattern is the necessary compromise.

The compromise has a cost, and if you've operated Module Federation at scale, you've paid it:

  • Framework lock-in at the tooling layer. Your host and every remote are implicitly committed to webpack (or a webpack-compatible bundler) for the life of the federated system, because the runtime contract is a webpack runtime contract.
  • A parallel module system living alongside the real one. The browser has a native module system — ES Modules — that has matured enormously in recent years. Module Federation doesn't use it; it builds its own resolution and loading logic on top of webpack.
  • Migration friction that compounds. Moving a host off webpack — to Vite, to esbuild, to whatever ships next — means either dragging Module Federation's runtime along as a dependency of a tool you're trying to leave, or re-architecting your federation layer as part of a bundler migration. Two hard problems, coupled.

None of this made Module Federation the wrong choice when it was the only serious option. It made it a choice with an expiration date tied to how long webpack stayed the default.

What "browser-native" actually buys you

Native Federation's core bet is structural: instead of building a container protocol on top of a bundler's runtime, build directly on ECMAScript Modules and Import Maps — both of which are web standards the browser already implements, with no bundler runtime mediating the load.

Concretely, that means:

  • A remote's exposed module is a genuine ES module, served as a genuine .js file, importable with a genuine dynamic import().
  • Shared dependency resolution happens through the browser's import map mechanism — a JSON structure that tells the browser "when code asks for react, resolve it to this URL" — rather than through a bundler-authored sharing runtime.
  • The build tool's job shrinks to what a build tool should do: produce standards-compliant output and a manifest (remoteEntry.json) describing what's exposed and what's shared. It stops being responsible for how the browser resolves modules at runtime, because the browser already knows how to do that.

This is the part that's easy to undersell as "just an implementation detail," but for an architecture team it's the whole point: the bundler becomes replaceable. Native Federation ships a reference esbuild adapter and — most maturely — an Angular adapter that hooks directly into Angular's own esbuild-based Application Builder, but the underlying contract (ESM + import maps + a JSON manifest) doesn't require any of that. Swap the build tool, keep the federation contract. That's not true of container-based Module Federation, and it's the single biggest reason to treat this as an infrastructure decision independent of your current framework and bundler choices — not just an Angular-ecosystem convenience.

Why this matters more in v4 than it did in v3

Native Federation existed well before v4, largely as a feature of the Angular CLI plugin ecosystem — genuinely useful, but organizationally still shaped like "a clever thing the Angular Architects team built for Angular users." v3 proved the ESM/import-map model worked in production.

v4 is the rewrite that makes that claim credible, and the evidence isn't a changelog bullet point — it's organizational. The project moved out of the original angular-architects/module-federation-plugin monorepo entirely and now lives under its own GitHub organization, split into independently versioned repositories: a framework-and-bundler-agnostic core, a dedicated esbuild adapter, a dedicated Angular adapter, and a separately maintained orchestrator for runtime consumption.

That's not cosmetic restructuring. It's the difference between "a plugin with internal modules" and "a platform with published contracts between its layers." When a build tool, a framework adapter, and a runtime consumer are three separately versioned artifacts instead of three files in the same package, you get to ask a much sharper architectural question about any given piece of the system: what does this layer promise to the layer above it, and what can I swap out without breaking that promise? We'll answer that question concretely for every layer over the next two articles.

The other structural signal in v4 worth naming now, because it recurs throughout this series: the project explicitly frames itself as moving toward first-class support for frameworks beyond Angular, and toward runtime consumption from environments that aren't single-page applications at all — plain HTML pages, and server-rendered hosts in different ecosystems, consuming the same manifest contract. Angular remains the most mature adapter today by a wide margin, and this series will go deep on it because that's where the tooling is production-ready — but the ceiling of the architecture is no longer "an Angular thing." That ceiling matters when you're making a five-year platform bet, not just a next-quarter delivery decision.

Series Roadmap

This is an 8-part series, and each article builds on the ones before it. Here's the full plan, and where we are right now:

  1. The Mental Model, Revisited — why Native Federation exists and what changed in v4 (this article)
  2. Anatomy of the v4 Package Graph — Core, Adapters, Runtime, and Orchestrator as independently versioned layers
  3. Build-Time, End to End — the Core library and its Adapters, and what they actually do to your code
  4. The Classic Runtime — what it got right, and where it hits its ceiling
  5. The Orchestrator — semver-aware resolution and persistent caching, under the hood
  6. Version Drift and Resolution Strategy — the governance problem nobody's build pipeline catches
  7. v3 vs. v4 — a systematic comparison and a real migration playbook
  8. Reference Architecture — running Native Federation v4 in a regulated enterprise

Article 2 is next: it takes the org restructuring covered above seriously and maps the actual package graph, so you know precisely which layer you're touching, and which promises it's making to the layers around it, before we go deep on any single one.


Next in this series: **Anatomy of the v4 Package Graph* — mapping Core, Adapters, Classic Runtime, and the Orchestrator as independently versioned layers with explicit contracts between them.*

Top comments (0)