Command
build
Is this a regression?
The previous version in which this bug was not present was
Angular 21 (@angular/build 21.x) — the same app built and ran correctly before the upgrade to Angular 22
Description
With @angular/build's default chunk-optimization pass (NG_BUILD_OPTIMIZE_CHUNKS, threshold 3), a production build of a large app that externalizes @angular/core (as Angular Native
Federation does for singleton sharing) produces chunks where the optimizer's renamer assigns the same minified identifier e to BOTH the external @angular/core namespace import AND a
component's named class expression in the same chunk. The class-expression name shadows the namespace inside the class body, so the static initializer calls <class>.ɵɵdefineComponent(...),
which is undefined → TypeError at bootstrap → blank white page.
Exact mechanism (compiled output)
import * as e from '@angular/core'; // external namespace import → minified to "e"
let e$1 = class e { // class-expression name → ALSO minified to "e"
static ɵfac = (o) => new (o || e)(); // e = the class (self-ref, correct)
static ɵcmp = e.ɵɵdefineComponent({ type: e }); // e.ɵɵdefineComponent: e = the class (WRONG — must be @angular/core)
};
Correct (non-optimized) output uses distinct names: import * as i0 from '@angular/core' + i0.ɵɵdefineComponent({ type: _MyComponent }).
It is specifically the identifier e
Measured on one real build (~130 lazy chunks importing the @angular/core namespace):
@angular/core namespace minified to… |
chunks |
of which collide |
e |
52 |
5 |
| any other letter (t, n, a, i, o, r, s, …) |
~78 |
0 |
namespace=e + class=t → fine; namespace=o + class=e → fine; only namespace=e + class=e collides. The renamer fails to deduplicate only for the letter e, and only when both a kept
external namespace import and a class-expression name in the same chunk land on it. Colliding components share no source pattern — it is purely a renamer name-allocation collision.
Same class of defect as the closed #32764 → rolldown/rolldown#8793 ("colliding variable names"), but still reproduces on [email protected] as bundled by @angular/[email protected].
Workaround:
NG_BUILD_OPTIMIZE_CHUNKS=0 — disables only the chunk-optimization pass; keeps mangling + other minification (main bundle ~126 kB vs ~129 kB).
NG_BUILD_MANGLE=0 — keeps full identifier names, so the namespace stays i0 and never collides.
Minimal Reproduction
A minimal synthetic reproduction could not be constructed, and the reason is diagnostic: the collision only occurs when the renamer's GLOBAL name-frequency allocation over a large, diverse module
graph happens to place the identifier e on both a kept external @angular/core namespace import and a class-expression name in the same chunk. In uniform synthetic apps (Native Federation +
optimizer, up to ~240 lazy chunks, even with ~35 imports per component and many e-namespace chunks), classes are allocated other letters (a/n/o/i) and never collide with the e namespace.
It reproduces reliably in a large Angular Native Federation host shell (~130 lazy chunks from ~270 diverse compiled components). Extensive bisection ruled out: federation infrastructure
(orchestrator, @module-federation/enhanced, share/sharedMappings config), the eager provider graph, the shared component library (an isolated MFE with 88 shared components at 157 chunks
does NOT collide), specific dependencies, experimentalDecorators, @angular/build/esbuild/native-federation versions, and raw chunk count. Two independent parts of the app each push it over
the threshold, confirming an emergent renamer-allocation effect rather than a single module.
Exception or Error
ERROR TypeError: e.ɵɵdefineComponent is not a function
at <static_initializer> (https://.../chunk-XXXXXXXX.js:1:4257)
at https://.../chunk-XXXXXXXX.js
at https://.../main-XXXXXXXX.js
(thrown from runInitializers → the application never bootstraps → blank page)
Your Environment
Angular CLI: 22.0.3
Node: 22.x
Package Manager: pnpm 10.10
OS: darwin arm64
Angular: 22.0.2
... @angular/build 22.0.3
... @angular-devkit/* 22.0.3
Additional:
@angular-architects/native-federation 22.0.5
@softarc/native-federation 4.3.1
rolldown (transitive via @angular/build) 1.0.3
Nx 23.1.0
TypeScript 6.0.3
Anything else relevant?
Command
build
Is this a regression?
The previous version in which this bug was not present was
Angular 21 (@angular/build 21.x) — the same app built and ran correctly before the upgrade to Angular 22
Description
With
@angular/build's default chunk-optimization pass (NG_BUILD_OPTIMIZE_CHUNKS, threshold 3), a production build of a large app that externalizes@angular/core(as Angular NativeFederation does for singleton sharing) produces chunks where the optimizer's renamer assigns the same minified identifier
eto BOTH the external@angular/corenamespace import AND acomponent's named class expression in the same chunk. The class-expression name shadows the namespace inside the class body, so the static initializer calls
<class>.ɵɵdefineComponent(...),which is
undefined→TypeErrorat bootstrap → blank white page.Exact mechanism (compiled output)
Correct (non-optimized) output uses distinct names:
import * as i0 from '@angular/core'+i0.ɵɵdefineComponent({ type: _MyComponent }).It is specifically the identifier
eMeasured on one real build (~130 lazy chunks importing the
@angular/corenamespace):@angular/corenamespace minified to…enamespace=e+ class=t→ fine;namespace=o+ class=e→ fine; onlynamespace=e+ class=ecollides. The renamer fails to deduplicate only for the lettere, and only when both a keptexternal namespace import and a class-expression name in the same chunk land on it. Colliding components share no source pattern — it is purely a renamer name-allocation collision.
Same class of defect as the closed #32764 → rolldown/rolldown#8793 ("colliding variable names"), but still reproduces on
[email protected]as bundled by@angular/[email protected].Workaround:
NG_BUILD_OPTIMIZE_CHUNKS=0— disables only the chunk-optimization pass; keeps mangling + other minification (main bundle ~126 kB vs ~129 kB).NG_BUILD_MANGLE=0— keeps full identifier names, so the namespace staysi0and never collides.Minimal Reproduction
A minimal synthetic reproduction could not be constructed, and the reason is diagnostic: the collision only occurs when the renamer's GLOBAL name-frequency allocation over a large, diverse module
graph happens to place the identifier
eon both a kept external@angular/corenamespace import and a class-expression name in the same chunk. In uniform synthetic apps (Native Federation +optimizer, up to ~240 lazy chunks, even with ~35 imports per component and many
e-namespace chunks), classes are allocated other letters (a/n/o/i) and never collide with theenamespace.It reproduces reliably in a large Angular Native Federation host shell (~130 lazy chunks from ~270 diverse compiled components). Extensive bisection ruled out: federation infrastructure
(orchestrator,
@module-federation/enhanced,share/sharedMappingsconfig), the eager provider graph, the shared component library (an isolated MFE with 88 shared components at 157 chunksdoes NOT collide), specific dependencies,
experimentalDecorators,@angular/build/esbuild/native-federation versions, and raw chunk count. Two independent parts of the app each push it overthe threshold, confirming an emergent renamer-allocation effect rather than a single module.
Exception or Error
Your Environment
Anything else relevant?
chunk-optimizer.jsis byte-identical between@angular/build22.0.3 and 22.0.7.[email protected](vs 0.28.1) via a package-manager override does not change anything.@angular-architects/native-federation(22.0.1 → 22.0.5) does not change anything.NG_BUILD_OPTIMIZE_CHUNKScan cause variable name collisions #32764, [Bug]: colliding variable names rolldown/rolldown#8793.