GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(@angular/build): disable code splitting for unit test builds by jonmarozick · Pull Request #33729 · angular/angular-cli

Conversation

@jonmarozick

@jonmarozick jonmarozick commented Aug 2, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Fixes #33728

PR Checklist

Please check to confirm your PR fulfills the following requirements:

  • The commit message follows our guidelines: https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-commit-message-guidelines
  • Tests for the changes have been added (for bug fixes / features) — unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts; see "Other information" for how the fixture was validated red/green.
  • Docs have been added / updated (for bug fixes / features) — N/A: the new option is internal-only and not exposed in the builder schema.

PR Type

What kind of change does this PR introduce?

  • Bugfix

What is the current behavior?

Issue Number: #33728

Under @angular/build:unit-test with the default (jsdom) runner, an export from a module that code
splitting hoisted into a shared chunk can be observed as undefined by an importing chunk — a
component reading an exported const in a class-field initializer fails with
TypeError: Cannot read properties of undefined (reading 'map'), in a test that never touches the
runner's own API. The emitted bundles are valid ESM and the same specs pass in browser mode, so the
defect is in how the Node-side module-runner path evaluates the split output, not in the bundles.

Every spec file is its own entry point, so splitting hoists any module reached from more than one
spec into a shared chunk behind lazy __esm initializers; importing chunks then read the export as
a live ESM binding, which that path does not reliably preserve at class-field-initialization time.
The full analysis — including the exact four-condition trigger set (shared chunk + cross-chunk
field read + an async test callback + zone.js polyfills) isolated by single-variable toggles — is
in #33728, with a minimal reproduction at https://github.com/jonmarozick/ng-shared-chunk-repro
(npm install && npm test: expected 2 passed, actual 1 failed).

The failure only appears once a project has more than one spec file, because a single entry point
inlines everything and never splits — so a suite can pass for a long time and then break when a
second spec file is added. The impact can also be silent: in a larger project on the same setup,
jsdom reported 108 of 216 tests (three spec files never loaded) in half the wall-clock of browser
mode.

What is the new behavior?

esbuild code splitting is disabled for the unit-test build only, through a new internal option
(disableCodeSplitting) alongside the existing internal test-only options. Test bundles are never
downloaded by a browser, so splitting has nothing to optimize here. Three small changes:

  • application/options.ts — declare the internal option and pass it through normalizeOptions.
  • esbuild/application-code-bundle.ts — honour it in createBrowserCodeBundleOptions, following
    the existing buildOptions.splitting = false precedent used for the polyfills bundle.
  • unit-test/runners/vitest/build-options.ts — set it for the Vitest runner's build.

With the change, the minimal reproduction passes (2/2) and no shared chunks are emitted for test
builds. Application builds are untouched.

Does this PR introduce a breaking change?

  • No

Cost: shared modules are duplicated into each spec bundle. Measured on a real 105-spec-file
project via --dump-virtual-files:

output files total size chunks wall
splitting on 169 1.21 MB 57 30s
splitting off 112 6.93 MB 0 28s

Total output grows ~5.7×; test run time was unchanged. If the extra memory is a concern for very
large suites, gating the option on the runner being jsdom (rather than all Vitest builds) would
narrow it, since browser mode does not exhibit the bug — happy to adjust.

Other information

Verification. Verified by applying the equivalent change to an installed 21.2.19
@angular/build (building the CLI from source with Bazel was not available on this machine), so CI
should be treated as the authoritative run:

check before after
minimal reproduction (jsdom) 1 failed / 1 passed 2 passed
real 105-spec-file project, browser mode 1018 passed 1018 passed
ng build (application build, flag unset) ok ok, still splits

Regression test. unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts encodes the
issue's four trigger conditions: a shared const imported by two spec entries (shared chunk), a
component reading it in a class-field initializer, an async test callback in that component's
spec (no await needed), and zone.js in the polyfills (the setupApplicationTarget default).
Earlier fixture attempts passed without the fix because their test callbacks were synchronous —
zone forces async downleveling, the spec then imports the __async helper, and esbuild emits that
spec entry CommonJS-wrapped with the component module behind a lazy __esm initializer, which is
the shape in which the cross-chunk read fails.

Since Bazel wasn't runnable on my machine, the fixture's exact file contents were validated
against an installed 21.2.19 @angular/build in a fresh ng new workspace: unpatched, the
container spec fails
with the issue's TypeError (1 failed / 2 passed); with this change
applied, all pass
and no chunk-*.js is emitted. Please treat the CI run of the harness test as
authoritative. One caveat encoded as a comment in the test: the failure is sensitive to inert
content (a top-level console.log in either module defuses it), so the fixtures intentionally
mirror the reproduction byte-for-byte rather than a paraphrase.

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gemini-code-assist[bot] reacted with eyes emoji
All reactions
  • 👀 1 reaction
@angular-robot angular-robot Bot added the area: @angular/build label Aug 2, 2026
@google-cla

google-cla Bot commented Aug 2, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jonmarozick
jonmarozick force-pushed the fix/unit-test-shared-chunk-init branch from b8a3e00 to 9e99820 Compare August 2, 2026 19:19
gemini-code-assist[bot]
gemini-code-assist Bot reviewed Aug 2, 2026
View reviewed changes

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new internal option, disableCodeSplitting, to the application builder options. This option is designed to disable esbuild code splitting for browser code bundles, which is particularly useful for unit-test runners (such as Vitest) where module loading does not reliably preserve live ESM bindings across chunk boundaries. The option has been integrated into the option normalization process, the Vitest build options, and the browser code bundle creation logic. I have no feedback to provide as there are no review comments to address.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@jonmarozick
fix(@angular/build): disable code splitting for unit test builds
746b351
Every spec file is its own entry point, so esbuild code splitting hoists any module
reached from more than one spec into a chunk shared between them. A module placed in a
shared chunk is wrapped in a lazy `__esm` initializer, so its exported value is only
assigned once that initializer runs, and importing chunks read the export as a live ESM
binding.

The unit test runners load the generated output through a module runner rather than the
browser's own ESM implementation, and that does not reliably preserve those bindings. An
importing chunk can therefore observe the export as `undefined`. A component whose class
field initializer reads a `const` exported from a module that was hoisted into a shared
chunk fails with a `TypeError`, while the same value read later, or read from within the
shared chunk itself, is correct. It only appears once a project has more than one spec
file, because a single entry point inlines everything and never splits.

Test bundles are never downloaded by a browser, so splitting has nothing to optimize
here. This disables it for the unit test build only, via an internal option, leaving
application builds unaffected.

The regression test mirrors the reproduction's exact shape: a shared const read during
class-field initialization from a spec with an async test callback, under zone.js
polyfills. That combination is load-bearing: zone.js downlevels async, the spec then
imports the `__async` helper, and esbuild emits the spec entry CommonJS-wrapped with the
component module behind a lazy `__esm` initializer. The fixture file set was verified to
fail against an unpatched 21.2.19 build and pass with this change applied.
@jonmarozick
jonmarozick force-pushed the fix/unit-test-shared-chunk-init branch from 9e99820 to 746b351 Compare August 2, 2026 19:33
@alan-agius4
alan-agius4 requested a review from clydin August 6, 2026 09:27
@alan-agius4 alan-agius4 added action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release labels Aug 6, 2026
clydin
clydin approved these changes Aug 13, 2026
View reviewed changes

@clydin clydin left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution.
This appears to be the cleanest approach to ensure resilience with Vitest's synthetic module runner.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@clydin clydin added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Aug 13, 2026
@dgp1130
dgp1130 merged commit 0ffe2d2 into angular:main Aug 13, 2026
42 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@dgp1130

dgp1130 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

This PR was merged into the repository. The changes were merged into the following branches:

  • main: 0ffe2d2
  • 22.1.x: b467999
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Aug 13, 2026
unit-test (vitest): isolate:false default silently breaks cross-file vi.mock() of shared modules on low-CPU workers #33570
Closed
Unit test fails with zone application and vitest #32555
Closed
"No test suite found in file" error when running Vitest #32252
Closed
@jonmarozick
jonmarozick deleted the fix/unit-test-shared-chunk-init branch August 17, 2026 20:49
@PatVandyke PatVandyke mentioned this pull request Aug 24, 2026
unit-test builder: "sideEffects": false elides statically-referenced barrel modules, component reads as undefined in TestBed #33910
Closed
@fabiofigrod fabiofigrod mentioned this pull request Oct 3, 2026
karma builder: component imported through a barrel is undefined in TestBed when another spec has an async test (cross-chunk lazy init, karma counterpart of #33728) #34250
Open
@angular-automatic-lock-bot

angular-automatic-lock-bot Bot commented Oct 19, 2026

Copy link
Copy Markdown

This pull request has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@angular-automatic-lock-bot angular-automatic-lock-bot Bot locked and limited conversation to collaborators Oct 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@clydin clydin clydin approved these changes
+1 more reviewer
@gemini-code-assist gemini-code-assist[bot] gemini-code-assist[bot] left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

action: merge The PR is ready for merge by the caretaker area: @angular/build target: patch This PR is targeted for the next patch release

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

@angular/build:unit-test: with the default jsdom runner and zone.js, an export from a shared chunk is undefined when read during class-field initialisation from a spec with an async test

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@jonmarozick @dgp1130 @clydin @alan-agius4
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.