Angular Config
What @angular-architects/native-federation/config adds on top of the core federation.config.mjs — Angular skip list, platform inference and locale handling.
The Angular adapter re-exports the core's withNativeFederation, share and shareAll from a wrapper at @angular-architects/native-federation/config. The wrappers are thin: same options, same shape, same semantics. They only differ in the Angular-aware defaults they apply. For everything that isn't called out here, refer to the canonical core configuration reference.
On this page
- Imports
- withNativeFederation differences
- NG_SKIP_LIST
- Building the shared config from package.json
- Platform inference
- shareAngularLocales & locale handling
- Version-pinned share scopes
- Shared mappings
- What the schematic generates
Imports
import {
withNativeFederation,
share,
shareAll,
fromPackageJson,
shareAngularLocales,
autoShareScope,
mappingsFromWorkspace,
NG_SKIP_LIST,
} from "@angular-architects/native-federation/config";
Everything except mappingsFromWorkspace is an Angular-aware wrapper; mappingsFromWorkspace is a plain re-export of the core builder (see below). Anything else (helpers, other types, advanced overrides) lives in the core — import it from @softarc/native-federation/config or @softarc/native-federation/domain.
Since 22.1.3 the FederationConfig type is exported from this entry point too, next to the withNativeFederation that takes it. Since 22.2.0 so are ExternalConfig, SharedExternalsConfig and SharedMappingEntry. Add // @ts-check at the top of federation.config.mjs to have your editor check the config against FederationConfig.
What withNativeFederation Adds
The Angular wrapper does three things and then delegates to the core:
- If you didn't set
platformexplicitly, it infers it from your shared dependencies (see below). - It calls the core's
withNativeFederationwith your config. - If
features.ignoreUnusedDepsis off, it strips every@angular/common/locales/*entry from the resulting shared map. This preserves backwards-compatibility with v3 setups where Angular's locale data needed to be pulled in differently.
Everything else — name, exposes, shared, sharedMappings, skip, chunks, features, build modes, … — comes from the core and lives in federation.config.mjs. See the core configuration reference for the complete schema.
NG_SKIP_LIST
The Angular share and shareAll default to NG_SKIP_LIST instead of the core's DEFAULT_SKIP_LIST. The list extends the core defaults with packages that should never be shared in an Angular project:
export const NG_SKIP_LIST: SkipList = [
...DEFAULT_SKIP_LIST,
"@angular-architects/native-federation",
"zone.js",
"@angular/localize",
"@angular/localize/init",
"@angular/localize/tools",
/^@nx\/angular/,
(pkg) => pkg.startsWith("@angular/") && !!pkg.match(/\/testing(\/|$)/),
// only when the optional peer is not installed:
(pkg) => pkg.startsWith("@angular/platform-browser/animations") && !isInstalled("@angular/animations"),
(pkg) => /^@angular\/(router|common)\/upgrade$/.test(pkg) && !isInstalled("@angular/upgrade"),
];
Why these:
zone.jspatches global APIs and must run once per realm — sharing across remotes corrupts patching.@angular/localizeentries inject globals at boot and aren't safe to load lazily.@angular/platform-browser/animations*and@angular/{router,common}/upgradeimport an optional peer (@angular/animations,@angular/upgrade) and can't be built without it. Since 22.2.4 they are skipped only when that peer isn't installed; with it installed they are shared like any other entry point.@nx/angularis dev-only.- Any
@angular/.../testingentry — these are not part of the runtime.
You can pass your own skip list to shareAll or share:
shareAll(
{ singleton: true, strictVersion: true, requiredVersion: "auto" },
{ skipList: [...NG_SKIP_LIST, /^@my-org\//] },
);
For the broader semantics of skip (entry-by-entry exclusion vs. the global skip list), see core configuration → skip.
Building the Shared Config from package.json
As an alternative to shareAll, the fromPackageJson builder derives the shared config from your package.json and returns a fluent builder you can refine before handing it to withNativeFederation:
import {
withNativeFederation,
fromPackageJson,
} from "@angular-architects/native-federation/config";
export default withNativeFederation({
shared: fromPackageJson({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
})
.skip(["rxjs/ajax", "rxjs/fetch"])
.override({ "large-lib": { singleton: false } }),
});
Unlike the core's fromPackageJson, this adapter's version pre-seeds NG_SKIP_LIST — the same list shareAll uses — so Angular-internal and localization packages are skipped out of the box and .skip() only lists what is specific to your app. The builder methods (.only, .skip, .override, .patch) are otherwise identical, and shared takes the builder as is; .get() and the share helper are deprecated, as in the core. See core configuration → shared.
Platform Inference
Native Federation builds for either browser (default) or node. The core requires you to set platform explicitly; the Angular wrapper infers it: if any shared dependency starts with @angular/platform-server or @angular/ssr, it sets platform: 'node'. Otherwise it stays on browser. A builder passed as shared is resolved first, so the inference sees the packages it shares.
You can always override:
export default withNativeFederation({
name: 'mfe1',
platform: 'node', // ← explicit wins
shared: { ... },
});
shareAngularLocales & Locale Handling
Angular ships per-locale data as separate files under @angular/common/locales. They aren't proper subpath exports, so the standard share helpers can't pick them up. The adapter has two answers:
The recommended path: ignoreUnusedDeps
Locale loading works out of the box if you opt into ignoreUnusedDeps (the default for new projects):
export default withNativeFederation({
// ...
features: {
ignoreUnusedDeps: true,
},
});
The core scans your entry points and only ships the locale files you actually import. Nothing extra to configure.
The fallback: shareAngularLocales
If you can't enable ignoreUnusedDeps, declare the locales explicitly:
import {
withNativeFederation,
shareAll,
shareAngularLocales,
} from "@angular-architects/native-federation/config";
export default withNativeFederation({
name: "mfe1",
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
...shareAngularLocales(["en", "de", "fr"]),
},
});
shareAngularLocales(keys, opts?) generates one shared entry per locale key (@angular/common/locales/<key>), published at the installed @angular/common version. Pass opts.config to override the default { singleton: true, strictVersion: true, requiredVersion: 'auto' }; pass opts.legacy: true to use the old .mjs filenames if your @angular/common still ships them.
Since 22.2.5 it also takes opts.pool and opts.build, which win over the same fields in config. Set them the way @angular/common is shared, so the locales join its pool and its bundle. With a different build, the locale lands in a second bundle and the remote publishes a second @angular/common record:
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: "auto", build: "package" }),
...shareAngularLocales(["en", "de", "fr"], { build: "package" }),
},
When @angular/common is in a pool, pass the same label as pool.
See Localization for the wider context.
Version-Pinned Share Scopes
Since adapter 22.0.4 / 21.2.x. A shareScope isolates shared dependencies into a named bucket, so packages are only shared between remotes that use the same scope. The autoShareScope helper derives that name from a dependency's declared version, letting you pin sharing to a version line without hardcoding the number:
import {
withNativeFederation,
shareAll,
autoShareScope,
} from "@angular-architects/native-federation/config";
export default withNativeFederation({
// Only share with remotes built against the same Angular minor, e.g. "ng21.1"
shareScope: autoShareScope(),
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
autoShareScope(opts?) reads the version from dependencies, devDependencies or peerDependencies in your package.json and returns a scope string. It accepts:
| Option | Type | Default | Description |
|---|---|---|---|
level | 'major' | 'minor' | 'patch' | 'minor' | Granularity of the generated scope. |
dependency | string | '@angular/core' | The package whose version drives the scope. |
prefix | string | 'ng' | Prepended to the generated name. |
projectPath | string | cwd() | Where to start looking for package.json. |
The level controls the granularity of the generated scope (given @angular/core is 21.1.4):
level | Result |
|---|---|
'major' | "ng21" |
'minor' | "ng21.1" (default) |
'patch' | "ng21.1.4" |
You can also point it at another package or set a per-dependency scope:
export default withNativeFederation({
shareScope: autoShareScope({ level: "patch" }),
shared: {
// Override the scope for a single package
rxjs: {
singleton: true,
shareScope: autoShareScope({ dependency: "rxjs" }),
},
},
});
autoShareScope throws if the dependency isn't declared, or if the declared version lacks enough segments for the requested level.
Shared Mappings
Workspace libraries mapped in compilerOptions.paths are shared via sharedMappings. Besides plain strings, an entry can pair a list of patterns with an ExternalConfig, so a mapped path carries the same metadata as a shared npm package:
export default withNativeFederation({
sharedMappings: [
"@my-org/auth-lib",
[["@my-org/ui/*"], { singleton: false }],
],
});
For more than a couple of entries, the adapter re-exports the core's mappingsFromWorkspace builder, so a config file can reach it without depending on @softarc/native-federation directly:
import {
withNativeFederation,
mappingsFromWorkspace,
} from "@angular-architects/native-federation/config";
export default withNativeFederation({
sharedMappings: mappingsFromWorkspace({
singleton: true,
strictVersion: true,
})
.only(["@my-org/ui/*", "@my-org/auth-lib"])
.patch(["@my-org/ui/*"], { singleton: false }),
});
Since 22.2.4 the builder also has .package(prefix, cfg?), which declares where a mapped library begins and ends when slashes in your workspace mark a domain rather than a package (@shared/components/table vs @shared/components/collapse). See core configuration → Declaring mapping packages.
See core configuration → sharedMappings for the builder methods, which ExternalConfig properties a mapping honours, how includeSecondaries: { keepAll: true, resolveGlob: true } keeps mappings nothing imports, and why only barrel imports can be shared as a mapped path.
What the Schematic Generates
For reference, this is the federation.config.mjs that ng add emits for a remote on v4:
import {
withNativeFederation,
fromPackageJson,
} from "@angular-architects/native-federation/config";
export default withNativeFederation({
name: "mfe1",
exposes: {
"./Component": "./projects/mfe1/src/app/app.component.ts",
},
//shareScope: autoShareScope({level: 'minor'}),
shared: fromPackageJson({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
build: "package",
})
.patch(["@angular/*"], { includeSecondaries: { keepAll: true } }),
//sharedMappings: mappingsFromWorkspace({ singleton: true }).filter(['@my-org/*']).get(),
skip: ["rxjs/ajax", "rxjs/fetch", "rxjs/testing", "rxjs/webSocket"],
// Please read our docs about configuring native federation:
// https://native-federation.com/docs/v4/angular-adapter/configuration/
features: {
denseChunking: true,
denseExternals: true,
strictImports: false,
},
});
Notable defaults: build: 'package' for the per-package build mode (each external gets its own meta file — see build modes), includeSecondaries.keepAll patched onto every @angular/* package (see below), denseChunking: true to compress the remoteEntry.json, and denseExternals: true so each package's entry points travel as one entry. .patch() merges into the entries fromPackageJson already built, so every Angular package keeps the base options and its installed version. strictImports is spelled out as false; flip it to fail the build on an import nothing resolves. Configs generated before 22.2.0 spread shareAll(...) with an overrides entry instead; the resolved config is the same.
Why keepAll for @angular/core?
With ignoreUnusedDeps on, the core only shares the secondary entry points (e.g. @angular/core/rxjs-interop) that this remote actually imports. That's fine for most packages but costly for Angular: the orchestrator serves each package from one build, so a remote that imports a secondary the serving build omitted runs its own copy of that package — a second Angular instance, which fails with NG0203 when its components render inside the host's tree. includeSecondaries: { keepAll: true } makes every build publish the same Angular entry points, so they can all share one.
The builder warns when denseExternals is off or an @angular/* package is shared without keepAll; the template above avoids both.
Related
- Core: federation.config.mjs reference — the canonical source for every option.
- Core: Sharing Dependencies — share, shareAll, secondary entry points, the downsides of sharing.
- Localization — locale data in detail.