1. esbuild Today vs. Why Webpack Persists
Week 2 covered the modern Angular application builder — esbuild for dependency bundling, Vite for the dev server — and for the large majority of apps, it's simply the better default: faster cold starts, faster rebuilds, less configuration surface.
Teams still reach for Webpack underneath Angular for a narrower set of real reasons:
- A build customization esbuild's plugin API can't express yet — a specific loader, a legacy asset pipeline, or output shaping a project depends on.
- Module Federation (Section 4) — runtime composition of independently-built and independently-deployed bundles, which is a Webpack-specific capability with no first-class esbuild equivalent as of this course.
- A large legacy codebase already deeply invested in Webpack config that migrating off isn't currently worth the disruption.
None of these apply to most new Angular apps — treat Webpack as something you reach for when a specific requirement demands it, not a default to restore out of habit from older Angular versions.
2. Extending the Build Without Ejecting
@angular-builders/custom-webpack swaps Angular CLI's builder while keeping
everything else — angular.json, schematics, the dev server — intact. It
merges your own Webpack config into Angular's generated one rather than replacing it
wholesale.
{
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": { "path": "./webpack.extra.js" }
}
}
}
module.exports = {
module: {
rules: [
{ test: /\.gql$/, use: 'raw-loader' },
],
},
plugins: [
new webpack.DefinePlugin({
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
}),
],
};
This is deliberately narrow — you're not hand-rolling Angular's entire Webpack configuration (entry points, output, the Angular compiler plugin), only adding to it. "Ejecting" in the create-react-app sense — copying out the full generated config to edit directly — is rarely necessary and gives up the CLI's ability to keep your build current across Angular upgrades.
3. What You'd Actually Use It For
Concrete, common reasons a real project reaches for this builder:
- A non-standard file type — importing
.graphqldocuments as raw strings (feeding Week 26's codegen pipeline), or bundling a proprietary asset format a design tool exports. - Build-time constants — injecting a build timestamp, commit hash, or feature-flag defaults via
DefinePlugin, values that don't belong in a runtime environment file. - Output shaping for a specific legacy host — a hosting environment with unusual requirements about chunk naming or asset paths that the standard builder doesn't expose configuration for.
Each of these is a genuine one-off customization, not a reason to migrate a whole app's build off esbuild — the custom-webpack builder is meant for surgical additions, and reaching for it should stay the exception on a well-run Angular project.
4. Module Federation for Micro-Frontends
Week 24 covered micro-frontend architecture with Nx's build-time integration. Module Federation is a genuinely different mechanism: independently-built, independently deployed bundles that load each other's code at runtime, over the network, with no shared build step at all.
const { withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'catalog',
exposes: {
'./ProductList': './src/app/product-list/product-list.component.ts',
},
shared: { /* Section 5 */ },
});
module.exports = withModuleFederationPlugin({
remotes: {
catalog: 'catalog@https://catalog.example.com/remoteEntry.js',
},
shared: { /* Section 5 */ },
});
{
path: 'products',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'https://catalog.example.com/remoteEntry.js',
exposedModule: './ProductList',
}).then((m) => m.ProductListComponent),
},
The host application never bundles the catalog team's code at build time — it fetches
remoteEntry.js at runtime and loads the exposed component on demand,
through the exact same loadComponent lazy-loading mechanism from Week 11.
This is what lets the catalog team deploy independently, on their own schedule, without
the host application rebuilding at all.
6. Module Federation vs. Nx's Build-Time Integration
Week 24 built micro-frontends inside an Nx monorepo, composed at build time. Both solve "split a large app into independently-owned pieces" — the real question is which boundary actually matters for your org.
- Nx monorepo (Week 24): one repo, one build pipeline, apps composed at build time. Simpler mental model, easier to keep consistent, but every app still ships together — a change anywhere can require rebuilding and redeploying the whole workspace.
- Module Federation: genuinely independent repos, builds and deployments. The catalog team ships without coordinating with the checkout team's release. Worth the added operational complexity specifically when teams need to deploy on separate schedules — not merely because the codebase is large.
A large codebase that still deploys as one unit is usually better served by Nx's build-time composition; genuinely independent deployment cadence across teams is the actual justification for Module Federation's extra complexity.
7. Hands-on Exercise
Add a custom Webpack step, then federate a component
Two parts: a small custom-webpack customization, then a genuine two-app Module Federation setup.
Part 1 — custom-webpack:
- Install
@angular-builders/custom-webpackand swap the builder inangular.json(Section 2). - Add one real customization — a
DefinePlugininjecting a build timestamp constant, displayed somewhere in the app's footer. - Confirm the app still builds and serves normally through the standard
ng build/ng servecommands — the point of this builder is that nothing else about your workflow changes.
Part 2 — Module Federation:
- Create two separate Angular apps (a "host" and a "catalog" remote), each with its own
webpack.config.jsvia@angular-architects/module-federation. - Expose one standalone component from the remote (Section 4) and lazy-load it into a route in the host via
loadRemoteModule. - Configure
sharedcorrectly (Section 5) and confirm — via a breakpoint or a log statement — that only one instance of Angular's core is actually running on the page once both apps are loaded together. - Run both apps on different ports and confirm you can rebuild and redeploy the remote alone (restart just that dev server) without touching the host — the actual capability this whole mechanism exists to provide.
If the remote component renders but injected services behave strangely (a shared state service appears to have two different values depending on which app reads it), that's almost always a missed or misconfigured entry in shared — check every Angular package the two apps have in common, not just @angular/core.
8. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Given esbuild's speed advantage, what's the actual justification for reaching for Webpack on a new Angular project?
Given esbuild's speed advantage, what's the actual justification for reaching for Webpack on a new Angular project?
A specific capability esbuild doesn't offer — most commonly Module Federation, or a build customization (loader, plugin) esbuild's plugin API can't express. It shouldn't be a default choice; for most new apps esbuild is genuinely the better fit, and Webpack is a targeted exception.
Q2
What does @angular-builders/custom-webpack actually replace, and what does it leave alone?
What does @angular-builders/custom-webpack actually replace, and what does it leave alone?
It swaps the build tool the Angular CLI delegates to, merging your custom Webpack config into the CLI's generated one. It leaves angular.json, schematics, and the rest of the CLI workflow (ng build, ng serve) intact — you're extending the build, not replacing the CLI's control of it.
Q3
What actually goes wrong if @angular/core isn't marked singleton: true in a Module Federation setup?
What actually goes wrong if @angular/core isn't marked singleton: true in a Module Federation setup?
Each federated app loads its own copy of Angular, resulting in multiple separate Angular instances running on the same page. That breaks dependency injection and change detection across the boundary between host and remote — services can appear to have inconsistent state depending on which app's Angular instance is reading them.
Q4
What's the real deciding factor between Nx's build-time micro-frontend composition (Week 24) and Module Federation?
What's the real deciding factor between Nx's build-time micro-frontend composition (Week 24) and Module Federation?
Whether teams genuinely need to deploy on independent schedules. Codebase size alone doesn't justify Module Federation's added operational complexity — a large app that still ships as one deployable unit is usually better served by Nx's simpler, build-time composed monorepo approach; true independent deployment cadence is what Module Federation actually buys you.