-
-
Notifications
You must be signed in to change notification settings - Fork 327
feat(vite-plugin-angular): add OXC engine adapter for fastCompile #2368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: beta
Are you sure you want to change the base?
Changes from all commits
4c283b2
2694ce5
f41f796
4aabdaf
b7cc874
3e466fe
94dab0c
ff8582b
6f6acbf
37ffbc2
9304775
6ee0868
9ee6e38
806e7e3
f429901
6125ce2
2756f23
43a62c6
8bf121a
b4d372a
79d6b01
e6cfb07
0a77d3e
527fd23
5125c1a
d95a9d1
7c79eda
46ff568
d45050b
60e3a61
a12da49
a42fe9a
0001718
982b6b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,12 +20,16 @@ env: | |||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||
| conformance: | ||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||
| # 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' }} | ||||||||||||||||||||||||||
| strategy: | ||||||||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||||||||
| matrix: | ||||||||||||||||||||||||||
| angular-major: [17, 18, 19, 20, 21, 'latest', 'next'] | ||||||||||||||||||||||||||
| engine: [ts, oxc] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| name: Angular ${{ matrix.angular-major }} | ||||||||||||||||||||||||||
| name: Angular ${{ matrix.angular-major }} (engine=${{ matrix.engine }}) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||
| - uses: actions/checkout@v3 | ||||||||||||||||||||||||||
|
|
@@ -46,4 +50,8 @@ jobs: | |||||||||||||||||||||||||
| - name: Run conformance tests | ||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||
| ANGULAR_SOURCE_DIR: .angular-conformance | ||||||||||||||||||||||||||
| run: pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts | ||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||
|
Comment on lines
+54
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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:
💡 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 ( Citations:
🌐 Web query:
💡 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:
🌐 Web query:
💡 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:
🏁 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:
💡 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 Citations:
🌐 Web query:
💡 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 Citations:
🌐 Web query:
💡 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:
🏁 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
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| # Angular Compilation | ||
|
|
||
| Analog compiles your Angular components, directives, and pipes to Ivy definitions during the Vite build. There are two compilation paths: | ||
|
|
||
| - **Default compilation** — Angular's own compiler (`ngtsc`), with full compile-time type checking. | ||
| - **OXC-based engine** — a native Rust compiler that produces the same Ivy output much faster. | ||
|
|
||
| Both emit identical Ivy code and identical runtime behavior, because both ultimately call the same `@angular/compiler` lowering APIs. | ||
|
|
||
| ## Default compilation | ||
|
|
||
| This is what you get out of the box — no configuration required: | ||
|
|
||
| ```ts | ||
| // vite.config.ts | ||
| import { defineConfig } from 'vite'; | ||
| import angular from '@analogjs/vite-plugin-angular'; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [angular()], | ||
| }); | ||
| ``` | ||
|
|
||
| The default path runs Angular's compiler through the Angular Compilation API. It performs **full TypeScript and template type checking** (including `strictTemplates`), emits declaration files, and supports i18n message extraction. Use it when you want compile-time safety — wrong template bindings, missing inputs, and type mismatches fail the build. | ||
|
|
||
| ## OXC-based engine | ||
|
|
||
| The OXC-based engine swaps Angular's compiler for [`@oxc-angular/vite`](https://github.com/voidzero-dev/oxc-angular-compiler) — the **OXC Angular compiler**, a native Rust port of Angular's component compiler. It produces equivalent Ivy output and reduces cold-build and hot-rebuild times, since it never constructs a full `ts.Program`. | ||
|
|
||
| ### Enabling | ||
|
|
||
| `@oxc-angular/vite` is an optional peer dependency — install it alongside Analog: | ||
|
|
||
| ```bash | ||
| npm install -D @oxc-angular/vite | ||
| ``` | ||
|
|
||
| Then opt in via the `angular()` plugin: | ||
|
|
||
| ```ts | ||
| // vite.config.ts | ||
| import { defineConfig } from 'vite'; | ||
| import angular from '@analogjs/vite-plugin-angular'; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [ | ||
| angular({ | ||
| fastCompile: 'oxc', | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
|
|
||
| :::note | ||
| In an Analog application, configure the plugin through `@analogjs/platform` instead — its `analog()` plugin forwards the same `fastCompile` option: | ||
|
|
||
| ```ts | ||
| import analog from '@analogjs/platform'; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [analog({ fastCompile: 'oxc' })], | ||
| }); | ||
| ``` | ||
|
|
||
| ::: | ||
|
|
||
| You can also enable the engine without editing your config by setting the `ANALOG_OXC=true` environment variable — useful for trying it in CI. An explicit `fastCompile` in your config takes precedence. | ||
|
|
||
| ### What it supports | ||
|
|
||
| | Capability | OXC-based engine | | ||
| | -------------------------------------------- | ---------------------------------------------------------------------- | | ||
| | AOT compilation (`ɵɵdefineComponent`) | ✅ Native Rust pipeline | | ||
| | JIT compilation | ✅ | | ||
| | Inline `template:` / `styles:` | ✅ Inline SCSS / Sass / Less is preprocessed before compilation | | ||
| | External `templateUrl` / `styleUrl` | ✅ Resolved and preprocessed via Vite | | ||
| | Hot Module Replacement (HMR) | ✅ | | ||
| | Style encapsulation | ✅ | | ||
| | Cross-file selector resolution | ✅ | | ||
| | Library / partial builds (`compilationMode`) | ✅ Emits partial declarations and Ivy `.d.ts` type members (see below) | | ||
| | Compile-time template type checking | ❌ Use the Angular Language Service in your editor | | ||
|
|
||
| ### Building a library | ||
|
|
||
| For a library build (`fastCompileMode: 'partial'`), the OXC engine emits the partial `ɵɵngDeclare*` declarations and augments the emitted `.d.ts` with Angular's Ivy type members (`ɵfac`, `ɵcmp`, …) so consumers get full template type-checking against your published package. | ||
|
|
||
| The base `.d.ts` files themselves must be produced by a declaration generator (`rolldown-plugin-dts`, `vite-plugin-dts`, `tsdown`, or `tsc`); the OXC engine augments those declarations rather than generating them. See [Building an Angular library](/docs/guides/libraries). | ||
|
|
||
| ## Type checking | ||
|
|
||
| The OXC engine does not type-check your templates or TypeScript. To catch those errors, run Angular's compiler as a separate verification step against your app's tsconfig. Pass `--noEmit` so it only reports errors without writing output: | ||
|
|
||
| ```bash | ||
| ngc -p tsconfig.app.json --noEmit | ||
| ``` | ||
|
|
||
| Add it as a script and run it alongside your build: | ||
|
|
||
| ```json | ||
| // package.json | ||
| { | ||
| "scripts": { | ||
| "typecheck": "ngc -p tsconfig.app.json --noEmit" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| This keeps fast builds during development while still failing on template and type errors before you ship — and the Angular Language Service surfaces the same errors live in your editor. | ||
|
|
||
| ## Compatibility | ||
|
|
||
| ### Angular versions | ||
|
|
||
| The OXC Angular compiler supports **Angular 19–22**. | ||
|
|
||
| | Angular Version | OXC-based engine | | ||
| | --------------- | ---------------- | | ||
| | ^22.0.0 | ✅ | | ||
| | ^21.0.0 | ✅ | | ||
| | ^20.0.0 | ✅ | | ||
| | ^19.0.0 | ✅ | | ||
|
|
||
| For overall Angular/Analog/Vite version support, see [Version Compatibility](/docs/guides/compatibility). | ||
|
|
||
| ### Compiler package | ||
|
|
||
| | Analog Version | `@oxc-angular/vite` | | ||
| | -------------- | ------------------- | | ||
| | **latest** | ^0.0.31 | | ||
|
|
||
| ### Default vs OXC-based engine | ||
|
|
||
| | | Default | OXC-based engine | | ||
| | ----------------------------------- | ----------------------- | ------------------------------------------ | | ||
| | Engine | Angular (`ngtsc`) | Native Rust (`@oxc-angular/vite`) | | ||
| | Ivy output | Full | Identical | | ||
| | Cold build / hot rebuild | Standard | Significantly faster | | ||
| | Compile-time template type checking | Yes (`strictTemplates`) | No — use the Angular Language Service | | ||
| | i18n message extraction | Yes | `$localize` emitted (extraction not wired) | | ||
| | Library / partial + `.d.ts` | Yes | Yes (`.d.ts` augmented) | | ||
| | Status | Stable | Experimental | | ||
|
|
||
| ## Caveat | ||
|
|
||
| :::caution | ||
| The OXC Angular compiler is **experimental** and trades compile-time safety for speed. | ||
|
|
||
| - **No compile-time template type checking.** The OXC engine emits identical Ivy output and identical runtime behavior, but it does not validate template bindings, inputs, or types at build time. Run the **Angular Language Service** in your editor to catch these errors as you type — they appear as editor squiggles instead of failing the build. | ||
| - **Optional peer dependency.** The engine requires `@oxc-angular/vite` to be installed. If it is missing, enabling `fastCompile: 'oxc'` throws a clear error at startup. | ||
|
|
||
| If you need template type errors to fail your build (for example in CI), add the [type-checking step](#type-checking) or use the default compilation path. | ||
| ::: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: analogjs/analog
Length of output: 541
🏁 Script executed:
Repository: 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.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-erroris set), plus a clear graduation/removal criterion forcontinue-on-error.🤖 Prompt for AI Agents