fix(angular): only add @oxc-project/runtime on the vitest-analog path - #35734
Conversation
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
View your CI Pipeline Execution ↗ for commit 7b586b1
☁️ Nx Cloud last updated this comment at |
a4fa4eb to
fa11974
Compare
2f9accc to
762ff7f
Compare
`@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.
762ff7f to
7b586b1
Compare
There was a problem hiding this comment.
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:
🎓 Learn more about Self-Healing CI on nx.dev
…#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)
…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>
Current Behavior
The Angular vitest generators added
@oxc-project/runtimeto the user'sdevDependencieson 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 setsoptimizeDeps.noDiscovery: trueplus an in-memory test provider, so no rolldown pre-bundling against@angular/*runs on that path.Separately,
addVitestAngular/addVitestAnalogsilently dropped theGeneratorCallbacks returned byaddDependenciesToPackageJsonand@nx/vitest'sconfigurationGenerator. Install still ran in practice because the parent application/library generators callinstallPackagesTaskseparately, but the wiring was incorrect and any caller that didn't double-call install would lose the post-add hooks.Expected Behavior
addVitestAngularno longer adds@oxc-project/runtime(it's not needed on that path).addVitestAnalogcontinues to add@oxc-project/runtime, with a corrected comment that accurately attributes the cause.GeneratorCallbacks; the application and library generators chain them into their task lists.update-23-0-0migration 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'sconfigurationGenerator(foruiFramework: 'angular') registers@analogjs/vite-plugin-angular'sangular(). In test mode, analog additionally registers anangularVitestPluginwhosetransformhook matches@angular/*fesm2022modules containingasync(plus any@angular/cdkfile) and calls: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
fakeAsyncand friends, which it cannot do against nativeasync/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 defaultHelperMode = 'Runtime'). Nothing in the upstream chain (@analogjs/vite-plugin-angular,@angular/core,vite,rolldown) declares@oxc-project/runtimein a way that's resolvable from the consumer's workspace, sovite:import-analysisfails to resolve those imports unless the dep is added explicitly. This behavior is unchanged through analog3.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-testfor libraries) bypasses analog entirely.optimizeDeps.noDiscovery: trueand uses an in-memory test provider, so no rolldown pre-bundling runs against@angular/*.angularVitestPluginis loaded → notarget: 'es2016'downlevel → no@oxc-project/runtime/helpers/*imports emitted.Implementation Details
addVitestAngular/addVitestAnalogreturnPromise<GeneratorCallback>; the application and library generators chain them throughrunTasksInSerial(...)so the install-packages and configuration callbacks actually run through the generator pipeline.@oxc-project/runtimeis added only byaddVitestAnalog, with the comment now describing the actual mechanism (analog'sangularVitestPlugin+transformWithOxc({ target: 'es2016' })for Zone.js compatibility).update-23-0-0/ai-instructions-for-vite-8.mdsection 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.projects-build-and-test.test.tsopts into vitest-angular explicitly (app w/--bundler=esbuild, lib w/--buildable) and runsnx testagainst both. The existing test exercises vitest-analog implicitly throughapp1(webpack) →setGeneratorDefaultswritesunitTestRunner: vitest-analogtonx.json, locking subsequent generations to that runner regardless of per-project defaults.Verification
@oxc-project/runtimeabsent,nx run <lib>:testfails atvite:import-analysistrying to resolve@oxc-project/runtime/helpers/definePropertyfrom@angular/core/fesm2022/testing.mjs. The on-disktesting.mjsdoes not contain those imports — they are injected in-memory by analog'sangularVitestPlugin.transform. Installing the dep makes the test pass.2.1.3,2.5.1, and3.0.0-alpha.54— thetarget: 'es2016'downlevel is unchanged.nx testsuccessfully without relying on@oxc-project/runtimefor the path.High confidence in the root cause and the path-scoped fix.