feat(vite-plugin-angular): add OXC engine adapter for fastCompile#2368
feat(vite-plugin-angular): add OXC engine adapter for fastCompile#2368brandonroberts wants to merge 34 commits into
Conversation
Introduce `compiler/oxc-engine.ts`, a thin adapter around
`@oxc-angular/vite/api`'s `transformAngularFile`. Resolves external
`templateUrl`/`styleUrl` resources (running Vite's `preprocessCSS` for
SCSS/Less so the Rust pipeline only sees plain CSS), packages them into
the NAPI `ResolvedResources` shape, and returns a `{ code, map,
resourceDependencies }` triple matching what `compile()` produces.
`@oxc-angular/vite` is declared as an optional peer dependency; the
adapter dynamic-imports it so users who never opt in don't have to
install the native binary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `fastCompileEngine` option to the public `angular()` plugin and
the internal `fastCompilePlugin()` factory. Default stays `'ts'`, so
existing users see no change.
When `'oxc'` is selected (and the build is AOT, full-mode), the new
dispatch in `handleFastCompileTransform` short-circuits past the TS-side
resource inlining, registry merging, `compile()` call, and TS-strip
pass — `oxcTransform` handles all of that internally and returns the
final `{ code, map }`. Engine selection is intentionally narrow: JIT
mode and `fastCompileMode: 'partial'` still flow through the TS
compiler because the OXC NAPI surface doesn't model `ɵɵngDeclare*`
emission or JIT signal-API downleveling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tput
`@oxc-angular/vite@0.0.30`'s `transformAngularFile` augments the class
with Ivy static members but leaves the original field initializers and
decorator arguments unmodified — so a signal-API field like
`label = input<string>('')` lands in the output verbatim and breaks
downstream JS parsing (`<string>` parses as `<` / `>` comparisons).
Run the same TS strip pass the in-tree TS engine uses after `compile()`
so the OXC engine's output is always valid JS regardless of the source
TS surface.
Surfaced by the parity suite in the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 fixtures covering signal APIs, control flow, host bindings, queries, NgModules, constructor DI, inheritance, standalone imports, templateUrl and styleUrl on disk, @defer, and HostBinding decorators. Each fixture is compiled through both engines; `expect.soft` lets a single fixture surface every divergence in one run so the failure list is a real punch list. A parallel `describe` block asserts the OXC output parses as JS — a regression check for the kind of residual-TS leak fixed in the preceding commit. A `KNOWN_OXC_GAPS` set is provided for marking fixtures that are expected to fail against a given OXC version; flipping a fixture out of that set is what closes the gap. `@oxc-angular/vite` is installed at the workspace root as a devDep so the suite can actually load the native binary in CI. Production users who don't opt into `fastCompileEngine: 'oxc'` still pay nothing — runtime loading is gated behind a dynamic import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ldens
Adds a second parity layer: instead of just checking the two engines
agree with each other, this asks the stricter question — does each
engine match Angular's own compliance test goldens at
`packages/compiler-cli/test/compliance/test_cases/`?
Compares against the local Angular checkout (`~/projects/angular/angular`
by default; override with `ANGULAR_SRC` env var). If the path doesn't
exist the suite skips, so this file is safe to commit. Pinned to
Angular v22.1.0-next.0 emit.
`__upstream_match__.ts` is a slim port of Angular's
`compliance/test_helpers/expect_emit.ts` — ellipsis chunks, `$X$`
identifier placeholders that capture-and-reuse across chunks, plus
`__AttributeMarker.X__`/`__SelectorFlags.X__` macro expansion.
Token-based whitespace normalization so `/* @__PURE__ */` and
`/*@__PURE__*/` are treated as equivalent.
Six fixtures empirically established:
| Fixture | TS engine | OXC engine |
|----------------------------------|-----------|------------|
| signal_inputs/input_component | ✅ match | ✅ match |
| output_function/output_in_comp | ✅ match | ✅ match |
| model_inputs/model_component | ✅ match | ✅ match |
| control_flow/basic_if | ❌ drift | ✅ match |
| control_flow/basic_for | ❌ drift | ✅ match |
| signal_queries/query_in_comp | ❌ drift | ❌ drift |
Each divergent fixture carries `expectedDivergent: { ts, oxc }` plus
a comment documenting the actual emit divergence. Flipping a bit out
of `expectedDivergent` is what closes the gap — that's the punch list.
Punch list summary:
- TS engine: signal-query flag emit missing `emitDistinctChangesOnly`
bit (emits `1`/`0`, upstream `5`/`4`). Real correctness gap.
- TS engine: template / contentQueries / viewQuery emit inline arrow
functions; upstream uses top-level named `function` declarations.
- OXC engine: chained instruction calls (`fn(...)(...)`) flattened to
separate statements; same runtime behavior, breaks the upstream
emit contract for `instructionChainAfter()`.
- OXC engine: const-table numbering (`_c<N>`) is reversed for content
vs view queries relative to upstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fastCompile constructs R3 query metadata with `emitFlags: 0`, but
Angular's R3QueryMetadata interface (compiler/src/render3/view/api.ts:
412) actually reads `emitDistinctChangesOnly: boolean`. The misnamed
field is silently dropped, so the runtime
`ɵɵ{view,content}QuerySignal(..., flag)` argument comes out as `1`/`0`
(descendants only) instead of upstream's `5`/`4` (descendants |
emitDistinctChangesOnly).
This is a real correctness gap — without
`QueryFlags.emitDistinctChangesOnly` set, the query signal emits on
every change-detection check instead of only when the result set
actually changes. Upstream ngtsc sets this unconditionally for every
signal query (compiler-cli/src/ngtsc/annotations/directive/src/
query_functions.ts:123).
Fixes:
- Signal queries (metadata.ts:604): rename `emitFlags: 0` →
`emitDistinctChangesOnly: true`.
- Decorator queries (metadata.ts:773): rename `emitFlags: 0` →
`emitDistinctChangesOnly: <parsed value, default true>`, and parse the
`emitDistinctChangesOnly` option from `@ContentChildren` / `@ViewChildren`
decorators. Default matches Angular's `emitDistinctChangesOnlyDefaultValue
= true` (compiler/src/core.ts:19).
Surfaced by the upstream-anchored parity suite via the
`signal_queries/query_in_component` Angular compliance fixture. The TS
engine output for that case now matches upstream Angular v22 emit
exactly (flag values 5, 4, 5, 5 vs. upstream 5, 4, 5, 5; previously 1,
0, 1, 1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ssion
Angular's R3 codegen constructs `o.FunctionExpr` (with a `name`
property) for emit nodes that need to remain *named* in the output —
`template`, `viewQuery`, `contentQueries`, factory bodies — so stack
traces, the runtime HMR detector, and partial-mode tooling can find
the original component by function name (compiler/src/output/output_ast.
ts:1025).
fastCompile's `visitFunctionExpr` (js-emitter.ts:355) was dropping the
name and emitting an arrow expression instead. Output:
template: (rf, ctx) => { … }
static ɵfac = /*@__PURE__*/ (__ngFactoryType__) => …
Upstream Angular v22 (and OXC engine) emit the equivalent named form:
template: function HelloComponent_Template(rf, ctx) { … }
static ɵfac = function HelloComponent_Factory(__ngFactoryType__) …
Fix: emit `function <name>(<params>) { <body> }` from `visitFunctionExpr`,
falling back to `function (<params>) { <body> }` when the AST node has
no name. `ArrowFunctionExpr` is untouched — it still emits as an
arrow, since Angular uses *that* node when it actually wants arrow
semantics.
Surfaced by the upstream-anchored parity suite via two control-flow
fixtures (`basic_if`, `basic_for`) and the signal-queries fixture.
After this fix the TS engine matches upstream Angular v22 on the
function-form contract.
The compliance regex-based test rate ticks up from 91.9% → 92.2%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that signal-query flags and named template/factory function emit match upstream, flip the `expectedDivergent` flags so the harness reflects the new reality: - signal_queries/query_in_component: TS engine now passes upstream parity. OXC engine still diverges (chained-call flattening + reversed `_c<N>` numbering). - control_flow/basic_for: both engines now match upstream. Drop the `expectedDivergent` block. - control_flow/basic_if: TS engine still diverges (inline conditional template body — `MyApp_Conditional_2_Template` isn't hoisted to a top-level declaration yet). OXC engine matches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four emit sites were wrapping defensively in cases where upstream Angular emits cleanly: - `visitConditionalExpr`: was emitting `(cond ? a : b)` for every ternary. The parent-context wrapping in `visitBinaryOperatorExpr`'s `childNeedsParens` already covers the nesting cases that need guarding — function-call args and bare-statement contexts don't. - `visitUnaryOperatorExpr`: was emitting `-(expr)` for every unary minus, including bare literals. Now emits `-N` for numeric-literal operands; complex operands still get the defensive wrap. - `visitLiteralExpr`: was emitting `(-N)` for negative numbers. The parens are only needed when the literal is a member receiver (`-1.toString()` would otherwise mis-parse). Emit `-N` here and let `emitReceiverForMemberAccess` wrap when needed. - `visitParenthesizedExpr`: was wrapping every explicit-paren node even when the inner expression is a primary form (numeric literal, unary on numeric literal) that doesn't need them. `emitReceiverForMemberAccess` is widened to wrap *all* numeric literals as member receivers (previously only non-negative). The comment on visitLiteralExpr handing off this case to the receiver emitter is now accurate. Net effect for the upstream-parity suite: the `control_flow/basic_if` fixture's ternary in the `@if` template emits as `ɵɵconditional(ctx.value() ? 2 : -1)` rather than `ɵɵconditional((ctx.value() ? 2 : (-1)))`, matching Angular's `r3_view_compiler_control_flow/basic_if_template.js` golden. Full test suite stays green (1207 pass) and conformance pass rate is unchanged at 92.2%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`control_flow / basic_if` was the final fixture still marked
`expectedDivergent: { ts: true }`. The js-emitter paren cleanups in
the preceding commit close it — TS engine output now matches Angular's
golden `r3_view_compiler_control_flow/basic_if_template.js` exactly.
All six upstream-parity fixtures now agree with Angular v22 for the
TS engine. Only OXC retains one known divergence (chained instruction
calls flattened to separate statements in signal_queries).
Also refreshes the `emitReceiverForMemberAccess` docstring to reflect
the widened numeric-literal wrapping behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The OXC engine's HMR dispatch needs three helpers that the upstream plugin keeps private: - extractInlineTemplate(code, className) — pull a specific component's inline `template:` text - extractInlineStyles(code, className) — pull a component's inline `styles:` literal array - stripComponentMetadata(code) — empty out every @component's template/styles fields, used to cheaply detect "only template/styles changed" via byte-diff against a cached stripped snapshot `@oxc-angular/vite@0.0.30` ships them in `vite-plugin/utils/decorator- fields.ts` and `vite-plugin/index.ts` but doesn't re-export them on `./api`. Following the OXC-equivalents rule, the long-term plan is upstreaming the exports — but the HMR wiring can't wait. Inlined here near-verbatim with a TODO at the top of the file to delete it the moment the upstream exports land. The locator implementation is a character-by-character walker that honors string/template-literal boundaries, ${…} interpolations, and JS comments — regex parsing isn't safe for these field bodies because attribute selectors and template strings can contain the closing delimiter the regex would rely on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When `fastCompileEngine: 'oxc'` and `liveReload: true`, route HMR through OXC's own per-component update protocol instead of falling back to Vite's full-reload path. Components hot-swap their templates and inline styles via Angular's runtime `ɵɵreplaceMetadata` — same behavior Angular CLI's `@angular/build` provides on the official path. `oxc-engine.ts`: - Pass `hmr: true` to `transformAngularFile` when liveReload + watchMode, so OXC emits the HMR initializer that dynamically imports `@ng/component?c=<encoded id>` virtual modules. - Pass the installed `@angular/compiler` version (from utils/devkit) so the OXC NAPI's version-conditional emit lands on the right shape — including the chained-query emit gated by AngularVersion:: supports_chained_queries() in voidzero-dev/oxc-angular-compiler#323. - Surface `templateUpdates` / `styleUpdates` from the transform result so the plugin layer can drive its component → file membership cache. - Export `loadOxcHmrApi` so `oxc-hmr.ts` shares the same lazy NAPI loader; the native binary is still loaded at most once. New `oxc-hmr.ts` — controller class that owns: - Per-component state: `componentsByFile`, `pendingHmrUpdates`, `inlineTemplateCache`, `inlineStylesCache`, `componentMetadataCache`, `resourceToComponent`. - The `@ng/component?c=<id>` HTTP middleware. On request it reads the current source, extracts the component's template / styles (external templateUrl / styleUrl preferred, falling back to inline), runs OXC's `compileForHmrSync`, and serves the result. Missing slot or stale className → empty response (browser no-ops). Compile failure → empty response + `angular:invalidate` WS broadcast → full-reload fallback. - The 4-branch `handleHotUpdate` dispatch: external `.html`/`.css`/ SCSS/Less resource → dispatch all components in the owning file; component `.ts` with stripped-metadata equality → inline-only HMR; any other component `.ts` change → full reload; plain `.ts` outside node_modules → full reload; everything else → fall through to Vite. - The `angular:invalidate` WebSocket listener used by the browser-side runtime to escalate failed `ɵɵreplaceMetadata` calls to a reload. `fast-compile-plugin.ts`: - Lazily build the controller in `configResolved` when OXC + liveReload + watch are all active. - `transform` hook feeds it `recordTransform(filePath, code, templateUpdates)` after each OXC compile, plus `recordResource(dep, id)` for every templateUrl/styleUrl dependency. `pruneStaleResources` keeps the reverse map honest when a component's resource set shrinks. - `configureServer` mounts the middleware + WS listeners. - `handleHotUpdate` delegates to the controller in OXC mode (returning its 4-branch verdict); the existing TS-engine path stays untouched. - `resolveId` / `load` safety net: in SSR (Nitro module-runner) the HTTP middleware doesn't run, so `@ng/component` resolves to a virtual module that loads as `export default undefined;`. HMR is browser-only. The OXC decorator-field helpers used by `oxc-hmr.ts` live in `oxc-hmr-helpers.ts` (added in the previous commit) as a TODO-tagged inline copy until `@oxc-angular/vite/api` exports them. Full test suite stays green (1207 passing, conformance unchanged at 92.2%). End-to-end smoke against `signal_queries/query_in_component` also passes — confirms the rebuilt local NAPI + the new `angularVersion` plumbing reach the chained-emit code path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tream parity The OXC engine now emits chained query instructions and content-before-view const-table indices that match Angular v22's golden once the rebuilt local NAPI is in place and the adapter passes the installed Angular version through to transformAngularFile. Drops the oxc divergent marker on the signal_queries fixture so the assertion now expects a clean upstream match for both engines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously combining the OXC engine with JIT silently fell back to Analog's TS-side jitTransform because the JIT branch ran before the OXC dispatch and bailed early. Reordered so the OXC engine claims both AOT and JIT in full mode. Partial-library output still flows through the TS path since the OXC NAPI does not model ngDeclare emission. OXC's JIT support came together in three upstream PRs that landed since the engine was first wired here, covering signal-API lowering to synthesized propDecorators, class-metadata for TestBed, and JIT-mode Service decorator handling. The OXC adapter now surfaces a jit flag on the engine context and forwards it to transformAngularFile. The plugin collapses the two OXC dispatch blocks into a single branch above the TS-engine jit fallback so the engine routes consistently. URL handling is unchanged. OXC's JIT output emits the same external- resource URL shape Analog's fast-compile handler already understands. The existing resolveId branch splits on the semicolon, routes style-suffixed URLs to Vite's inline-CSS pipeline marked safe, and routes templates through the virtual-raw module loader. Inline template and styles stay in the decorator literal as plain strings for Angular's runtime JIT compiler. Full test suite stays green at 1207 passing. Conformance unchanged at 92.2 percent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the OXC engine path honored `inlineStylesExtension` only for external styleUrls (those flow through Vite's preprocessCSS via `ResolvedResources.styles`). Inline `styles: ['…scss…']` arrays passed through untouched, since OXC's NAPI has no slot for pre-processed inline styles and treats whatever is in the literal as plain CSS. A consumer setting `inlineStylesExtension: 'scss'` on the OXC engine got their SCSS shipped as-is into the final component definition. The browser saw unparseable CSS and dropped the rules silently. The fix runs Vite's preprocessor over every inline `styles:` literal in the source before OXC sees the file. For each `@Component(...)` decorator the rewriter locates the styles field (array or bare string), pipes each string literal through `preprocessCSS` with a synthetic path that ends in the right extension so Vite picks the matching preprocessor, and substitutes the result back as a JSON-quoted plain-CSS literal. Substitutions happen end-first so earlier offsets stay valid. Reuses the existing decorator-field locators from the temp helper module (`oxc-hmr-helpers.ts`) — they were private and HMR-only; now exported and shared with the inline-style pass. The TODO header already covers replacing this module with upstream re-exports. The `inlineStylesExtension: 'css'` path is a no-op and skips the walker entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When `fastCompileEngine: 'oxc'` is selected, mount a Vite/Rolldown
plugin that runs `linkAngularPackage` over pre-compiled Angular
libraries in node_modules. Partial-format libraries publish
`ɵɵngDeclare*` calls and rely on a build-time linker to expand them
into `ɵɵdefine*` final form; without that step the runtime falls back
to JIT linking which pulls `@angular/compiler` into the browser
bundle.
OXC ships an `angularLinkerPlugin()` factory privately in
`vite-plugin/angular-linker-plugin.ts` but doesn't re-export it from
`@oxc-angular/vite/api`. Per the OXC-equivalents memory rule, the
actual linking work runs through the public `linkAngularPackage` NAPI
export. The plugin shell — `optimizeDeps.exclude` filtering plus a
Rolldown pre-bundle `load` hook plus a Vite `transform` hook — is
ported inline as `oxc-linker-plugin.ts` with a TODO header to delete
the file once OXC exposes the plugin factory.
`fastCompileEngine: 'oxc'` is the only path that mounts the plugin —
the TS engine path keeps its existing dts-reader-based handling.
`OxcApi` validates the new `linkAngularPackage` export at NAPI load
time so a stale `@oxc-angular/vite` version surfaces a clear error
instead of silent breakage.
Regex use in the ported plugin is limited to file-path matching and an
`includes('ɵɵngDeclare')` fast-path bailout — both allowed by the
prefer-OXC-AST rule, since neither parses source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When `fastCompileEngine: 'oxc'` is selected, swap Analog's `buildOptimizerPlugin` for an OXC-backed equivalent that runs `optimizeAngularPackage` over FESM packages in production builds. The TS engine path keeps Analog's existing optimizer, which uses `@angular/build`'s `JavaScriptTransformer` for the same job. Optimizations applied (matches OXC defaults for production): elideMetadata drops `ɵsetClassMetadata` calls, wrapStaticMembers pure-IIFE-wraps static Ivy member assignments, markPure annotates top-level calls, and adjustEnums collapses TS-enum patterns into pure IIFEs. All four feed the bundler's tree-shaker, dropping bytes that wouldn't otherwise be marked side-effect-free. The plugin also sets up production build defines (ngDevMode, ngJitMode, ngI18nClosureMode, ngServerMode) on both `define` and `oxc.define`, mirroring how the TS-engine optimizer configures them. OXC ships `buildOptimizerPlugin()` privately in `vite-plugin/angular- build-optimizer-plugin.ts` but doesn't re-export it from `@oxc-angular/vite/api`. Per the OXC-equivalents memory rule, the actual optimization work runs through the public `optimizeAngularPackage` NAPI export; the plugin shell is ported inline as `oxc-optimizer-plugin.ts` with a TODO header to delete the file once OXC exposes the factory. `OxcApi` validates the new export at NAPI load time. Regex use is limited to a FESM filename filter — allowed by the prefer-OXC-AST rule, since it matches paths not source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the platform option onto `vite-plugin-angular`'s `angular()` plugin so consumers can flip between the existing in-process TS/OXC-AST compiler (`'ts'`, default) and the native Rust path via `@oxc-angular/vite` (`'oxc'`). Selecting `'oxc'` also activates OXC's HMR contract, library linker, and FESM build optimizer for that consumer; `fastCompileMode: 'partial'` keeps flowing through the TS engine since OXC's NAPI doesn't model ngDeclare emission. Field placement matches the existing `fastCompile` / `fastCompileMode` options on `Options`, with the docstring noting the experimental scope and the `@oxc-angular/vite` optional-peer requirement. `platform-plugin.ts` threads the new option through to the angular() call alongside `fastCompile` / `fastCompileMode`. The `vite:` override spread still lets vite-level config override platform-level (the analog-app and other consumers can set it under either). `deps-plugin.ts` is intentionally unchanged — its dep-optimizer decisions only key on whether fastCompile is on, not which engine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Vite
Previously `oxcTransform` threw a plain Error on the first OXC NAPI
error and silently dropped warnings (debug-only). Reasons that
mattered:
- Only the first error surfaced. Files with several template parse
failures showed one fixable error per recompile instead of the full
list.
- The structured `labels`, `helpMessage`, and `codeframe` OXC ships on
every diagnostic were collapsed to the headline message. Users saw
"Template parse error" with no location.
- Warnings vanished unless the user ran with `DEBUG=oxc-engine`.
- Plain Errors thrown from the adapter bypass Vite's `this.error()`
routing so the dev-server overlay never showed a codeframe.
The adapter now returns diagnostics on the result instead of throwing.
Each entry preserves OXC's structured shape and adds:
- `severity`: 'Error' | 'Warning' | 'Advice'
- `formatted`: headline + per-label notes + codeframe + helpMessage,
ready to hand to `this.error()` or `this.warn()`
- `offset`: byte offset of the first label, so Rollup's plugin context
can map it to a `{line, column}` overlay position
The transform handler in fast-compile-plugin.ts iterates the
diagnostics list, routing errors through `this.error()` (terminates
the transform with proper position info) and warnings through
`this.warn()` (overlay-visible). Every error in the list is reported
before the first one throws, so a multi-error save shows the full set
in one round trip.
linker + optimizer plugins replace bare `console.warn` with
`this.warn()` so failures show in the dev overlay and build log
instead of being lost to stderr. Neither escalates to `this.error()`
since the unoptimized / unlinked output is still functionally correct.
HMR middleware now checks `compileForHmrSync`'s structured errors
rather than only catching throws. Compile failures fire
`angular:invalidate` over the WebSocket, falling back to a full
reload — same path the file-read failure case already used.
Focused unit test (`oxc-diagnostics.spec.ts`) compiles a fixture with
two `*` template-prefix bindings on the same element (Angular's
template parser surfaces it as a structured error) and asserts the
result returns a non-empty diagnostics array with severity 'Error'
and the `[oxc-angular]` prefix — covers the "errors come back, not
throws" contract without pinning a specific message.
Full test suite stays green at 1211 passing (1209 baseline + 2 new).
Conformance unchanged at 92.2%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hem all
The per-diagnostic loop introduced in the previous commit only ever
surfaced the first error from a multi-error file, because Vite's
`this.error()` throws synchronously and the loop never reached the
subsequent entries. A single template parse step in OXC can return
several errors at once (e.g. two `*` template-prefix bindings on
different elements), so the user was effectively fixing one error
per save instead of seeing the full list.
Routing reshaped so warnings flush first via `this.warn()`, then all
errors are combined into a single `this.error()` call. The formatted
blob carries the header `N OXC compile errors in this file:` plus
each diagnostic's pre-rendered message + codeframe + helpMessage,
which Vite's dev-server overlay renders inline. The first error's
offset drives the overlay's `{line, column}` cursor; subsequent
errors remain visible as text within the formatted message.
Verified end-to-end on apps/analog-app by introducing two malformed
elements in the same template: the dev-server log now shows both
errors under a `2 OXC compile errors in this file:` header, formatted
through Vite's `_formatLog` with the plugin attribution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the workspace-root pnpm override that pointed
`@oxc-angular/vite` at the local
`voidzero-dev/oxc-angular-compiler` checkout. The dependency now
resolves through the npm registry to the published 0.0.30 — same
floor the package.json `peerDependencies` already declared.
`signal_queries / query_in_component` flips back to
`expectedDivergent: { oxc: true }` since 0.0.30 doesn't yet contain
the chained-query emit and content-before-view pool ordering fixes
from `voidzero-dev/oxc-angular-compiler#323` (merged upstream but not
released to npm). The TS engine stays clean. Flip the marker back
when the next OXC release lands.
Full test suite stays green at 1211 passing. Conformance unchanged at
92.2 percent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The compiler doc previously covered only the in-tree TS engine and did not mention OXC. Adds an OXC Engine section between style preprocessing and the ngtsc comparison covering the fastCompileEngine option, the coverage matrix against the published 0.0.30 (AOT, JIT, inline SCSS, HMR, linker, optimizer, structured diagnostics), open drift tracked by upstream OXC PR 323, the source files added by the adapter, and the test files that guard the contract. The intro notes the engine choice up front so readers who only want the TS engine docs know the OXC content lives in its own section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sformer for OXC engine OXC's optimizeAngularPackage no-ops @angular/core/fesm2022/core.mjs (adds 401 B of @__PURE__ annotations instead of eliding metadata and dead JIT branches), so unused public re-exports stay alive through tree-shaking and the JIT compiler runtime ships to the client. Measured on analog-app: client bundle 756 KB → 564 KB, SSR 1.7 MB → 1.4 MB, ɵsetClassMetadata leaks zeroed. Both engines now route through buildOptimizerPlugin (Angular's JavaScriptTransformer). oxc-optimizer-plugin.ts stays in tree, unmounted, until upstream closes the gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The OXC HMR controller's decorator-field helpers were a 336-line inline port of OXC's private string-walking utilities (Ctx state machine over paren/array/brace/sq/dq/tpl). The port carried a TODO to delete the moment OXC re-exports the upstream versions. Drop the upstream dependency entirely: re-implement the five public helpers on top of oxc-parser, which is already a direct dependency and the established AST entrypoint for the rest of the compiler folder. Same public API, raw-source semantics preserved (extractInlineTemplate and extractInlineStyles return source between delimiters, not decoded strings, so compileForHmrSync still sees the raw template/style text). Drops ~200 LOC, deletes the TODO, aligns with the prefer-OXC-AST rule, and removes one TODO entry from COMPILER.md's OXC engine section. Also lands a focused spec covering all five exports — the existing OXC suite only exercises locateComponentDecorators + locateStylesInArgs via oxc-inline-styles.spec.ts; the three HMR-only helpers had no direct coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONFORMANCE_ENGINE=oxc routes every conformance fixture through
@oxc-angular/vite's transformAngularFile NAPI instead of the in-tree
TS engine. Same fixtures, same expectEmit matcher, same pass-rate
threshold — engine-vs-Angular-goldens divergence shows as a delta in
the per-engine summary.
Wiring details:
- compileWithEngine() picks the engine; oxc bridges to oxcTransform
and surfaces error-severity diagnostics as throws to match the
TS engine's try/catch contract
- test bodies are async (TS engine's compile() is sync, OXC's is not)
- describe + summary include the engine in their titles so logs
self-identify
Documented OXC crash: r3_view_compiler_template SIGILLs the OXC NAPI
binary on at least one fixture (uncatchable from JS — Rust panic).
OXC_CRASHING_CATEGORIES skips the whole category under OXC for now;
follow-up will narrow to the specific offending fixture.
CI matrix gains an engine: [ts, oxc] axis on conformance.yml. OXC
rows run with continue-on-error: true so they stay advisory until
the engine ships as default. TS rows keep their required-passing
status.
Local pass rates against ~/projects/angular/angular (latest):
- TS: 92.2% (295/320 compared)
- OXC: 93.7% (282/301 compared; template category excluded)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…was the crash The r3_view_compiler_template SIGILL wasn't a fixture bug — it was a vitest pool-isolation interaction with the OXC NAPI binary. Running the spec with --no-isolate (single shared worker) clears the panic on every previously-crashing fixture; --pool=vmThreads and --pool=vmForks also work. Direct Node calls to transformAngularFile, with or without sequential accumulation, never reproduced it. Drop OXC_CRASHING_CATEGORIES, drop the skip branch, and pass --no-isolate to the vitest invocation in the CI matrix. Harmless on the TS engine — same pass rate, marginally faster. Local pass rates with the full template category included: - TS: 92.2% (295/320 compared) - OXC: 94.0% (324/345 compared) OXC now outperforms the TS engine on Angular's own goldens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the local link override and bump @oxc-angular/vite to ^0.0.31, which ships the const-enum and signal-query compatibility fixes. Flip the signal_queries/query_in_component parity fixture to oxc:false now that the OXC engine matches upstream Angular. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `fastCompileEngine: 'oxc'`, route library builds (`fastCompileMode: 'partial'`) through OXC too — it now supports `ɵɵngDeclare*` partial emission and returns per-class Ivy `.d.ts` member declarations (`dtsDeclarations`). - oxc-engine adapter: capture/forward `dtsDeclarations` (previously dropped) and thread a `compilationMode` option to the native API. - fast-compile-plugin: collect declarations during OXC partial compilation and splice them into the `.d.ts` produced by a separate declaration generator (rolldown-plugin-dts, vite-plugin-dts, tsdown, tsc). The splice runs in a dedicated `enforce: 'post'` plugin so its `generateBundle` sees the generator's emitted `.d.ts` assets (the main fast-compile plugin is `enforce: 'pre'`); fastCompilePlugin now returns a plugin pair. - dts-writer: TS injector that adds the members + the `i0` import. Base `.d.ts` must still come from a declaration generator; this only augments them with the Angular Ivy members. Verified end-to-end with a real OXC partial library build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docs only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers default compilation and the OXC-based engine, with compatibility tables and the OXC Angular compiler caveat.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis PR integrates an optional OXC-based Rust compiler ( Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This PR touches multiple package scopes: Please confirm the changes are closely related. |
✅ Deploy Preview for analog-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for analog-blog ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for analog-app ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Setting ANALOG_OXC=true turns on the OXC engine without editing config —
equivalent to { fastCompile: true, fastCompileEngine: 'oxc' }. Explicit
plugin options take precedence; the var only fills unset values.
fastCompilePlugin now returns a [main, dts] plugin pair; update the fast-compile spec to drive the main plugin's transform hook.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
packages/vite-plugin-angular/src/lib/compiler/oxc-hmr-helpers.ts (1)
78-86: 💤 Low valueRe-parsing source for each helper call.
locateStylesInArgs(line 117) re-parses the entire source viaparseProgram(code)even though the caller (rewriteInlineStylesin oxc-engine.ts) already has decorator ranges fromlocateComponentDecorators. For HMR hot paths this is fine, but if this becomes a bottleneck, consider passing the already-parsed program or decorator match.Also applies to: 113-125
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vite-plugin-angular/src/lib/compiler/oxc-hmr-helpers.ts` around lines 78 - 86, locateStylesInArgs is re-parsing the source with parseProgram(code) even though locateComponentDecorators/rewriteInlineStyles already computed decorator ranges; modify the helpers to accept and reuse the parsed Program or the decorator matches instead of calling parseProgram again: update locateStylesInArgs (and the similar helper at 113-125) to take an extra parameter (e.g., program or ComponentDecorator[] from iterateComponentDecorators) and use that to find style args, adjust callers like rewriteInlineStyles to pass the parsed program or the decorator list, and remove the redundant parseProgram(code) calls (references: locateComponentDecorators, locateStylesInArgs, parseProgram, iterateComponentDecorators, callArgsRange, rewriteInlineStyles).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/conformance.yml:
- Around line 23-25: The conformance workflow currently uses continue-on-error:
${{ matrix.engine == 'oxc' }}, which hides OXC failures; add a post-job step
that always runs (use if: always()) and when matrix.engine == 'oxc' records the
outcome (e.g., via actions/github-script or the Checks API to create a dedicated
"OXC drift" check run or a PR comment/step-summary) including the job result and
run URL so OXC drift is observable even when the job was allowed to fail; also
add a short comment in the workflow describing the graduation/removal criterion
for continue-on-error so maintainers know when to remove the opt-in flag.
- Around line 54-57: Update the conformance workflow so the vitest run command
only appends --no-isolate when matrix.engine == 'oxc' (i.e., guard the run step
or use a conditional to include the flag for the oxc matrix value), revise the
inline comment to accurately state that --no-isolate runs tests in a single
worker rather than using Vitest's worker isolation model, remove the unsupported
claim about TS being harmless, and add a link to the upstream OXC↔Vitest N-API
crash issue/PR so the workaround can be tracked and reverted later; apply these
edits around the existing run: pnpm exec vitest ... line and reference
matrix.engine in the condition.
In `@packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts`:
- Line 3: conformance.spec.ts currently eagerly imports oxcTransform (from
./oxc-engine) at module scope which triggers oxc-engine -> utils/devkit to
require Angular devkit during test file load; change to a lazy/dynamic import so
the module is loaded only when needed (e.g. inside the test, beforeAll, or
inside the specific describe/it that uses oxcTransform): replace the top-level
static import of oxcTransform with an awaited import('./oxc-engine') (or
require) at runtime, then grab the exported oxcTransform and run the existing
tests, ensuring any references to ENGINE branching (ts vs oxc) remain intact and
types are handled after the dynamic import.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-diagnostics.spec.ts`:
- Around line 1-61: Add a runtime Angular version guard to these tests so they
skip when the installed Angular is older than the supported version (e.g. <18);
locate the test file using the oxcTransform import (oxc-diagnostics.spec.ts)
and, at the top of the spec, programmatically read the installed Angular package
version (via require/import of `@angular/core/package.json` or reading
node_modules/@angular/core/package.json) and call describe.skip or early return
from the top-level describe when the semver check fails; ensure the skip only
affects the tests around oxcTransform diagnostics and leave normal behavior when
Angular version >=18.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-optimizer-plugin.ts`:
- Around line 1-121: The oxcOptimizerPlugin export is dead code: either wire it
into the optimizer selection or remove it. Fix by either (A) integrating
oxcOptimizerPlugin into the existing optimizer chooser where
buildOptimizerPlugin(...) is selected (call oxcOptimizerPlugin({...}) instead of
buildOptimizerPlugin when using the OXC path, pass the same options such as
jit/sourcemap, and ensure loadOxcHmrApi()/optimizeAngularPackage are available
at runtime), or (B) delete the oxcOptimizerPlugin export and remove any related
imports/comments so only the existing buildOptimizerPlugin remains; target the
functions oxcOptimizerPlugin and buildOptimizerPlugin (and the transform handler
that calls loadOxcHmrApi) when applying the change.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-parity.spec.ts`:
- Line 27: The test imports oxcTransform at module scope which triggers
devkit.ts evaluation errors before beforeAll can set oxcAvailable; change the
top-level "import { oxcTransform } from './oxc-engine.js'" to a dynamic import
inside the beforeAll (e.g. const { oxcTransform } = await
import('./oxc-engine.js')) and store oxcTransform in a local variable used for
the warmup call and any compileOxc uses, so that missing/devkit failures are
caught during the beforeAll check and the suite can set oxcAvailable=false and
skip gracefully.
In `@packages/vite-plugin-angular/src/lib/fast-compile-plugin.ts`:
- Around line 103-108: The collectedDtsDeclarations Map is keyed only by class
name which allows collisions across modules; change the collection and injection
logic so entries carry module/source identity (e.g. key by
`${moduleId}::${className}` or store an object {moduleId,className,decl}),
update the code that populates collectedDtsDeclarations (where
fastCompileEngine/fastCompileMode collect Ivy members) to include module/file
identity and to detect collisions and throw (fail-fast) if ambiguous, and update
generateBundle where the global set is injected into .d.ts assets to
filter/select only declarations whose moduleId matches the current asset (or to
match by original source path) so only the correct class declarations are
injected into each .d.ts. Ensure references to collectedDtsDeclarations and
generateBundle are updated consistently.
---
Nitpick comments:
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-hmr-helpers.ts`:
- Around line 78-86: locateStylesInArgs is re-parsing the source with
parseProgram(code) even though locateComponentDecorators/rewriteInlineStyles
already computed decorator ranges; modify the helpers to accept and reuse the
parsed Program or the decorator matches instead of calling parseProgram again:
update locateStylesInArgs (and the similar helper at 113-125) to take an extra
parameter (e.g., program or ComponentDecorator[] from
iterateComponentDecorators) and use that to find style args, adjust callers like
rewriteInlineStyles to pass the parsed program or the decorator list, and remove
the redundant parseProgram(code) calls (references: locateComponentDecorators,
locateStylesInArgs, parseProgram, iterateComponentDecorators, callArgsRange,
rewriteInlineStyles).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e7a0e8ff-08f8-4b23-8b3b-d7a896f43090
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamland included by none
📒 Files selected for processing (28)
.github/workflows/conformance.ymlapps/docs-app/docs/guides/angular-compilation.mdapps/docs-app/sidebars.jspackage.jsonpackages/platform/src/lib/options.tspackages/platform/src/lib/platform-plugin.tspackages/vite-plugin-angular/package.jsonpackages/vite-plugin-angular/src/lib/angular-vite-plugin.tspackages/vite-plugin-angular/src/lib/compiler/COMPILER.mdpackages/vite-plugin-angular/src/lib/compiler/__upstream_match__.tspackages/vite-plugin-angular/src/lib/compiler/component.spec.tspackages/vite-plugin-angular/src/lib/compiler/conformance.spec.tspackages/vite-plugin-angular/src/lib/compiler/dts-writer.spec.tspackages/vite-plugin-angular/src/lib/compiler/dts-writer.tspackages/vite-plugin-angular/src/lib/compiler/js-emitter.tspackages/vite-plugin-angular/src/lib/compiler/metadata-emit.spec.tspackages/vite-plugin-angular/src/lib/compiler/metadata.tspackages/vite-plugin-angular/src/lib/compiler/oxc-diagnostics.spec.tspackages/vite-plugin-angular/src/lib/compiler/oxc-engine.tspackages/vite-plugin-angular/src/lib/compiler/oxc-hmr-helpers.spec.tspackages/vite-plugin-angular/src/lib/compiler/oxc-hmr-helpers.tspackages/vite-plugin-angular/src/lib/compiler/oxc-hmr.tspackages/vite-plugin-angular/src/lib/compiler/oxc-inline-styles.spec.tspackages/vite-plugin-angular/src/lib/compiler/oxc-linker-plugin.tspackages/vite-plugin-angular/src/lib/compiler/oxc-optimizer-plugin.tspackages/vite-plugin-angular/src/lib/compiler/oxc-parity.spec.tspackages/vite-plugin-angular/src/lib/compiler/oxc-upstream-parity.spec.tspackages/vite-plugin-angular/src/lib/fast-compile-plugin.ts
| # OXC engine rows are advisory while it's an opt-in compiler — drift | ||
| # signal without blocking PR merges. TS engine rows stay required. | ||
| continue-on-error: ${{ matrix.engine == 'oxc' }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify OXC conformance failure visibility
# Check if branch protection or status checks reference conformance jobs
gh api repos/analogjs/analog/branches/beta/protection 2>/dev/null | \
jq -r '.required_status_checks.contexts[]? // empty' | \
grep -i conformance || echo "No required status checks found for conformance"
# Search recent workflow runs for OXC failures
echo "=== Recent conformance runs with OXC failures ==="
gh run list --workflow=conformance.yml --limit 20 --json conclusion,displayTitle,url | \
jq -r '.[] | select(.conclusion == "success") | "\(.displayTitle) → \(.url)"' | \
grep -i 'oxc' || echo "No recent OXC runs found"Repository: analogjs/analog
Length of output: 541
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Workflow excerpt around continue-on-error / matrix ==="
sed -n '1,140p' .github/workflows/conformance.yml | cat -n
echo
echo "=== Search for continue-on-error + engine condition ==="
rg -n "continue-on-error|matrix\.engine|CONFORMANCE_ENGINE|--no-isolate" .github/workflows/conformance.yml
echo
echo "=== Find recent conformance workflow runs and filter by OXC in title/displayTitle ==="
gh run list --workflow=conformance.yml --limit 30 --json databaseId,conclusion,displayTitle,url | \
jq -r '.[] | "\(.databaseId)\t\(.conclusion)\t\(.displayTitle)"' | \
rg -i "oxc|ox c" || echo "No OXC-titled runs found in last 30."
echo
echo "=== Check run details for recent runs that mention OXC, for job-level conclusions ==="
# For each run whose displayTitle mentions OXC, query its jobs and show any job conclusions != success.
for id in $(gh run list --workflow=conformance.yml --limit 30 --json databaseId,displayTitle | \
jq -r '.[] | select((.displayTitle|ascii_downcase)|test("oxc")) | .databaseId'); do
echo "--- run id: $id ---"
gh api repos/analogjs/analog/actions/runs/$id/jobs --paginate --jq '.jobs[] | "\(.name)\t\(.conclusion)"' | \
rg -v "\tsuccess$" || echo "All jobs concluded success for this run."
doneRepository: analogjs/analog
Length of output: 3613
Make OXC conformance drift observable despite continue-on-error
.github/workflows/conformance.ymlsetscontinue-on-error: ${{ matrix.engine == 'oxc' }}, so OXC failures won’t block PR merges; current branch protection onbetadoesn’t require any “conformance” status checks, so there’s no enforced surface for OXC regressions.- No recent OXC-titled runs showed job failures, but this still leaves an “invisible unless manually watched” monitoring gap.
Recommendation: add an explicit OXC drift signal (e.g., dedicated check run/PR comment/step-summary that records the OXC matrix leg status even when continue-on-error is set), plus a clear graduation/removal criterion for continue-on-error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/conformance.yml around lines 23 - 25, The conformance
workflow currently uses continue-on-error: ${{ matrix.engine == 'oxc' }}, which
hides OXC failures; add a post-job step that always runs (use if: always()) and
when matrix.engine == 'oxc' records the outcome (e.g., via actions/github-script
or the Checks API to create a dedicated "OXC drift" check run or a PR
comment/step-summary) including the job result and run URL so OXC drift is
observable even when the job was allowed to fail; also add a short comment in
the workflow describing the graduation/removal criterion for continue-on-error
so maintainers know when to remove the opt-in flag.
| # OXC's NAPI binary crashes vitest's default isolated forks pool. | ||
| # `--no-isolate` runs all spec files in a single shared worker, | ||
| # which the binary survives. Identical pass rate, harmless on TS. | ||
| run: pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts --no-isolate |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for upstream NAPI crash tracking and test conditional isolation
# Search for related issues in `@oxc-angular/vite` or vitest repos
echo "=== Searching for NAPI + Vitest isolation issues ==="
gh search issues --repo oxc-project/oxc-angular "vitest isolate NAPI crash" --limit 5 || \
echo "No matches in oxc-angular (may need manual search)"
# Verify that TS engine can run with isolation enabled
echo ""
echo "=== Test TS engine with isolation ==="
echo "Recommended: Run locally to confirm TS engine works with --pool=forks (default):"
echo " CONFORMANCE_ENGINE=ts pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts"
echo "If the above passes, update the workflow to conditionally apply --no-isolate only when matrix.engine == 'oxc'."Repository: analogjs/analog
Length of output: 760
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/conformance.yml"
echo "=== Workflow file excerpt: $FILE (lines 1-120) ==="
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,140p'
else
echo "File not found: $FILE" >&2
exit 1
fi
echo ""
echo "=== Search for vitest flags in workflow ==="
rg -n "vitest .*run|--no-isolate|--isolate|pool=|forks|isolat" "$FILE" || true
echo ""
echo "=== Search repository for same --no-isolate usage ==="
rg -n "--no-isolate" .github/workflows || trueRepository: analogjs/analog
Length of output: 199
🌐 Web query:
OXC NAPI binary crashes vitest isolated forks pool --no-isolate issue
💡 Result:
Crashes involving OXC NAPI binaries in Vitest are typically caused by incompatibilities between native Node-API modules and Vitest's default execution environments [1][2]. Key insights regarding these crashes include: 1. Pool Selection: Vitest previously defaulted to worker threads (pool: 'threads'), which are often incompatible with native modules that use thread-unsafe C++ code or specific NAPI patterns [1][2]. While Vitest now defaults to pool: 'forks' (which uses child_process to provide better isolation and compatibility for native modules), specific configurations or older versions may still experience issues [1][2][3]. 2. Isolation (--no-isolate): The --no-isolate flag (or isolate: false in configuration) disables Vitest's module isolation [4]. While this can sometimes be used as a debugging technique to bypass state-related or environment-loading issues, it is generally discouraged for standard testing because it allows tests to share global state, potentially leading to unpredictable failures [5]. If you are encountering crashes, it is usually recommended to ensure you are using pool: 'forks' rather than disabling isolation [1][2]. 3. Node.js Cleanup Race Conditions: Some NAPI-related crashes in projects like OXC have been attributed to race conditions during V8/Node.js shutdown [6]. For example, a known issue with ThreadsafeFunction cleanup in Node.js versions prior to 25.4.0 can cause crashes when running tests on large codebases [6]. A documented workaround involves adding a slight delay (e.g., 50ms) before process.exit() to allow pending NAPI operations to finish [6]. 4. Troubleshooting Steps: - Ensure you are using pool: 'forks' in your Vitest configuration [1][2]. - Verify your Node.js version; if it is below 25.4.0, you may be hitting the documented ThreadsafeFunction cleanup race condition [6]. - Avoid --no-isolate if possible, as it can hide issues by permitting cross-test side effects [5]. - If crashes persist, check for platform-specific constraints, as native modules can behave differently on Windows, macOS, and Linux [7][5].
Citations:
- 1: fix!: change default
poolto'forks'vitest-dev/vitest#5047 - 2: vitest will crash when using ffi-napi vitest-dev/vitest#2091
- 3: FATAL ERROR: v8::FromJust Maybe value is Nothing. vitest-dev/vitest#2175
- 4: vitest-dev/vitest@a60a140e
- 5:
ERR_DLOPEN_FAILEDrerunning tests vitest-dev/vitest#1639 - 6: fix(oxfmt): workaround Node.js ThreadsafeFunction cleanup race condition oxc-project/oxc#18980
- 7: feat(ci): run napi tests on windows oxc-project/oxc#14383
🌐 Web query:
site:github.com oxc vitest isolate napi crash
💡 Result:
Crashes involving Node-API (NAPI) bindings within Vitest, particularly when isolation is enabled, are often caused by the fundamental architectural differences between how native addons handle resources and how Vitest manages its test environments [1][2][3]. Common causes and solutions for these crashes include: Native Incompatibility with Worker Threads: Vitest uses worker threads by default for test execution. Many native Node.js addons (like ffi-napi or certain WebGPU implementations) are not designed to run within worker threads and may crash or fail when initialized in that context [2][3]. If your addon requires a main-thread context or is sensitive to the worker thread environment, you can try disabling isolation by running Vitest with --pool=forks or --no-threads, which uses separate child processes instead of worker threads [1][2]. HandleScope and Async Contexts: Native addons sometimes fail to properly establish a HandleScope when performing asynchronous operations or inside callbacks triggered by the Node.js event loop [3]. If a crash occurs with an error like Cannot create a handle without a HandleScope, the addon code likely needs to be updated to explicitly open a HandleScope before interacting with the NAPI environment during async task completion [3]. Node.js Version-Specific Race Conditions: Some crashes, specifically those involving ThreadsafeFunction cleanup during V8 shutdown, have been observed in older versions of Node.js. For example, the Oxc project identified a race condition in Node.js versions prior to 25.4.0 that caused crashes on large codebases [4]. A common workaround in such cases is to introduce a small delay (e.g., using setTimeout) before process exit to allow pending NAPI operations to finish [4]. Environment and Template Issues: If you are using napi-rs and encountering segmentation faults, ensure your project is up-to-date with the latest napi-rs package templates [5]. Outdated configurations or dependencies in your package setup can occasionally lead to crashes that are resolved by syncing with modern project infrastructure [5]. If you are encountering a crash, first attempt to use --pool=forks to rule out worker-thread-specific incompatibilities [1][2]. If the issue persists, examine the stack trace for NAPI-related errors and consider whether the native code is correctly managing HandleScopes in asynchronous contexts [3].
Citations:
- 1: Crashes with libc++abi: terminating with uncaught exception of type Napi::Error vitest-dev/vitest#2972
- 2: vitest will crash when using ffi-napi vitest-dev/vitest#2091
- 3: Fatal crash: "Cannot create a handle without a HandleScope" when WebGPU is used from Vitest worker threads dawn-gpu/node-webgpu#13
- 4: fix(oxfmt): workaround Node.js ThreadsafeFunction cleanup race condition oxc-project/oxc#18980
- 5: Getting
Segmentation fault (core dumped)on a fresh project napi-rs/napi-rs#2450
🌐 Web query:
"no-isolate" oxc vitest
💡 Result:
In Vitest, the --no-isolate flag is a configuration option used to improve test performance by disabling the isolation of test files [1][2]. By default (true), Vitest runs every test file in an isolated environment (such as a separate Worker or forked child process) to prevent side effects and state leakage between tests [1][3]. When --no-isolate is enabled (set to false), Vitest runs tests without this per-file isolation [1][2]. This can significantly reduce test execution time for projects that are well-behaved and do not rely on side effects or require state cleanup [1][2]. However, this mode is not compatible with the vmThreads pool [1][2]. Users can also configure isolation per project or per pool in the vitest.config.js file to balance performance and safety [1][4][5]. Regarding your query about "oxc," the Oxlint (Oxc) project maintains a linter that includes support for Vitest-specific rules (often ported from or compatible with Jest rules), such as vitest/no-conditional-tests or vitest/no-standalone-expect [6][7][8]. There is no known direct functional relationship between the Vitest --no-isolate flag and the Oxc toolset itself, other than Oxc providing linting rules to help maintain test quality in Vitest projects [6][9][10].
Citations:
- 1: https://github.com/vitest-dev/vitest/blob/1a4705da/docs/guide/improving-performance.md
- 2: https://vitest.dev/guide/improving-performance
- 3: https://vitest.dev/guide/cli
- 4: Per-file opt-in isolation vitest-dev/vitest#8010
- 5: vitest-dev/vitest@4d55a026
- 6: https://oxc.rs/docs/guide/usage/linter/rules/vitest/no-conditional-tests
- 7: feat(linter): add vitest/no-standalone-expect rule oxc-project/oxc#8986
- 8: feat(linter): split jest/no-disabled-tests into vitest/no-disabled-tests oxc-project/oxc#21802
- 9: feat(linter): add more Vitest compatible Jest rules oxc-project/oxc#8445
- 10: fix(linter): detect vitest jest alias rules oxc-project/oxc#7567
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/conformance.yml"
echo "=== Workflow file excerpt: $FILE (lines 1-140) ==="
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,140p'
else
echo "File not found: $FILE" >&2
exit 1
fi
echo ""
echo "=== Search for vitest flags in workflow ==="
rg -n "vitest .*run|--no-isolate|--isolate|pool=|forks|isolat" "$FILE" || true
echo ""
echo "=== Search repository for same --no-isolate usage ==="
rg -n "--no-isolate" .github/workflows || trueRepository: analogjs/analog
Length of output: 199
🌐 Web query:
OXC NAPI binary crashes vitest isolated forks pool --no-isolate issue
💡 Result:
Crashes involving native Node-API (NAPI) binaries, such as those used in the OXC project, often stem from incompatibility between the binary's C++/Rust runtime and the default multi-threaded test environment in Vitest [1][2]. Key insights regarding this issue include: 1. Native Binary Incompatibility: Native modules (including those built with NAPI) frequently struggle with node:worker_threads, which Vitest uses by default [1][2]. These modules may throw Napi::Error or cause segmentation faults due to how they handle memory or thread-local storage [3][1][2]. 2. Use of Alternative Pools: To resolve these crashes, Vitest introduced pool: 'forks', which utilizes node:child_process instead of worker threads [1][2]. For projects relying on native binaries, switching to pool: 'forks' is the recommended solution as it provides better isolation and compatibility for C++/Rust-backed modules [3][1]. 3. The --no-isolate Flag: While some users attempt to use --no-isolate to debug or work around process crashes, this flag relates to test environment isolation [4]. If a native module crashes the process (e.g., a segmentation fault), the crash will typically persist regardless of whether isolation is enabled [5]. Note that --no-isolate combined with --no-file-parallelism (or --max-workers=1) runs tests sequentially in a single process, which may bypass some concurrency-related issues but can negatively impact performance [4][5]. 4. Practical Guidance: - If experiencing crashes, verify your Vitest configuration is set to pool: 'forks' [1]. - If you are still encountering issues, check your configuration for specific test exclusions (often used for problematic native tests on specific platforms like Windows) [6]. - Ensure your project is not relying on configurations that force all tests into a single thread if the underlying native code requires separate process memory spaces [1][4]. For specific projects like OXC, tests are generally run using vitest run, and issues on platforms like Windows or specific environments are sometimes managed by excluding the failing test files or adjusting the pool settings in the Vitest configuration [6].
Citations:
- 1: fix!: change default
poolto'forks'vitest-dev/vitest#5047 - 2: vitest will crash when using ffi-napi vitest-dev/vitest#2091
- 3: Crashes with libc++abi: terminating with uncaught exception of type Napi::Error vitest-dev/vitest#2972
- 4: Share a single jsdom between suites when running as
--no-isolatevitest-dev/vitest#8478 - 5: Bug Report - Segmentation fault on MacOS with workerd and wrangler and vitest cloudflare/workerd#6583
- 6: feat(ci): run napi tests on windows oxc-project/oxc#14383
🌐 Web query:
site:github.com oxc vitest isolate napi crash
💡 Result:
Crashes involving Oxc (a collection of high-performance JavaScript tools written in Rust) and Vitest, specifically when involving NAPI (Node-API) bindings, generally stem from concurrency, threading, and resource management limitations rather than a single direct "isolation crash" bug [1][2][3][4]. If you are experiencing crashes when running NAPI-based tests in Vitest, consider the following common causes and solutions identified by the Oxc and Vitest communities: 1. Pool/Isolation Configuration Vitest uses worker threads by default, which can cause issues with native add-ons that are not thread-safe or are incompatible with Node.js worker threads [1][2]. - Solution: Try setting pool: 'forks' in your vitest.config.ts [1][2]. Using forks instead of worker threads executes tests in separate child processes, which often resolves segmentation faults or Napi::Error crashes caused by native libraries [1][2][3]. 2. Node.js Shutdown Race Conditions Oxc has identified specific race conditions in Node.js versions prior to 25.4.0 related to the cleanup of ThreadsafeFunction during V8 shutdown [5]. This can lead to crashes like Check failed: node->IsInUse() or Check failed: object_ != kGlobalHandleZapValue [3][5]. - Solution: Ensure you are using a modern Node.js version (25.4.0 or later). If upgrading is not possible, Oxc has implemented internal workarounds (like adding short delays before process exit) to mitigate this [5]. 3. Worker Thread Incompatibility Some NAPI-based modules are fundamentally incompatible with worker_threads [2][4]. - Solution: Avoid using oxc-parser or other native bindings inside worker threads if they trigger heap corruption or stability issues [4]. Where possible, use the provided asynchronous bindings (parseAsync), which are designed to handle off-main-thread execution more safely than spawning your own workers [4]. 4. Resource Limits Crashes such as SIGABRT or memory-related aborts can occur if tools like oxlint (which allocate large amounts of virtual address space) run under strict resource limits (e.g., ulimit -v) that interfere with mmap() calls [6]. - Solution: If running in CI environments, ensure that strict memory limits are not inappropriately applied to processes that require large virtual memory allocation, even if their physical RSS usage is low [6]. If you are encountering a crash, check the native stack trace provided by the crash output [3][6]. Crashes originating from the native layer (the .node binary) often point to underlying Rust or NAPI-RS issues rather than the Vitest test runner itself [2][6].
Citations:
- 1: Crashes with libc++abi: terminating with uncaught exception of type Napi::Error vitest-dev/vitest#2972
- 2: vitest will crash when using ffi-napi vitest-dev/vitest#2091
- 3: Check failed: node->IsInUse(). Crash in watch mode vitest-dev/vitest#5037
- 4: npm oxc-parser: importing package in Node worker thread leads to heap corruption error on Windows oxc-project/oxc#14732
- 5: fix(oxfmt): workaround Node.js ThreadsafeFunction cleanup race condition oxc-project/oxc#18980
- 6: oxlint 1.50.0 crashes with SIGABRT on large TypeScript projects (dead code / no-unused-vars analysis) oxc-project/oxc#19660
🌐 Web query:
"no-isolate" oxc vitest
💡 Result:
The query conflates two distinct technical features from the Vitest and Oxc ecosystems, which are unrelated to each other: 1. Vitest --no-isolate: This is a configuration option in Vitest used to improve test performance [1][2]. By default, Vitest runs each test file in an isolated environment (such as a separate Worker or process) to ensure side-effect-free test execution [1][3]. Using the --no-isolate CLI flag or setting test.isolate: false in the configuration disables this isolation [1][3][4]. This can speed up tests by avoiding the overhead of re-creating environments, but it risks test contamination if your code relies on global state or has side effects [5][2][6]. 2. Oxc Isolated Declarations: This is a feature of the Oxc (JavaScript Oxidation Compiler) project [7][8]. It refers to the implementation of the TypeScript "isolated declarations" specification, which allows for the generation of.d.ts declaration files without performing full type checking [7][8]. This enables faster, parallelized declaration emission during build processes [7][8]. It is unrelated to test runner isolation [7][9]. In summary, --no-isolate in Vitest is a performance optimization for test execution [1][6], while isolated declarations in Oxc is a build-time performance optimization for TypeScript declaration generation [7][8].
Citations:
- 1: https://github.com/vitest-dev/vitest/blob/1a4705da/docs/guide/improving-performance.md
- 2: https://vitest.dev/guide/improving-performance
- 3: https://vitest.dev/config/isolate
- 4: https://vitest.dev/guide/cli
- 5: feat: make
isolatedefault to false vitest-dev/vitest#969 - 6: https://vitest.dev/guide/features
- 7: https://github.com/oxc-project/oxc/tree/main/crates/oxc_isolated_declarations
- 8: https://crates.io/crates/oxc_isolated_declarations/0.118.0
- 9: https://npmx.dev/package/oxc-transform
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/conformance.yml"
echo "=== File exists? ==="
ls -la "$FILE" || exit 1
echo ""
echo "=== Show conformance.yml with line numbers (40-90) ==="
awk 'NR>=40 && NR<=90 {printf "%4d:%s\n", NR, $0}' "$FILE"
echo ""
echo "=== Extract matrix.engine definition (if any) ==="
rg -n "matrix\.engine|CONFORMANCE_ENGINE|engine:" "$FILE" || true
echo ""
echo "=== Show the vitest run step command block (if any) ==="
rg -n "vitest run .*conformance\.spec|--no-isolate" "$FILE" || trueRepository: analogjs/analog
Length of output: 1760
Conditionally apply --no-isolate only for matrix.engine == 'oxc', and tighten the workaround rationale.
.github/workflows/conformance.ymlrunsvitest ... --no-isolatefor bothengine: [ts, oxc], so TS is also losing isolation.- The inline comment’s description (“single shared worker / isolated forks pool”) is imprecise for what
--no-isolatedoes; update wording to match Vitest semantics. - “Identical pass rate, harmless on TS” is unsupported in the workflow—remove or substantiate.
- Add a link to the upstream OXC/Vitest issue/PR tracking the NAPI crash so the workaround can be reverted/adjusted later.
Apply --no-isolate conditionally
- name: Run conformance tests
env:
ANGULAR_SOURCE_DIR: .angular-conformance
CONFORMANCE_ENGINE: ${{ matrix.engine }}
- # OXC's NAPI binary crashes vitest's default isolated forks pool.
- # `--no-isolate` runs all spec files in a single shared worker,
- # which the binary survives. Identical pass rate, harmless on TS.
- run: pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts --no-isolate
+ # OXC's NAPI binary crashes vitest's default isolated forks pool.
+ # Apply --no-isolate only to OXC runs while the crash is investigated.
+ run: |
+ if [ "${{ matrix.engine }}" = "oxc" ]; then
+ pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts --no-isolate
+ else
+ pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts
+ fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # OXC's NAPI binary crashes vitest's default isolated forks pool. | |
| # `--no-isolate` runs all spec files in a single shared worker, | |
| # which the binary survives. Identical pass rate, harmless on TS. | |
| run: pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts --no-isolate | |
| # OXC's NAPI binary crashes vitest's default isolated forks pool. | |
| # Apply --no-isolate only to OXC runs while the crash is investigated. | |
| run: | | |
| if [ "${{ matrix.engine }}" = "oxc" ]; then | |
| pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts --no-isolate | |
| else | |
| pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/conformance.yml around lines 54 - 57, Update the
conformance workflow so the vitest run command only appends --no-isolate when
matrix.engine == 'oxc' (i.e., guard the run step or use a conditional to include
the flag for the oxc matrix value), revise the inline comment to accurately
state that --no-isolate runs tests in a single worker rather than using Vitest's
worker isolation model, remove the unsupported claim about TS being harmless,
and add a link to the upstream OXC↔Vitest N-API crash issue/PR so the workaround
can be tracked and reverted later; apply these edits around the existing run:
pnpm exec vitest ... line and reference matrix.engine in the condition.
| @@ -1,5 +1,6 @@ | |||
| import { describe, it, expect } from 'vitest'; | |||
| import { compile, type CompileOptions } from './compile'; | |||
| import { oxcTransform } from './oxc-engine'; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts"
echo "== File: $FILE (first 120 lines with numbers) =="
nl -ba "$FILE" | head -n 120
echo
echo "== Search for ENGINE gating in the file =="
rg -n "ENGINE|oxc|oxcTransform|getOxcTransform|oxc-engine" "$FILE" || true
echo
echo "== Show the block around the referenced lines 35-80 =="
nl -ba "$FILE" | sed -n '35,90p'
echo
echo "== Inspect oxc-engine export shape used by tests =="
# Find the referenced oxc-engine file
OXCF="packages/vite-plugin-angular/src/lib/compiler/oxc-engine.ts"
if [ -f "$OXCF" ]; then
nl -ba "$OXCF" | head -n 120
else
echo "Missing expected file $OXCF; searching by filename..."
fd -a "oxc-engine.*" packages/vite-plugin-angular/src/lib/compiler || true
fi
echo
echo "== Inspect devkit.ts for the esbuild angular compiler plugin reference =="
DEVKIT="packages/vite-plugin-angular/src/lib/utils/devkit.ts"
if [ -f "$DEVKIT" ]; then
nl -ba "$DEVKIT" | head -n 220
else
echo "Missing expected file $DEVKIT; searching..."
fd -a "devkit.ts" packages/vite-plugin-angular/src/lib/utils || true
fi
echo
echo "== Search for angular compiler-plugin.js missing in repo references =="
rg -n "compiler-plugin\.js|`@angular-devkit/build-angular/src/tools/esbuild/angular`" -S packages/vite-plugin-angular/src || trueRepository: analogjs/analog
Length of output: 229
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts"
awk '{printf "%6d\t%s\n", NR, $0}' "$FILE" | head -n 120
echo
echo "== Search for engine gating / oxc-engine usage in the file =="
rg -n "ENGINE|oxc|oxcTransform|getOxcTransform|oxc-engine" "$FILE" || true
echo
echo "== Show the block around lines 35-90 (based on the original comment) =="
awk 'NR>=35 && NR<=90 {printf "%6d\t%s\n", NR, $0}' "$FILE"
echo
echo
OXCF="packages/vite-plugin-angular/src/lib/compiler/oxc-engine.ts"
if [ -f "$OXCF" ]; then
echo "== Inspect: $OXCF =="
awk '{printf "%6d\t%s\n", NR, $0}' "$OXCF" | head -n 160
else
echo "Missing expected file $OXCF; searching by filename..."
fd -a "oxc-engine.*" packages/vite-plugin-angular/src/lib/compiler || true
fi
echo
echo
DEVKIT="packages/vite-plugin-angular/src/lib/utils/devkit.ts"
if [ -f "$DEVKIT" ]; then
echo "== Inspect: $DEVKIT (first 220 lines) =="
awk '{printf "%6d\t%s\n", NR, $0}' "$DEVKIT" | head -n 220
else
echo "Missing expected file $DEVKIT; searching..."
fd -a "devkit.ts" packages/vite-plugin-angular/src/lib/utils || true
fi
echo
echo
echo "== Repo-wide references to the missing Angular build esbuild compiler plugin =="
rg -n "`@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin`\.js" packages/vite-plugin-angular || true
rg -n "compiler-plugin\.js" packages/vite-plugin-angular || true
rg -n "esbuild/angular" packages/vite-plugin-angular/src/lib || trueRepository: analogjs/analog
Length of output: 5385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
OXCF="packages/vite-plugin-angular/src/lib/compiler/oxc-engine.ts"
if [ -f "$OXCF" ]; then
echo "== $OXCF (first 240 lines) =="
awk '{printf "%6d\t%s\n", NR, $0}' "$OXCF" | head -n 240
else
echo "Missing $OXCF"
exit 1
fi
echo
echo "== Search within oxc-engine.ts for devkit / `@angular-devkit` / esbuild compiler-plugin =="
rg -n "`@angular-devkit/build-angular`|esbuild/angular/compiler-plugin|devkit|from './.*devkit|compiler-plugin\.js|angular/compiler-plugin" "$OXCF" || true
echo
echo "== Inspect devkit.ts (first 240 lines) =="
DEVKIT="packages/vite-plugin-angular/src/lib/utils/devkit.ts"
if [ -f "$DEVKIT" ]; then
awk '{printf "%6d\t%s\n", NR, $0}' "$DEVKIT" | head -n 240
else
echo "Missing $DEVKIT"
exit 1
fi
echo
echo "== Search within devkit.ts for the missing compiler plugin path =="
rg -n "`@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin`\.js|compiler-plugin\.js|esbuild/angular" "$DEVKIT" || true
echo
echo "== Repo-wide references to the missing compiler plugin =="
rg -n "`@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin`\.js" packages/vite-plugin-angular/src || trueRepository: analogjs/analog
Length of output: 10364
🏁 Script executed:
#!/bin/bash
set -euo pipefail
DEVKIT="packages/vite-plugin-angular/src/lib/utils/devkit.ts"
echo "== $DEVKIT (first 160 lines) =="
awk '{printf "%6d\t%s\n", NR, $0}' "$DEVKIT" | head -n 160
echo
echo "== devkit.ts: references to the missing compiler-plugin.js path =="
rg -n "`@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin`\.js|`@angular-devkit/build-angular/src/tools/esbuild/angular`" "$DEVKIT" || true
echo
echo "== devkit.ts: any top-level dynamic/static imports of `@angular-devkit/build-angular` =="
rg -n "import\(|import .*from|require\(|`@angular-devkit/build-angular`" "$DEVKIT" || trueRepository: analogjs/analog
Length of output: 3645
Lazy-load oxc-engine in conformance.spec.ts to avoid Angular 17 devkit module-load crashes.
conformance.spec.ts eagerly imports oxcTransform, which loads oxc-engine.ts, which loads utils/devkit.ts at module scope; for Angular 17 it eagerly require()s @angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin.js, so the suite can crash before the ENGINE (ts vs oxc) branch is reached.
Suggested fix
-import { oxcTransform } from './oxc-engine';
+type OxcTransform = typeof import('./oxc-engine').oxcTransform;
+type OxcCtx = Parameters<OxcTransform>[2];
+
+let oxcTransformPromise: Promise<OxcTransform> | null = null;
+function getOxcTransform(): Promise<OxcTransform> {
+ if (!oxcTransformPromise) {
+ oxcTransformPromise = import('./oxc-engine').then((m) => m.oxcTransform);
+ }
+ return oxcTransformPromise;
+}
@@
-const OXC_CTX: Parameters<typeof oxcTransform>[2] = {
+const OXC_CTX: OxcCtx = {
@@
if (ENGINE === 'oxc') {
- const result = await oxcTransform(code, fileName, OXC_CTX);
+ const oxcTransform = await getOxcTransform();
+ const result = await oxcTransform(code, fileName, OXC_CTX);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts` at line 3,
conformance.spec.ts currently eagerly imports oxcTransform (from ./oxc-engine)
at module scope which triggers oxc-engine -> utils/devkit to require Angular
devkit during test file load; change to a lazy/dynamic import so the module is
loaded only when needed (e.g. inside the test, beforeAll, or inside the specific
describe/it that uses oxcTransform): replace the top-level static import of
oxcTransform with an awaited import('./oxc-engine') (or require) at runtime,
then grab the exported oxcTransform and run the existing tests, ensuring any
references to ENGINE branching (ts vs oxc) remain intact and types are handled
after the dynamic import.
Source: Pipeline failures
| import { mkdtempSync } from 'node:fs'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { join } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { resolveConfig } from 'vite'; | ||
|
|
||
| import { oxcTransform } from './oxc-engine.js'; | ||
|
|
||
| const ctx = async (): Promise<Parameters<typeof oxcTransform>[2]> => ({ | ||
| resolvedConfig: await resolveConfig({}, 'serve'), | ||
| inlineStylesExtension: 'css', | ||
| liveReload: false, | ||
| watchMode: false, | ||
| jit: false, | ||
| }); | ||
|
|
||
| describe('OXC adapter diagnostic surfacing', () => { | ||
| it('returns structured diagnostics instead of throwing on compile errors', async () => { | ||
| const dir = mkdtempSync(join(tmpdir(), 'oxc-diag-')); | ||
| const file = join(dir, 'broken.component.ts'); | ||
| // Two `*` template-prefix bindings on the same element — Angular's | ||
| // template parser surfaces this as a structured error. The contract | ||
| // under test is "errors come back on `diagnostics`, not as a throw", | ||
| // not a specific message string. | ||
| const src = ` | ||
| import { Component } from '@angular/core'; | ||
| @Component({ | ||
| selector: 'app-broken', | ||
| template: '<div *ngIf="a" *ngFor="let b of items"></div>', | ||
| }) | ||
| export class BrokenComponent { a = true; items = []; } | ||
| `; | ||
| const result = await oxcTransform(src, file, await ctx()); | ||
|
|
||
| const errors = result.diagnostics.filter((d) => d.severity === 'Error'); | ||
| expect(errors.length).toBeGreaterThan(0); | ||
| for (const e of errors) { | ||
| // Headline message preserved. | ||
| expect(e.message).toBeTruthy(); | ||
| // `formatted` is what `this.error()` receives — should start with | ||
| // the `[oxc-angular]` prefix so the dev-server overlay attributes | ||
| // the diagnostic to this plugin. | ||
| expect(e.formatted).toMatch(/\[oxc-angular\]/); | ||
| } | ||
| }); | ||
|
|
||
| it('returns an empty diagnostics array for a clean compile', async () => { | ||
| const dir = mkdtempSync(join(tmpdir(), 'oxc-diag-clean-')); | ||
| const file = join(dir, 'fine.component.ts'); | ||
| const src = ` | ||
| import { Component } from '@angular/core'; | ||
| @Component({ | ||
| selector: 'app-fine', | ||
| template: '<h1>Hello</h1>', | ||
| }) | ||
| export class FineComponent {} | ||
| `; | ||
| const result = await oxcTransform(src, file, await ctx()); | ||
| expect(result.diagnostics).toEqual([]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
**Pipeline failure: Tests need Angular version guard.**The CI failure on Angular 17.3.12 is expected — "While the builders when executed via the Angular CLI and their associated options are considered stable, the programmatic APIs are not considered officially supported." The compiler-plugin.js path doesn't exist in Angular 17's build tooling.
These OXC-specific tests should be skipped when running against older Angular versions that don't have the required internal APIs. Consider adding a version guard or moving these tests to a separate test configuration that only runs on supported Angular versions (18+).
🧰 Tools
🪛 GitHub Actions: Compiler Compatibility Tests / Angular 17.3.12
[error] 1-1: Vitest failed due to missing module '@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin.js' (error originates from packages/vite-plugin-angular/src/lib/utils/devkit.ts).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-diagnostics.spec.ts` around
lines 1 - 61, Add a runtime Angular version guard to these tests so they skip
when the installed Angular is older than the supported version (e.g. <18);
locate the test file using the oxcTransform import (oxc-diagnostics.spec.ts)
and, at the top of the spec, programmatically read the installed Angular package
version (via require/import of `@angular/core/package.json` or reading
node_modules/@angular/core/package.json) and call describe.skip or early return
from the top-level describe when the semver check fails; ensure the skip only
affects the tests around oxcTransform diagnostics and leave normal behavior when
Angular version >=18.
Source: Pipeline failures
| /* ─────────────────────────────────────────────────────────────────────────── | ||
| * TEMPORARY: inline port of `@oxc-angular/vite`'s build-optimizer plugin. | ||
| * | ||
| * Upstream source: `@oxc-angular/vite@0.0.30` → `vite-plugin/angular-build- | ||
| * optimizer-plugin.ts`. The plugin sets up Angular production build defines | ||
| * (ngDevMode, ngJitMode, ngI18nClosureMode, ngServerMode) and runs OXC's | ||
| * `optimizeAngularPackage` over FESM packages from node_modules in | ||
| * production builds. | ||
| * | ||
| * Optimizations applied (matches OXC defaults): | ||
| * - elideMetadata: drop `ɵsetClassMetadata(...)` calls for tree-shaking | ||
| * - wrapStaticMembers: pure-IIFE-wrap static Ivy member assignments | ||
| * - markPure: annotate top-level calls with /* @__PURE__ *\/ | ||
| * - adjustEnums: optimize TS enum patterns into pure IIFEs | ||
| * | ||
| * Ported because the `buildOptimizerPlugin()` factory isn't on | ||
| * `@oxc-angular/vite/api`. The actual optimization work runs through the | ||
| * public `optimizeAngularPackage` NAPI export. | ||
| * | ||
| * TODO(oxc-engine): swap for a re-export of `buildOptimizerPlugin` once | ||
| * OXC exposes the plugin factory publicly. | ||
| * | ||
| * Regex use is limited to file-path filtering (`fesm20.*\.[cm]?js$`) — | ||
| * allowed by the prefer-OXC-AST rule. | ||
| * ─────────────────────────────────────────────────────────────────────── */ | ||
| import type { Plugin, UserConfig } from 'vite'; | ||
|
|
||
| import { loadOxcHmrApi } from './oxc-engine.js'; | ||
|
|
||
| interface OxcOptimizeOptions { | ||
| sourcemap?: boolean; | ||
| elideMetadata?: boolean; | ||
| wrapStaticMembers?: boolean; | ||
| markPure?: boolean; | ||
| adjustEnums?: boolean; | ||
| } | ||
|
|
||
| interface OxcOptimizerPluginOptions { | ||
| jit: boolean; | ||
| sourcemap: boolean; | ||
| } | ||
|
|
||
| // FESM packages — what OXC's optimizer targets. Matches Angular's | ||
| // own published bundles (`@angular/core/fesm2022/core.mjs`, etc.) and | ||
| // most third-party Angular libraries publishing in the same layout. | ||
| const FESM_REGEX = /fesm20.*\.[cm]?js$/; | ||
|
|
||
| export function oxcOptimizerPlugin({ | ||
| jit, | ||
| sourcemap, | ||
| }: OxcOptimizerPluginOptions): Plugin { | ||
| let isProd = false; | ||
|
|
||
| return { | ||
| name: '@analogjs/vite-plugin-angular-oxc-optimizer', | ||
| apply: 'build', | ||
|
|
||
| config(userConfig) { | ||
| isProd = | ||
| userConfig.mode === 'production' || | ||
| process.env['NODE_ENV'] === 'production'; | ||
| const isSSR = !!userConfig.build?.ssr; | ||
| const ngServerMode = `${isSSR}`; | ||
|
|
||
| if (isProd) { | ||
| const defines = { | ||
| ngJitMode: jit ? 'true' : 'false', | ||
| ngI18nClosureMode: 'false', | ||
| ngDevMode: 'false', | ||
| ngServerMode, | ||
| }; | ||
| return { | ||
| define: defines, | ||
| oxc: { define: defines }, | ||
| } as UserConfig; | ||
| } | ||
|
|
||
| // Dev SSR still needs `ngServerMode` so the runtime branches | ||
| // into platform-server. Production-only flags stay unset. | ||
| if (isSSR) { | ||
| const defines = { ngServerMode }; | ||
| return { | ||
| define: defines, | ||
| oxc: { define: defines }, | ||
| } as UserConfig; | ||
| } | ||
|
|
||
| return undefined; | ||
| }, | ||
|
|
||
| transform: { | ||
| filter: { id: FESM_REGEX }, | ||
| async handler(code, id) { | ||
| // Optimization only kicks in for production — dev keeps source | ||
| // legible and avoids pure-IIFE wrapping that hampers debugging. | ||
| if (!isProd) return; | ||
|
|
||
| try { | ||
| const api = await loadOxcHmrApi(); | ||
| const result = await api.optimizeAngularPackage(code, id, { | ||
| sourcemap, | ||
| elideMetadata: true, | ||
| wrapStaticMembers: true, | ||
| markPure: true, | ||
| adjustEnums: true, | ||
| } as OxcOptimizeOptions); | ||
|
|
||
| return { code: result.code, map: result.map ?? null }; | ||
| } catch (e) { | ||
| // Vite warning so the failure shows in the dev-server overlay | ||
| // and build log. We don't `this.error()` — the unoptimized | ||
| // bundle is still functionally correct. | ||
| this.warn( | ||
| `[oxc-optimizer] Failed to optimize ${id}: ${(e as Error)?.message ?? e}`, | ||
| ); | ||
| return; | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for any imports or usages of oxcOptimizerPlugin
rg -n "oxcOptimizerPlugin" --type tsRepository: analogjs/analog
Length of output: 297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== rg: oxcOptimizerPlugin =="
rg -n --hidden --glob '!*node_modules/*' "oxcOptimizerPlugin" .
echo
echo "== rg: buildOptimizerPlugin =="
rg -n --hidden --glob '!*node_modules/*' "buildOptimizerPlugin" packages/vite-plugin-angular/src/lib
echo
echo "== Inspect angular-vite-plugin.ts (relevant region) =="
FILE="packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts"
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,220p'
else
echo "Missing $FILE"
fi
echo
echo "== Inspect angular-build-optimizer-plugin.ts =="
FILE="packages/vite-plugin-angular/src/lib/angular-build-optimizer-plugin.ts"
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,260p'
else
echo "Missing $FILE"
fi
echo
echo "== Inspect oxc-optimizer-plugin.ts export signature & usage of optimizeAngularPackage =="
FILE="packages/vite-plugin-angular/src/lib/compiler/oxc-optimizer-plugin.ts"
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,240p'
else
echo "Missing $FILE"
fiRepository: analogjs/analog
Length of output: 1517
Wire up (or remove) unused oxcOptimizerPlugin: packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts uses buildOptimizerPlugin(...) for production optimization, and the only references to oxcOptimizerPlugin are its export in packages/vite-plugin-angular/src/lib/compiler/oxc-optimizer-plugin.ts plus a “Revisit oxcOptimizerPlugin once upstream closes the gap” comment—either integrate it into the optimizer selection/feature flag or remove the dead code.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-optimizer-plugin.ts` around
lines 1 - 121, The oxcOptimizerPlugin export is dead code: either wire it into
the optimizer selection or remove it. Fix by either (A) integrating
oxcOptimizerPlugin into the existing optimizer chooser where
buildOptimizerPlugin(...) is selected (call oxcOptimizerPlugin({...}) instead of
buildOptimizerPlugin when using the OXC path, pass the same options such as
jit/sourcemap, and ensure loadOxcHmrApi()/optimizeAngularPackage are available
at runtime), or (B) delete the oxcOptimizerPlugin export and remove any related
imports/comments so only the existing buildOptimizerPlugin remains; target the
functions oxcOptimizerPlugin and buildOptimizerPlugin (and the transform handler
that calls loadOxcHmrApi) when applying the change.
| import { afterAll, beforeAll, describe, expect, it } from 'vitest'; | ||
|
|
||
| import { compile as tsCompile } from './compile.js'; | ||
| import { oxcTransform } from './oxc-engine.js'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the exact file
FILE="packages/vite-plugin-angular/src/lib/compiler/oxc-parity.spec.ts"
test -f "$FILE"
echo "== $FILE =="
wc -l "$FILE"
# Show the relevant sections around the referenced lines
sed -n '1,80p' "$FILE"
echo "----"
sed -n '80,150p' "$FILE"
echo "----"
sed -n '150,240p' "$FILE"
# Also show any references to oxcAvailable / skip reasons / dynamic imports
rg -n "oxcAvailable|oxcSkipReason|beforeAll|oxc-engine|oxcTransform" "$FILE"Repository: analogjs/analog
Length of output: 8308
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the oxc-engine module(s)
ls -la packages/vite-plugin-angular/src/lib/compiler | sed -n '1,200p'
# Show any oxc-engine file(s)
fd -a "oxc-engine\.(ts|js)$" packages/vite-plugin-angular/src/lib/compiler -t f -H || true
fd -a "oxc-engine" packages/vite-plugin-angular/src/lib/compiler -t f -H || true
# Print relevant sections from oxc-engine.ts / .js (bounded)
for f in $(fd -a "oxc-engine\.(ts|js)$" packages/vite-plugin-angular/src/lib/compiler -t f -H); do
echo "===== $f (head) ====="
head -n 120 "$f"
echo "===== $f (mid) ====="
sed -n '120,260p' "$f"
echo "===== $f (tail) ====="
tail -n 120 "$f"
done
# Also inspect how oxcTransform is exported (exact text search)
rg -n "export (async )?function oxcTransform|export const oxcTransform|export \{.*oxcTransform" packages/vite-plugin-angular/src/lib/compiler/oxc-engine.* 2>/dev/null || trueRepository: analogjs/analog
Length of output: 18117
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/vite-plugin-angular/src/lib/compiler/oxc-engine.ts"
echo "== $FILE (oxcTransform section) =="
# Show around the export
rg -n "export async function oxcTransform" "$FILE"
sed -n '330,460p' "$FILE"
echo "== Also check for top-level calls to loadOxcApi/loadOxcHmrApi =="
rg -n "loadOxcApi\(|loadOxcHmrApi\(" "$FILE"Repository: analogjs/analog
Length of output: 4450
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/vite-plugin-angular/src/lib/utils/devkit.js"
test -f "$FILE"
echo "== $FILE =="
wc -l "$FILE"
sed -n '1,220p' "$FILE"
rg -n "throw |fs\.|readFile|existsSync|process\.(env|cwd)|require\(|import\(" "$FILE" || true
# Also locate devkit TypeScript if present
fd -a "devkit\.(ts|js)$" packages/vite-plugin-angular/src/lib/utils -t f -H || trueRepository: analogjs/analog
Length of output: 41
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the devkit module(s) that oxc-engine imports
fd -a "devkit\.(ts|js|mjs|cjs)$" packages/vite-plugin-angular/src/lib -t f -H
fd -a "devkit" packages/vite-plugin-angular/src/lib -t f -H
# Also locate the exact import string from oxc-engine.ts
rg -n "from '\\.\\./utils/devkit\\.js'|from \"\\.\\./utils/devkit\\.js\"" packages/vite-plugin-angular/src/lib/compiler/oxc-engine.ts
# If devkit.js exists, inspect it; otherwise inspect devkit.ts.
DEVKIT_JS="$(fd -a "devkit\.js$" packages/vite-plugin-angular/src/lib/utils -t f -H || true)"
DEVKIT_TS="$(fd -a "devkit\.ts$" packages/vite-plugin-angular/src/lib/utils -t f -H || true)"
if [ -n "$DEVKIT_JS" ]; then
FILE="$DEVKIT_JS"
elif [ -n "$DEVKIT_TS" ]; then
FILE="$DEVKIT_TS"
else
echo "No devkit.* found under packages/vite-plugin-angular/src/lib/utils"
exit 0
fi
echo "== Inspecting $FILE =="
wc -l "$FILE"
sed -n '1,260p' "$FILE"
rg -n "throw |process\\.(env|cwd)|readFile|existsSync|require\\(|import\\(" "$FILE" || trueRepository: analogjs/analog
Length of output: 2986
Lazy-load oxc-engine so import-time failures don’t bypass the oxcAvailable skip
oxc-parity.spec.ts eagerly imports oxcTransform, but oxc-engine.ts imports utils/devkit.ts at module scope; devkit.ts can throw during module evaluation (e.g. Angular <17 or missing/unrequirable Angular devkit internals). That happens before beforeAll can set oxcAvailable=false, so the suite won’t degrade gracefully in those environments. Replace the top-level oxcTransform import with a dynamic import('./oxc-engine.js') in beforeAll, store the loaded oxcTransform in a local variable, and use it for the warmup and compileOxc.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/vite-plugin-angular/src/lib/compiler/oxc-parity.spec.ts` at line 27,
The test imports oxcTransform at module scope which triggers devkit.ts
evaluation errors before beforeAll can set oxcAvailable; change the top-level
"import { oxcTransform } from './oxc-engine.js'" to a dynamic import inside the
beforeAll (e.g. const { oxcTransform } = await import('./oxc-engine.js')) and
store oxcTransform in a local variable used for the warmup call and any
compileOxc uses, so that missing/devkit failures are caught during the beforeAll
check and the suite can set oxcAvailable=false and skip gracefully.
Source: Pipeline failures
| // Angular Ivy `.d.ts` member declarations collected during OXC-engine | ||
| // library builds, keyed by class name (last write wins — a library | ||
| // publishes one class per name). Consumed by `generateBundle` to augment | ||
| // the `.d.ts` a separate declaration generator emits. Only populated when | ||
| // `fastCompileEngine: 'oxc'` and `fastCompileMode: 'partial'`. | ||
| const collectedDtsDeclarations = new Map<string, string>(); |
There was a problem hiding this comment.
Class-name-only .d.ts aggregation can corrupt library declaration output.
Line 108 keys collected Ivy members only by className, Line 376 overwrites on collision, and Line 546 injects that global set into every .d.ts asset by name. Two Angular classes with the same name in different files (valid in TS modules) will lose one declaration or receive the wrong Ivy members, breaking downstream template type-checking contracts.
Please carry source/module identity through collection and inject only declarations belonging to the current .d.ts asset (or fail fast on ambiguous collisions).
Also applies to: 372-377, 546-560
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/vite-plugin-angular/src/lib/fast-compile-plugin.ts` around lines 103
- 108, The collectedDtsDeclarations Map is keyed only by class name which allows
collisions across modules; change the collection and injection logic so entries
carry module/source identity (e.g. key by `${moduleId}::${className}` or store
an object {moduleId,className,decl}), update the code that populates
collectedDtsDeclarations (where fastCompileEngine/fastCompileMode collect Ivy
members) to include module/file identity and to detect collisions and throw
(fail-fast) if ambiguous, and update generateBundle where the global set is
injected into .d.ts assets to filter/select only declarations whose moduleId
matches the current asset (or to match by original source path) so only the
correct class declarations are injected into each .d.ts. Ensure references to
collectedDtsDeclarations and generateBundle are updated consistently.
Remove the separate `fastCompileEngine` option; `fastCompile` now accepts `boolean | 'ts' | 'oxc'`. `false`/unset is off, `true`/'ts' is the in-tree engine, 'oxc' is the native engine. ANALOG_OXC=true still selects oxc when the option is unset. Updates the platform option, docs, and peer-dep error.
PR Checklist
Adds an opt-in native-Rust compilation engine for
fastCompile, backed by the OXC Angular compiler (@oxc-angular/vite).Affected scope
vite-plugin-angularplatform(fastCompileEngineoption), docs-appRecommended merge strategy for maintainer [optional]
What is the new behavior?
fastCompilegains afastCompileEngineoption. WithfastCompileEngine: 'oxc', component compilation routes through@oxc-angular/vite(native Rust via NAPI) instead of the in-tree TS engine. The engine is opt-in and experimental; the default path is unchanged.The adapter (
oxc-engine.ts) wraps the OXC NAPI surface and covers:ɵɵdefineComponent) and JIT compilation@ng/componentvirtual-module contractɵɵngDeclare*→ɵɵdefine*)ɵɵngDeclare*and returnsdtsDeclarations; a dedicatedenforce: 'post'plugin splices the Ivy.d.tstype members into declarations emitted by a separate dts generator (rolldown-plugin-dts / vite-plugin-dts / tsdown / tsc)Also adds a Compilation guide to the docs covering the default path and the OXC-based engine, plus compatibility tables and the compiler caveat. Wired against
@oxc-angular/vite@^0.0.31; output parity with Angular's reference compiler is asserted by the upstream-parity suite.Test plan
Ran targeted
vite-plugin-angularspecs (oxc-parity,oxc-upstream-parity,partial,dts-writer),tsctypecheck of the package, an end-to-end OXC partial library build (verifiedɵɵngDeclareComponentJS + augmented.d.ts), andnx build docs-app(passes withonBrokenLinks: 'throw').Does this PR introduce a breaking change?
The OXC engine is opt-in behind
fastCompileEngine: 'oxc'; default compilation is unchanged. (Note: when the OXC engine is selected, library/partial builds now route through OXC rather than falling back to the TS engine.)Other information
@oxc-angular/viteis an optional peer dependency, only required whenfastCompileEngine: 'oxc'is set.