There was a resolve alias in our webpack config for a package that no longer shipped the way it used to. A plugin ordering constraint with a comment explaining what would break, but not why. A federation shared block copied between repositories often enough that the copies had drifted.
None of it was wrong. All of it was load-bearing. And four teams half-understood it, so every few months someone needing to change one line would go find whoever had touched it last.
Everyone quotes the build times. Mine got faster too, and it was pleasant, and it is not why I moved. I moved because that file was a queue.
The speed is the visible half
Angular's esbuild-based application builder has been the default for new applications since v17, and the webpack-based browser builder is deprecated. The headline is what you would expect: cold builds finish faster, the dev server starts faster, and the feedback loop tightens in a way you feel by the end of the first afternoon.
That is genuinely worth something. It is also the part of the argument I trust least, because build speed is easy to measure and easy to over-weight. A build that is thirty seconds faster saves real time. It does not change what your team is capable of.
The structural change does.
When federation lived in ModuleFederationPlugin, the composition model was a property of the bundler. Wanting a different build tool meant wanting a different federation implementation, which in practice meant not wanting a different build tool. Native Federation moves the same model onto ES modules and import maps, and expresses the setup as ordinary config:
// federation.config.js — no bundler API in sight
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'mfe-settings',
exposes: { './Settings': './src/app/settings/settings.ts' },
shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }) },
});
The build emits a JSON description of the result rather than a bundler-specific container:
GET https://settings.example.com/remoteEntry.json
{ "name": "mfe-settings",
"exposes": [ { "key": "./Settings", "outFileName": "Settings-63VUEVRP.js" } ],
"shared": [ { "packageName": "@angular/core", "version": "21.2.9", "singleton": true, … } ] }
A host consumes that file by fetching it. It does not need to know, or care, what produced it. That is the whole difference: the contract between your applications is now a file format instead of a plugin, so the tool that writes the file can be swapped without renegotiating the architecture.
Native Federation for Angular wraps the CLI's own builder and delegates the actual application build to it, rather than replacing it with a build pipeline of its own. That sounds like a footnote and is not. It means your build stays on the path the framework team maintains, and federation shrinks from a parallel build system you have to port to a thin adapter you wait a release on.
The federated shell demo is built this way — the host and both remotes on the esbuild builder, composed at runtime from manifest and JSON entries, with no bundler plugin in any config I own.
Where I would not move
I want to be direct, because opinion pieces that never concede anything are just advertising with a byline.
Staying on webpack is a legitimate position. A large federated setup that works, with a team fluent in its configuration, is an asset — and replacing a working asset because a newer thing exists is how organizations lose a quarter. Three things in particular should slow you down.
Custom webpack plugins. Anything that reaches into the module graph — a plugin that rewrites imports, injects a runtime, or manipulates chunks — has no direct translation. Some have esbuild plugin equivalents — though the application builder has no plugin hook of its own, so wiring one in means a third-party builder, which is its own trade. Some are behavior you will have to rebuild, and you should cost that before you commit, not after.
The shared config. It looks like it ports across, and mostly it does, but "mostly" is doing work in that sentence. Any place where you were exploiting webpack's specific negotiation semantics is a place to re-derive the intent rather than translate the syntax. Copy the meaning, not the block.
Anything downstream that assumed webpack. Bundle analysis, source map handling, a CI step that parses build output, a monitoring integration keyed to chunk names. These are individually small and collectively a week.
None of those are reasons never to move. They are reasons not to move on their own schedule, as a standalone modernization project with no user-facing outcome — which is the version that gets started, gets deprioritized in month two, and leaves you maintaining two build paths.
Do it while you are already in the build. An Angular major upgrade is the natural moment: you are touching dependencies anyway, you are already regression-testing the whole surface, and the migration tooling exists precisely for that path. The work is the same work. It just costs a fraction as much when it rides along with something that was already funded and already justified to whoever approves your roadmap.
The Best Build Config Is One Nobody Has to Specialize In
Every hour a developer spends becoming fluent in a build tool is an hour spent on something the user will never see. Sometimes that is a fair trade — depth in your tooling pays off when the tooling is doing something only it can do. What I stopped accepting was paying it by default, for a configuration nobody chose and everybody inherited. The move that mattered was not swapping one bundler for a faster one. It was making the composition model independent of the bundler, so the build became a detail again. A build tool has earned its place when your team can go a quarter without thinking about it.


