Command
test
Is this a regression?
The previous version in which this bug was not present was
No response
Description
Since @angular/build 22.1.5, ng test --no-watch no longer completes on our suite. The build
phase — before any vitest worker spawns — grows a single native esbuild process past 5 GB and
hard-freezes a 16 GB machine. It never reaches the test run. No application code changed; only
the builder version.
The cause is disableCodeSplitting: true, added in 22.1.5 to
src/builders/unit-test/runners/vitest/build-options.js, which sets splitting = false and
ignoreAnnotations = true in src/tools/esbuild/application-code-bundle.js.
Because every spec file is its own entry point, disabling splitting makes each bundle inline its
own private copy of all shared first-party code, and ignoreAnnotations additionally prevents
sideEffects: false pruning. Memory scales linearly with spec count times shared-graph size.
I understand this fixes a real correctness bug (shared-chunk exports read as undefined via live
ESM bindings). The problem is that the trade-off is unconditional, and on a suite of a few hundred
specs the memory cost is not survivable.
Measurements — peak RSS of the esbuild process on 22.1.6, same tree, --include used to vary
only the number of entry points:
| spec files |
peak RSS |
| 25 |
387 MB |
| 50 |
723 MB |
| 100 |
1251 MB |
| 200 |
2273 MB |
Linear at ~10.8 MB per spec file, projecting ~4.8 GB for our full 431 specs.
Projects using barrel files (index.ts re-exports) are hit far harder, since ignoreAnnotations
prevents pruning the unused members a barrel drags in. Same suite and builder, before removing
barrel imports: 25 specs → 2025 MB (vs 387 MB), 50 specs → over 3089 MB (vs 723 MB). That is
~80 MB per spec, projecting ~30 GB, which is why the failure presents as an instant machine freeze
rather than a slow build.
Patching disableCodeSplitting back to false on 22.1.6 runs the full suite at 772 MB peak with
all 431 files / 3523 tests passing, confirming this flag is the sole cause.
Suggested resolution — either expose disableCodeSplitting as a unit-test builder option so
projects can opt back into splitting (it is currently hardcoded and absent from both schema.json
files, so there is no supported workaround short of patching the package), or address the
live-binding undefined issue without disabling splitting wholesale, e.g. by forcing eager
initialization of shared chunks.
Minimal Reproduction
Any project using @angular/build:unit-test with the vitest runner and a few hundred spec files
sharing a common set of services/components. Run ng test --no-watch on 22.1.4, then on 22.1.5+,
watching the RSS of the esbuild process during "Building...".
Exception or Error
Your Environment
Angular CLI : 22.1.6
Angular : 22.1.4
Node.js : 26.5.1
Package Manager : pnpm 11.21.0
Anything else relevant?
No response
Command
test
Is this a regression?
The previous version in which this bug was not present was
No response
Description
Since
@angular/build22.1.5,ng test --no-watchno longer completes on our suite. The buildphase — before any vitest worker spawns — grows a single native
esbuildprocess past 5 GB andhard-freezes a 16 GB machine. It never reaches the test run. No application code changed; only
the builder version.
The cause is
disableCodeSplitting: true, added in 22.1.5 tosrc/builders/unit-test/runners/vitest/build-options.js, which setssplitting = falseandignoreAnnotations = trueinsrc/tools/esbuild/application-code-bundle.js.Because every spec file is its own entry point, disabling splitting makes each bundle inline its
own private copy of all shared first-party code, and
ignoreAnnotationsadditionally preventssideEffects: falsepruning. Memory scales linearly with spec count times shared-graph size.I understand this fixes a real correctness bug (shared-chunk exports read as
undefinedvia liveESM bindings). The problem is that the trade-off is unconditional, and on a suite of a few hundred
specs the memory cost is not survivable.
Measurements — peak RSS of the esbuild process on 22.1.6, same tree,
--includeused to varyonly the number of entry points:
Linear at ~10.8 MB per spec file, projecting ~4.8 GB for our full 431 specs.
Projects using barrel files (
index.tsre-exports) are hit far harder, sinceignoreAnnotationsprevents pruning the unused members a barrel drags in. Same suite and builder, before removing
barrel imports: 25 specs → 2025 MB (vs 387 MB), 50 specs → over 3089 MB (vs 723 MB). That is
~80 MB per spec, projecting ~30 GB, which is why the failure presents as an instant machine freeze
rather than a slow build.
Patching
disableCodeSplittingback tofalseon 22.1.6 runs the full suite at 772 MB peak withall 431 files / 3523 tests passing, confirming this flag is the sole cause.
Suggested resolution — either expose
disableCodeSplittingas aunit-testbuilder option soprojects can opt back into splitting (it is currently hardcoded and absent from both
schema.jsonfiles, so there is no supported workaround short of patching the package), or address the
live-binding
undefinedissue without disabling splitting wholesale, e.g. by forcing eagerinitialization of shared chunks.
Minimal Reproduction
Any project using @angular/build:unit-test with the vitest runner and a few hundred spec files
sharing a common set of services/components. Run
ng test --no-watchon 22.1.4, then on 22.1.5+,watching the RSS of the esbuild process during "Building...".
Exception or Error
Your Environment
Anything else relevant?
No response