Builder
The @angular-architects/native-federation:build target — what it puts in angular.json, how it wraps Angular's ApplicationBuilder, and every option it accepts.
The @angular-architects/native-federation:build target is a thin wrapper around @angular/build:application. It runs the Native Federation build (shared bundles, exposed modules, remoteEntry.json), then delegates to Angular's Application Builder for the host/remote app itself. The same builder is used for both build and serve; the difference is configured by options.
On Angular 22+ the adapter ships under the base package @angular-architects/native-federation (22.x) — the names on this page assume it. On Angular 20/21 the identical builder is published as @angular-architects/native-federation-v4:build; substitute the -v4 package name throughout. See Getting Started for the version matrix.
On this page
- The angular.json layout
- What the builder does
- Builder options
- tsConfig and module resolution
- Native import maps
- Subresource Integrity
- Dev server & hot reload
- Developing npm-linked shared libraries
- Nx and @angular/build preloading
- Locale-aware output paths
The angular.json Layout
The init schematic doesn't replace your existing build — it shifts everything sideways and slots the federation builder on top. After running it, every federated project has four targets:
{
"architect": {
"build": { "builder": "@angular-architects/native-federation:build", ... },
"serve": { "builder": "@angular-architects/native-federation:build", ... },
"esbuild": { "builder": "@angular/build:application", ... }, // ← old build
"serve-original": { "builder": "@angular/build:dev-server", ... } // ← old serve
}
}
esbuild— the original@angular/build:applicationtarget (renamed frombuild). The federation builder calls into it.serve-original— the original@angular/build:dev-servertarget. The federation builder uses its options when serving.build— the new federation build. Itsconfigurationspoint at<project>:esbuild:production/<project>:esbuild:development.serve— the new federation dev-server target. Points at<project>:serve-original:development.
The schematic's defaults look like this:
"build": {
"builder": "@angular-architects/native-federation:build",
"options": {
"projectName": "mfe1",
"tsConfig": "projects/mfe1/tsconfig.federation.json",
"cacheExternalArtifacts": true,
"entryPoints": ["projects/mfe1/src/main.ts"]
},
"configurations": {
"production": { "target": "mfe1:esbuild:production" },
"development": { "target": "mfe1:esbuild:development", "dev": true }
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-architects/native-federation:build",
"options": {
"projectName": "mfe1",
"tsConfig": "projects/mfe1/tsconfig.federation.json",
"target": "mfe1:serve-original:development",
"rebuildDelay": 500,
"cacheExternalArtifacts": true,
"dev": true,
"devServer": true,
"port": 0,
"entryPoints": ["projects/mfe1/src/main.ts"]
}
}
What the Builder Does
For every ng build or ng serve the federation builder runs roughly the following sequence:
- Resolve the underlying target (the
esbuildorserve-originaltarget referenced byoptions.target) and load its options. - Construct an
NFBuildAdapterover esbuild and Angular'sSourceFileCache(see below). - Call the core's
normalizeFederationOptionswith the project'sfederation.config.mjs(falling back tofederation.config.jsif no.mjsfile exists, or the explicitfederationConfigPathoption if set) andtsconfig.federation.json. - Validate that there are no invalid
.-imports in the externals (a current Vite limitation — see vitejs/vite#21036). Shared mappings are checked by the core itself, at the end ofnormalizeFederationOptions— on the set left after pruning and wildcard expansion, so paths that were never going to be published stay quiet. - Run the core's
buildForFederation— this writes the shared bundles, exposed modules andremoteEntry.jsoninto the configured output directory. - If I18N is configured, post-process the federation artifacts with
localize-translate(one bundle copy per target locale). - Hand off to
buildApplication(build) orserveWithVite(serve), passing the computed externals through to esbuild and registering federation middleware on the dev server. - In watch / dev mode: re-run steps 5–6 on every Angular rebuild, debounced by
rebuildDelay.
The build is rejected entirely (no federation artifacts written) when the underlying Angular build fails. Federation rebuilds in dev mode are cancellable: a new file change aborts an in-flight federation rebuild via an AbortSignal instead of stacking up.
Builder Options
Every property below comes from src/builders/build/schema.json:
| Option | Type | Default | Description |
|---|---|---|---|
target | string | — | The Angular target this builder delegates to (e.g. mfe1:esbuild:production, or for serve mfe1:serve-original:development). Required when used as a serve builder; for build it's set per configuration. |
projectName | string | — | The Angular project name. Used for the federation cache key and for default output paths. |
tsConfig | string | underlying target's tsconfig | A specific tsconfig used only for the federation build (exposed modules + shared mappings). The schematic creates tsconfig.federation.json for this purpose so the federation build doesn't pick up your test types or app-only paths. It also drives esbuild's module resolution, so it must declare or extend the workspace baseUrl/paths (see below). |
federationConfigPath | string | federation.config.mjs | Path to the project's federation config file. Point it elsewhere if your config doesn't sit next to the project or isn't named federation.config.mjs. Added in 22.0.1. |
entryPoints | string[] | [<tsConfig dir>/src/main.ts] | Entry points used to detect which dependencies are actually used. Combined with features.ignoreUnusedDeps in federation.config.mjs this drives shaking of unused shared externals. Seeded by the init / update-v4 schematics; defaults to [<sourceRoot>/main.ts]. |
dev | boolean | false | Enables development mode for the federation build: source maps, unminified output, watch-mode SSE notifications, and (for SSR) the dev host-instance bridge that registers the federation loader inside Vite's SSR graph. Set automatically by the development configuration. |
watch | boolean | false | Re-runs the federation build on file changes. Set automatically when serving; useful for ng build --watch. |
devServer | boolean | inferred from target name | Force the builder into dev-server mode. By default the builder serves whenever the target name contains "serve"; override here if your naming is unusual. |
port | number | 0 | Port for the dev server. 0 inherits the underlying serve-original target's port (which the schematic seeds with the --port argument). |
rebuildDelay | number (ms) | 2000 | Debounce window before re-running the federation build after Angular reports a change. Bursts of file saves get coalesced; in-flight rebuilds are cancelled in favour of the latest. The schematic seeds 500 for serve for snappy DX. |
cacheExternalArtifacts (alias cache) | boolean | true | Reuse the bundled external artifacts from node_modules/.cache/native-federation/<project> across builds. See core caching for the checksum logic. |
baseHref | string | — | Overrides the underlying Angular target's baseHref. Also used by the dev server to strip the prefix from federation artifact requests. |
outputPath | string | dist/<project> | Output base directory. The federation artifacts land in <outputPath>/browser/<sourceLocale?>. |
ssr | boolean | false | Marks this build as SSR-capable. When true, externals are passed through Angular's externalDependencies instead of an esbuild plugin (the SSR build path doesn't run that plugin). The CLI's server.mjs is emitted as-is; the federation loader is registered at launch via the node --import @angular-architects/native-federation/node-preload … preload (prod) or the dev host-instance bridge (ng serve). See SSR & Hydration. |
esmsInitOptions | object | { shimMode: true } | Options injected into the <script type="esms-options"> tag added to index.html. Forwarded to es-module-shims. Set { shimMode: false } to opt out of shim mode and use the browser's native import maps instead. |
skipHtmlTransform | boolean | false | Skip the index.html rewrite (script tags → type="module-shim" + esms-options). Useful if you template index.html yourself. |
buildNotifications | object | { enable: true, endpoint: '/@angular-architects/native-federation:build-notifications' } | Server-Sent Events stream that notifies a host when a remote finishes (re)building. See below. |
tsConfig and Module Resolution
The tsConfig option doesn't only select which files are compiled — it is passed through to esbuild's BuildOptions, so it also decides how bare specifiers resolve.
The reason is that Angular's compiler plugin only hooks onLoad, leaving esbuild to resolve specifiers itself. Without an explicit tsconfig, esbuild only honours baseUrl/paths from an auto-discovered file named exactly tsconfig.json — which misses tsconfig.app.json / tsconfig.lib.json layouts and breaks workspace imports with Could not resolve. Angular's own application builder passes the same normalized path.
Practically: the tsconfig you point tsConfig at must declare the workspace baseUrl and paths, or extends a config that does. The tsconfig.federation.json the schematic generates already extends the workspace root config, so generated projects need no change.
Native Import Maps
Since 22.0.3. By default the adapter runs es-module-shims in shim mode (esmsInitOptions: { shimMode: true }), which rewrites the app's script tags to type="module-shim". Modern browsers now support import maps natively, so you can opt out of shim mode and let the browser resolve the import map directly:
"options": {
"esmsInitOptions": { "shimMode": false }
}
The build and runtime sides must agree. If you disable shim mode in the builder, disable it on the runtime too — pass shimMode: false to the orchestrator's useShimImportMap (or initFederation) in your main.ts:
initFederation('federation.manifest.json', {
...useShimImportMap({ shimMode: false }),
// ...
});
Native import maps require a recent browser (Chrome/Edge 133+, Safari 18.4+, Firefox 150+); shim mode remains the default for broader compatibility. See Runtime for the runtime side.
Subresource Integrity
Since 22.0.1. When Angular's own subresourceIntegrity option is enabled, the CLI stamps integrity and crossorigin attributes onto the polyfills and main script tags. The federation builder now preserves those attributes when it rewrites the tags for federation — only the type attribute is changed. This keeps SRI hashes intact for environments that require them (e.g. PCI DSS compliance).
Dev Server & Hot Reload
When serving, the builder layers two things on top of Angular's Vite-based dev server:
- Federation file middleware. Every request that matches a file under the federation output dir (the shared bundles, the exposed modules,
remoteEntry.json) is served straight from disk with permissive CORS headers. This is what lets a hostfetcha remote onlocalhost:4201from a shell onlocalhost:4200. - Build notifications (SSE). When
buildNotifications.enableis true (the default), the dev server exposes an event stream at/@angular-architects/native-federation:build-notifications. Hosts can subscribe and trigger a reload whenever a remote rebuilds. The stream emitscompleted,cancelledanderrorevents. More on automatic shell reloading.
Developing npm-linked Shared Libraries
A common local-development setup is to build a shared library in its own repo with ng build --watch (ng-packagr) and pull it into the host with npm link.
Because Native Federation shares such a library as an external, it is excluded from Angular's own build — the change lands under node_modules, which Angular's build watcher skips. The federation builder covers that gap: it detects linked shared packages and re-bundles them on change, so no cache clear or dev-server restart is needed.
Requirements:
- The library is listed in the
sharedsection of yourfederation.config.mjs(viashareAll, an explicitsharedentry, orsharedMappings). - Its package directory under
node_modulesis a symlink — linked withnpm linkor the equivalent, not installed from a registry. - The library is rebuilt on change so the symlink target actually updates (
ng build --watchfor an Angular library).
# 1. In the shared library's repo — build to dist/ and keep watching
ng build --watch
# 2. Publish the built package to the local npm link registry
cd dist/my-lib && npm link
# 3. In the host repo — link the package into node_modules
npm link @my-scope/my-lib
# 4. Run the host as usual
ng serve
Editing a source file in the library now rebuilds its dist/, and the builder re-bundles the affected shared external and logs Done!. To also refresh the browser automatically, enable SSE-based reloading with initFederation(manifest, { sse: true }); otherwise refresh manually.
Under the hood the builder resolves the real path of each symlinked shared package and adds it to the federation file watcher. Linked packages live under node_modules, so they are watched by polling, and a short debounce coalesces ng-packagr's atomic multi-file writes into one rebuild. Only the affected shared externals are re-bundled; registry-installed dependencies keep the version-only cache fast path (see core caching).
Nx and @angular/build Preloading
The builder disables two Angular build features by setting environment variables before @angular/build loads:
NG_BUILD_OPTIMIZE_CHUNKS=0— Angular's chunk optimization pass (on by default in Angular 22 for production builds, from three lazy chunks upwards) re-bundles the esbuild output after Native Federation has computed its import map, so shared externals such as@angular/coreare no longer resolved as singletons. At runtime that surfaces asɵɵdefineComponent is not a function.NG_BUILD_PARALLEL_TS=0— lets the compilation steps share one cache, which is considerably faster here.
@angular/build reads those variables once, when it is first loaded. Under the Angular CLI the builder is loaded first, so this works. Nx loads @angular/build before it resolves the builder (nx/src/adapter/compat.js requires @angular/build/private to stub a version assertion), so the variables arrive too late and both features stay on — see #107 / #114.
The builder detects this and re-applies both settings to the already-loaded @angular/build, logging:
INFO @angular/build was already loaded when this builder started (Nx preloads it),
so its build environment was stale; re-applied useParallelTs=false,
optimizeChunksThreshold=Infinity.
That line is informational — the problem was corrected and no action is needed. Two things are worth knowing:
- Nx replays cached artifacts, so run one uncached build (
nx build my-app --skip-nx-cache) if a previous build produced broken output. - To set the variables yourself instead, put them in a workspace-root
.envfile — Nx loads dotenv files before@angular/build— and add{ "env": "NG_BUILD_OPTIMIZE_CHUNKS" }to the target'sinputsso the cache reacts to changes. SettingNF_NG_BUILD_ENV_REPLAY=0then keeps the builder from touching the loaded module at all.
If the builder instead warns that it could not re-apply a setting, @angular/build has changed internally: use the .env approach and report it upstream.
Locale-aware Output Paths
When the underlying Angular target has localize enabled (or an array of locales) the federation artifacts are written under the source-locale subfolder, mirroring Angular's convention:
dist/mfe1/browser/
├── en/ ← source locale, original federation artifacts
│ ├── remoteEntry.json
│ ├── _angular_core.<hash>.js
│ └── ...
├── de/ ← translated copy, generated by localize-translate
│ ├── remoteEntry.json
│ └── ...
└── fr/
└── ...
Configure your production federation.manifest.json to point at the locale-specific URL (/de/remoteEntry.json). See I18N for the details.
How the Adapter Wraps esbuild
For builds that emit federation artifacts (shared externals, exposed modules, mapped paths) the adapter creates two distinct esbuild contexts:
- Mappings & exposed modules are bundled with Angular's full toolchain (the AOT compiler plugin, stylesheet handling, …) so an exposed component compiles exactly like it would in your app.
- Shared externals from
node_modulesare bundled with a plainer esbuild context — they're already-compiled JS and don't need the Angular plugins.
Both contexts share the same SourceFileCache Angular uses for incremental rebuilds, so a TypeScript file invalidated in the federation build is also picked up by the next Angular rebuild.
Related
- Schematics — the schematic that wires this
angular.jsonfor you. - Custom Builder — drop in extra esbuild plugins via
runBuilder. - Core: Build Process — the underlying lifecycle the builder is wrapping.
- Core: Caching — what
cacheExternalArtifactscaches and how invalidation works.