Repository navigation
unit-test builder (Vitest): test bundle output is not deterministic, which defeats Vitest 5 fsModuleCache #34335
Description
Command
test
Is this a regression?
- Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
No response
Description
Building the same project twice in a row with @angular/build:unit-test (Vitest runner), with no source change in between, produces different output files: the same modules end up with different chunk names, because the import statements inside some chunks and spec entry points come out in a different order.
Only the order changes, never the code. Normalising the chunk names (chunk-[A-Z0-9]{8} → chunk-X) and comparing the files, every difference is one of:
- side-effect imports reordered in a spec entry point:
-import "./chunk-X.js"; // (A) -import "./chunk-X.js"; // (B) +import "./chunk-X.js"; // (B) +import "./chunk-X.js"; // (A)
- named imports moved between two
import { … } from "./chunk-X.js"statements:+import { + SomeMapper +} from "./chunk-X.js"; ... -import { - SomeMapper -} from "./chunk-X.js";
- identifiers swapped in a re-exported list (
ModuleA, ModuleB→ModuleB, ModuleA).
Since the chunk file names are content hashes, one reordered chunk renames itself and, in cascade, every chunk or spec that imports it.
Measured on two applications of the same workspace (two consecutive ng test <app> --no-watch --dump-virtual-files):
| project | JS files in the test bundle | files renamed between two runs |
|---|---|---|
app A, JIT test build (aot: false) |
593 | 79 to 89 |
| app B, JIT test build | 89 | 10 |
app B, AOT (default build:development) |
88 | 18 |
| two small libraries | 9 and 11 | 0 |
It happens with and without --coverage, in AOT and JIT, and with NG_BUILD_MAX_WORKERS=1, so it does not look like a worker race in the Angular compilation.
Why it matters. Vitest 5 enables fsModuleCache by default and keys it on file content and id. With the builder's output changing between runs, part of the cache is invalidated on every run (about 20 % of the entries for app A), so the cache cannot be reused between CI pipelines. Disabling splitting makes the output stable (100 % cache reuse) but the option is deprecated ("No longer needed with Vitest 5") and the suite is much slower without shared chunks (app A with coverage, warm cache: ~45 s instead of ~30 s).
Expected: identical inputs give byte-identical test bundles, as they would for a reproducible build.
Minimal Reproduction
Not yet reduced to a public repository. Any workspace whose specs share enough code to produce many shared chunks should show it:
ng test <project> --no-watch --dump-virtual-files- Copy
.angular/cache/<version>/<project>/unit-test/output-filesaside. - Run step 1 again, without touching anything.
- Compare the two lists of
*.jsfile names: some chunk names differ. Diffing a renamed chunk against its counterpart only shows reordered imports.
Small projects (around ten output files) do not show it in our workspace.
Exception or Error
No response
Your Environment
Angular CLI : 22.2.2
Angular : 22.2.2
Node.js : 24.21.0
Package Manager : npm 11.19.0
Operating System : darwin arm64 (on the node:24-alpine CI image, the persisted Vitest cache also grows on every pipeline instead of being reused)
@angular/build : 22.2.2
vitest : 5.0.4
esbuild : 0.28.2
typescript : 6.0.3
Unit-test target: @angular/build:unit-test, Vitest runner, jsdom, isolate: false, v8 coverage.
Anything else relevant?
Related but distinct: #31724 (unit-test builder slow for testing and coverage). A deterministic test bundle would let fsModuleCache be persisted between CI runs, which would help there.
Activity