Skip to content

fix(angular): only add @oxc-project/runtime on the vitest-analog path - #35734

Merged
FrozenPandaz merged 1 commit into
masterfrom
angular-oxc-runtime-dep
May 22, 2026
Merged

fix(angular): only add @oxc-project/runtime on the vitest-analog path#35734
FrozenPandaz merged 1 commit into
masterfrom
angular-oxc-runtime-dep

Conversation

@polygraph-snapshot-app

@polygraph-snapshot-app polygraph-snapshot-app Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Current Behavior

The Angular vitest generators added @oxc-project/runtime to the user's devDependencies on both the vitest-angular and vitest-analog paths, with a comment claiming @angular/build's rolldown usage emits external @oxc-project/runtime/helpers/* imports. That claim doesn't match @angular/build's source: its vitest builder sets optimizeDeps.noDiscovery: true plus an in-memory test provider, so no rolldown pre-bundling against @angular/* runs on that path.

Separately, addVitestAngular/addVitestAnalog silently dropped the GeneratorCallbacks returned by addDependenciesToPackageJson and @nx/vitest's configurationGenerator. Install still ran in practice because the parent application/library generators call installPackagesTask separately, but the wiring was incorrect and any caller that didn't double-call install would lose the post-add hooks.

Expected Behavior

  • addVitestAngular no longer adds @oxc-project/runtime (it's not needed on that path).
  • addVitestAnalog continues to add @oxc-project/runtime, with a corrected comment that accurately attributes the cause.
  • Both helpers return proper GeneratorCallbacks; the application and library generators chain them into their task lists.
  • The matching update-23-0-0 migration AI-instructions section is scoped to the vitest-analog path with the corrected explanation.

Why the dep is needed on the vitest-analog path

@nx/vitest's configurationGenerator (for uiFramework: 'angular') registers @analogjs/vite-plugin-angular's angular(). In test mode, analog additionally registers an angularVitestPlugin whose transform hook matches @angular/* fesm2022 modules containing async (plus any @angular/cdk file) and calls:

vite.transformWithOxc(code, id, { target: 'es2016',})

The downlevel is deliberate. The plugin source comments it as "downlevels any dependencies that use async/await to support zone.js testing and tests w/fakeAsync" — Zone.js relies on monkey-patching promise scheduling for fakeAsync and friends, which it cannot do against native async/await, so the plugin lowers them to a form Zone.js can intercept.

With target: 'es2016', oxc emits the helpers as external @oxc-project/runtime/helpers/* imports (oxc's default HelperMode = 'Runtime'). Nothing in the upstream chain (@analogjs/vite-plugin-angular, @angular/core, vite, rolldown) declares @oxc-project/runtime in a way that's resolvable from the consumer's workspace, so vite:import-analysis fails to resolve those imports unless the dep is added explicitly. This behavior is unchanged through analog 3.0.0-alpha.54 (latest at time of writing).

Why the dep is NOT needed on the @angular/build path

  • @angular/build:unit-test (and @nx/angular:unit-test for libraries) bypasses analog entirely.
  • It sets optimizeDeps.noDiscovery: true and uses an in-memory test provider, so no rolldown pre-bundling runs against @angular/*.
  • No angularVitestPlugin is loaded → no target: 'es2016' downlevel → no @oxc-project/runtime/helpers/* imports emitted.

Implementation Details

  • addVitestAngular/addVitestAnalog return Promise<GeneratorCallback>; the application and library generators chain them through runTasksInSerial(...) so the install-packages and configuration callbacks actually run through the generator pipeline.
  • @oxc-project/runtime is added only by addVitestAnalog, with the comment now describing the actual mechanism (analog's angularVitestPlugin + transformWithOxc({ target: 'es2016' }) for Zone.js compatibility).
  • update-23-0-0/ai-instructions-for-vite-8.md section 3 rewritten with the corrected mechanism, scoped to the vitest-analog path. Detection: rg '"@nx/vitest:test"' --type json + rg '@analogjs/vite-plugin-angular' --type ts --type js.
  • e2e: new case in projects-build-and-test.test.ts opts into vitest-angular explicitly (app w/ --bundler=esbuild, lib w/ --buildable) and runs nx test against both. The existing test exercises vitest-analog implicitly through app1 (webpack) → setGeneratorDefaults writes unitTestRunner: vitest-analog to nx.json, locking subsequent generations to that runner regardless of per-project defaults.

Verification

  • Reproduced the failure in an Nx e2e-generated workspace: with @oxc-project/runtime absent, nx run <lib>:test fails at vite:import-analysis trying to resolve @oxc-project/runtime/helpers/defineProperty from @angular/core/fesm2022/testing.mjs. The on-disk testing.mjs does not contain those imports — they are injected in-memory by analog's angularVitestPlugin.transform. Installing the dep makes the test pass.
  • Confirmed the mechanism against analog plugin source in versions 2.1.3, 2.5.1, and 3.0.0-alpha.54 — the target: 'es2016' downlevel is unchanged.
  • The new e2e covers the inverse: vitest-angular runs nx test successfully without relying on @oxc-project/runtime for the path.

High confidence in the root cause and the path-scoped fix.

@netlify

netlify Bot commented May 19, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 7b586b1
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a0ed79db9869f00087e2b4a
😎 Deploy Preview https://deploy-preview-35734--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 19, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 7b586b1
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a0ed79db9ca010008800136
😎 Deploy Preview https://deploy-preview-35734--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@leosvelperez leosvelperez self-assigned this May 19, 2026
@nx-cloud

nx-cloud Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 7b586b1

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 14m 7s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 9s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 19s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-21 11:22:52 UTC

nx-cloud[bot]

This comment was marked as outdated.

@leosvelperez
leosvelperez force-pushed the angular-oxc-runtime-dep branch from a4fa4eb to fa11974 Compare May 20, 2026 06:36
nx-cloud[bot]

This comment was marked as outdated.

@leosvelperez
leosvelperez marked this pull request as ready for review May 20, 2026 08:25
@leosvelperez
leosvelperez requested a review from a team as a code owner May 20, 2026 08:25
@leosvelperez
leosvelperez requested a review from MaxKless May 20, 2026 08:25
@leosvelperez
leosvelperez force-pushed the angular-oxc-runtime-dep branch from 2f9accc to 762ff7f Compare May 21, 2026 09:57
`@oxc-project/runtime` is required when the vitest pipeline pre-bundles
`@angular/core` through rolldown's oxc transformer (which lowers ES2022+
syntax in `@angular/core/fesm2022/*` and emits external
`@oxc-project/runtime/helpers/*` imports). That pipeline is engaged by
`@analogjs/vite-plugin-angular`, which is what the vitest-analog setup
(`@nx/vitest:test` executor) wires in.

The vitest-angular path (`@angular/build:unit-test` /
`@nx/angular:unit-test`) sets `optimizeDeps.noDiscovery: true` and uses
an in-memory test provider, so the rolldown pre-bundle that triggers
the rewrite is never engaged there; the dep is unnecessary on that
path.

- Drop the dep from `addVitestAngular`; keep it on `addVitestAnalog`
  with a corrected rationale comment.
- Make `addVitestAngular`/`addVitestAnalog` return `GeneratorCallback`s
  (previously the install callback from `addDependenciesToPackageJson`
  and the one returned by `@nx/vitest`'s configurationGenerator were
  silently discarded). Callers chain them into their task lists.
- Scope the matching vite-7-to-8 migration AI instructions to the
  vitest-analog path with the corrected explanation.
@leosvelperez
leosvelperez force-pushed the angular-oxc-runtime-dep branch from 762ff7f to 7b586b1 Compare May 21, 2026 09:59

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud has identified a possible root cause for your failed CI:

We identified all 6 failing tasks as environment-state failures unrelated to this PR's changes. The majority fail because pnpm's supply-chain security policy is blocking nx@23.0.0 build scripts ([ERR_PNPM_IGNORED_BUILDS]) during workspace creation in CI, while the remaining task fails due to a Cypress binary installation error. No fixes are required in this PR; the CI environment needs to be remediated.

No code changes were suggested for this issue.

Trigger a rerun:

Rerun CI

Nx Cloud View detailed reasoning on Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

@FrozenPandaz
FrozenPandaz merged commit 5264189 into master May 22, 2026
27 of 28 checks passed
@FrozenPandaz
FrozenPandaz deleted the angular-oxc-runtime-dep branch May 22, 2026 02:23
FrozenPandaz pushed a commit that referenced this pull request May 22, 2026
…#35734)

The Angular vitest generators added `@oxc-project/runtime` to the user's
`devDependencies` on **both** the vitest-angular and vitest-analog
paths, with a comment claiming `@angular/build`'s rolldown usage emits
external `@oxc-project/runtime/helpers/*` imports. That claim doesn't
match `@angular/build`'s source: its vitest builder sets
`optimizeDeps.noDiscovery: true` plus an in-memory test provider, so no
rolldown pre-bundling against `@angular/*` runs on that path.

Separately, `addVitestAngular`/`addVitestAnalog` silently dropped the
`GeneratorCallback`s returned by `addDependenciesToPackageJson` and
`@nx/vitest`'s `configurationGenerator`. Install still ran in practice
because the parent application/library generators call
`installPackagesTask` separately, but the wiring was incorrect and any
caller that didn't double-call install would lose the post-add hooks.

- `addVitestAngular` no longer adds `@oxc-project/runtime` (it's not
needed on that path).
- `addVitestAnalog` continues to add `@oxc-project/runtime`, with a
corrected comment that accurately attributes the cause.
- Both helpers return proper `GeneratorCallback`s; the application and
library generators chain them into their task lists.
- The matching `update-23-0-0` migration AI-instructions section is
scoped to the vitest-analog path with the corrected explanation.

`@nx/vitest`'s `configurationGenerator` (for `uiFramework: 'angular'`)
registers `@analogjs/vite-plugin-angular`'s `angular()`. In test mode,
analog additionally registers an `angularVitestPlugin` whose `transform`
hook matches `@angular/*` `fesm2022` modules containing `async ` (plus
any `@angular/cdk` file) and calls:

```ts
vite.transformWithOxc(code, id, { target: 'es2016', … })
```

The downlevel is deliberate. The plugin source comments it as
*"downlevels any dependencies that use async/await to support zone.js
testing and tests w/fakeAsync"* — Zone.js relies on monkey-patching
promise scheduling for `fakeAsync` and friends, which it cannot do
against native `async`/`await`, so the plugin lowers them to a form
Zone.js can intercept.

With `target: 'es2016'`, oxc emits the helpers as external
`@oxc-project/runtime/helpers/*` imports (oxc's default `HelperMode =
'Runtime'`). Nothing in the upstream chain
(`@analogjs/vite-plugin-angular`, `@angular/core`, `vite`, `rolldown`)
declares `@oxc-project/runtime` in a way that's resolvable from the
consumer's workspace, so `vite:import-analysis` fails to resolve those
imports unless the dep is added explicitly. This behavior is unchanged
through analog `3.0.0-alpha.54` (latest at time of writing).

- `@angular/build:unit-test` (and `@nx/angular:unit-test` for libraries)
bypasses analog entirely.
- It sets `optimizeDeps.noDiscovery: true` and uses an in-memory test
provider, so no rolldown pre-bundling runs against `@angular/*`.
- No `angularVitestPlugin` is loaded → no `target: 'es2016'` downlevel →
no `@oxc-project/runtime/helpers/*` imports emitted.

- `addVitestAngular`/`addVitestAnalog` return
`Promise<GeneratorCallback>`; the application and library generators
chain them through `runTasksInSerial(...)` so the install-packages and
configuration callbacks actually run through the generator pipeline.
- `@oxc-project/runtime` is added only by `addVitestAnalog`, with the
comment now describing the actual mechanism (analog's
`angularVitestPlugin` + `transformWithOxc({ target: 'es2016' })` for
Zone.js compatibility).
- `update-23-0-0/ai-instructions-for-vite-8.md` section 3 rewritten with
the corrected mechanism, scoped to the vitest-analog path. Detection:
`rg '"@nx/vitest:test"' --type json` + `rg
'@analogjs/vite-plugin-angular' --type ts --type js`.
- e2e: new case in `projects-build-and-test.test.ts` opts into
vitest-angular explicitly (app w/ `--bundler=esbuild`, lib w/
`--buildable`) and runs `nx test` against both. The existing test
exercises vitest-analog implicitly through `app1` (webpack) →
`setGeneratorDefaults` writes `unitTestRunner: vitest-analog` to
`nx.json`, locking subsequent generations to that runner regardless of
per-project defaults.

- Reproduced the failure in an Nx e2e-generated workspace: with
`@oxc-project/runtime` absent, `nx run <lib>:test` fails at
`vite:import-analysis` trying to resolve
`@oxc-project/runtime/helpers/defineProperty` from
`@angular/core/fesm2022/testing.mjs`. The on-disk `testing.mjs` does
**not** contain those imports — they are injected in-memory by analog's
`angularVitestPlugin.transform`. Installing the dep makes the test pass.
- Confirmed the mechanism against analog plugin source in versions
`2.1.3`, `2.5.1`, and `3.0.0-alpha.54` — the `target: 'es2016'`
downlevel is unchanged.
- The new e2e covers the inverse: vitest-angular runs `nx test`
successfully without relying on `@oxc-project/runtime` for the path.

High confidence in the root cause and the path-scoped fix.

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
(cherry picked from commit 5264189)
vrxj81 pushed a commit to vrxj81/nx that referenced this pull request Jun 7, 2026
…nrwl#35734)

## Current Behavior

The Angular vitest generators added `@oxc-project/runtime` to the user's
`devDependencies` on **both** the vitest-angular and vitest-analog
paths, with a comment claiming `@angular/build`'s rolldown usage emits
external `@oxc-project/runtime/helpers/*` imports. That claim doesn't
match `@angular/build`'s source: its vitest builder sets
`optimizeDeps.noDiscovery: true` plus an in-memory test provider, so no
rolldown pre-bundling against `@angular/*` runs on that path.

Separately, `addVitestAngular`/`addVitestAnalog` silently dropped the
`GeneratorCallback`s returned by `addDependenciesToPackageJson` and
`@nx/vitest`'s `configurationGenerator`. Install still ran in practice
because the parent application/library generators call
`installPackagesTask` separately, but the wiring was incorrect and any
caller that didn't double-call install would lose the post-add hooks.

## Expected Behavior

- `addVitestAngular` no longer adds `@oxc-project/runtime` (it's not
needed on that path).
- `addVitestAnalog` continues to add `@oxc-project/runtime`, with a
corrected comment that accurately attributes the cause.
- Both helpers return proper `GeneratorCallback`s; the application and
library generators chain them into their task lists.
- The matching `update-23-0-0` migration AI-instructions section is
scoped to the vitest-analog path with the corrected explanation.

## Why the dep is needed on the vitest-analog path

`@nx/vitest`'s `configurationGenerator` (for `uiFramework: 'angular'`)
registers `@analogjs/vite-plugin-angular`'s `angular()`. In test mode,
analog additionally registers an `angularVitestPlugin` whose `transform`
hook matches `@angular/*` `fesm2022` modules containing `async ` (plus
any `@angular/cdk` file) and calls:

```ts
vite.transformWithOxc(code, id, { target: 'es2016', … })
```

The downlevel is deliberate. The plugin source comments it as
*"downlevels any dependencies that use async/await to support zone.js
testing and tests w/fakeAsync"* — Zone.js relies on monkey-patching
promise scheduling for `fakeAsync` and friends, which it cannot do
against native `async`/`await`, so the plugin lowers them to a form
Zone.js can intercept.

With `target: 'es2016'`, oxc emits the helpers as external
`@oxc-project/runtime/helpers/*` imports (oxc's default `HelperMode =
'Runtime'`). Nothing in the upstream chain
(`@analogjs/vite-plugin-angular`, `@angular/core`, `vite`, `rolldown`)
declares `@oxc-project/runtime` in a way that's resolvable from the
consumer's workspace, so `vite:import-analysis` fails to resolve those
imports unless the dep is added explicitly. This behavior is unchanged
through analog `3.0.0-alpha.54` (latest at time of writing).

## Why the dep is NOT needed on the @angular/build path

- `@angular/build:unit-test` (and `@nx/angular:unit-test` for libraries)
bypasses analog entirely.
- It sets `optimizeDeps.noDiscovery: true` and uses an in-memory test
provider, so no rolldown pre-bundling runs against `@angular/*`.
- No `angularVitestPlugin` is loaded → no `target: 'es2016'` downlevel →
no `@oxc-project/runtime/helpers/*` imports emitted.

## Implementation Details

- `addVitestAngular`/`addVitestAnalog` return
`Promise<GeneratorCallback>`; the application and library generators
chain them through `runTasksInSerial(...)` so the install-packages and
configuration callbacks actually run through the generator pipeline.
- `@oxc-project/runtime` is added only by `addVitestAnalog`, with the
comment now describing the actual mechanism (analog's
`angularVitestPlugin` + `transformWithOxc({ target: 'es2016' })` for
Zone.js compatibility).
- `update-23-0-0/ai-instructions-for-vite-8.md` section 3 rewritten with
the corrected mechanism, scoped to the vitest-analog path. Detection:
`rg '"@nx/vitest:test"' --type json` + `rg
'@analogjs/vite-plugin-angular' --type ts --type js`.
- e2e: new case in `projects-build-and-test.test.ts` opts into
vitest-angular explicitly (app w/ `--bundler=esbuild`, lib w/
`--buildable`) and runs `nx test` against both. The existing test
exercises vitest-analog implicitly through `app1` (webpack) →
`setGeneratorDefaults` writes `unitTestRunner: vitest-analog` to
`nx.json`, locking subsequent generations to that runner regardless of
per-project defaults.

## Verification

- Reproduced the failure in an Nx e2e-generated workspace: with
`@oxc-project/runtime` absent, `nx run <lib>:test` fails at
`vite:import-analysis` trying to resolve
`@oxc-project/runtime/helpers/defineProperty` from
`@angular/core/fesm2022/testing.mjs`. The on-disk `testing.mjs` does
**not** contain those imports — they are injected in-memory by analog's
`angularVitestPlugin.transform`. Installing the dep makes the test pass.
- Confirmed the mechanism against analog plugin source in versions
`2.1.3`, `2.5.1`, and `3.0.0-alpha.54` — the `target: 'es2016'`
downlevel is unchanged.
- The new e2e covers the inverse: vitest-angular runs `nx test`
successfully without relying on `@oxc-project/runtime` for the path.

High confidence in the root cause and the path-scoped fix.

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants