Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4c283b2
feat(vite-plugin-angular): add OXC engine adapter for fastCompile
brandonroberts May 29, 2026
2694ce5
feat(vite-plugin-angular): plumb fastCompileEngine: 'ts' | 'oxc' option
brandonroberts May 29, 2026
f41f796
fix(vite-plugin-angular): strip residual TS syntax from OXC engine ou…
brandonroberts May 29, 2026
4aabdaf
test(vite-plugin-angular): add OXC engine parity suite vs TS engine
brandonroberts May 29, 2026
b7cc874
test(vite-plugin-angular): add upstream-anchored parity vs Angular go…
brandonroberts May 29, 2026
3e466fe
fix(vite-plugin-angular): set emitDistinctChangesOnly on signal queries
brandonroberts May 29, 2026
94dab0c
fix(vite-plugin-angular): emit FunctionExpr as a named function expre…
brandonroberts May 29, 2026
ff8582b
test(vite-plugin-angular): mark closed gaps in upstream parity suite
brandonroberts May 29, 2026
6f6acbf
fix(vite-plugin-angular): drop over-defensive parens in js-emitter
brandonroberts May 29, 2026
37ffbc2
test(vite-plugin-angular): close last TS-engine upstream parity gap
brandonroberts May 29, 2026
9304775
feat(vite-plugin-angular): copy OXC decorator-field helpers for OXC HMR
brandonroberts May 29, 2026
6ee0868
feat(vite-plugin-angular): wire OXC HMR contract into fastCompile
brandonroberts May 29, 2026
9ee6e38
test(vite-plugin-angular): close OXC signal_queries divergence in ups…
brandonroberts May 29, 2026
806e7e3
feat(vite-plugin-angular): route JIT through OXC engine when opted in
brandonroberts May 29, 2026
f429901
feat(vite-plugin-angular): preprocess inline styles in OXC engine
brandonroberts May 29, 2026
6125ce2
feat(vite-plugin-angular): wire OXC linker into fastCompile
brandonroberts May 29, 2026
2756f23
feat(vite-plugin-angular): wire OXC build optimizer into fastCompile
brandonroberts May 29, 2026
43a62c6
feat(platform): surface fastCompileEngine on the analog() options
brandonroberts May 29, 2026
8bf121a
feat(vite-plugin-angular): surface structured OXC diagnostics through…
brandonroberts May 29, 2026
b4d372a
fix(vite-plugin-angular): aggregate OXC errors so the overlay shows t…
brandonroberts May 29, 2026
79d6b01
chore(vite-plugin-angular): pin to published @oxc-angular/vite@0.0.30
brandonroberts May 29, 2026
e6cfb07
docs(vite-plugin-angular): describe OXC engine in COMPILER.md
brandonroberts May 29, 2026
0a77d3e
fix(vite-plugin-angular): route FESM optimizer through JavaScriptTran…
brandonroberts May 29, 2026
527fd23
refactor(vite-plugin-angular): rewrite OXC HMR helpers on oxc-parser AST
brandonroberts May 30, 2026
5125c1a
test(vite-plugin-angular): add OXC engine axis to the conformance suite
brandonroberts May 30, 2026
d95a9d1
test(vite-plugin-angular): drop OXC conformance skip; pool isolation …
brandonroberts May 30, 2026
7c79eda
Merge branch 'beta' into feat/fast-compile-oxc-engine
brandonroberts Jun 3, 2026
46ff568
chore(vite-plugin-angular): adopt @oxc-angular/vite 0.0.31
brandonroberts Jun 8, 2026
d45050b
feat(vite-plugin-angular): emit library .d.ts via OXC engine
brandonroberts Jun 8, 2026
60e3a61
docs(vite-plugin-angular): refresh OXC engine status for 0.0.31
brandonroberts Jun 8, 2026
a12da49
docs: add Angular Compilation guide for default and OXC-based engine
brandonroberts Jun 8, 2026
a42fe9a
feat(vite-plugin-angular): enable OXC engine via ANALOG_OXC env var
brandonroberts Jun 8, 2026
0001718
test(vite-plugin-angular): pick main plugin from fastCompilePlugin pair
brandonroberts Jun 8, 2026
982b6b9
refactor(vite-plugin-angular): consolidate fastCompile engine selection
brandonroberts Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Comment on lines +23 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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."
done

Repository: analogjs/analog

Length of output: 3613


Make OXC conformance drift observable despite continue-on-error

  • .github/workflows/conformance.yml sets continue-on-error: ${{ matrix.engine == 'oxc' }}, so OXC failures won’t block PR merges; current branch protection on beta doesn’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.

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
Expand All @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 || true

Repository: 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:


🌐 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:


🌐 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:


🏁 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 || true

Repository: 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:


🌐 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:


🌐 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:


🏁 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" || true

Repository: analogjs/analog

Length of output: 1760


Conditionally apply --no-isolate only for matrix.engine == 'oxc', and tighten the workaround rationale.

  • .github/workflows/conformance.yml runs vitest ... --no-isolate for both engine: [ts, oxc], so TS is also losing isolation.
  • The inline comment’s description (“single shared worker / isolated forks pool”) is imprecise for what --no-isolate does; 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.

Suggested change
# 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.

152 changes: 152 additions & 0 deletions apps/docs-app/docs/guides/angular-compilation.md
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.
:::
5 changes: 5 additions & 0 deletions apps/docs-app/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ const sidebars = {
id: 'guides/libraries',
label: 'Building an Angular library',
},
{
type: 'doc',
id: 'guides/angular-compilation',
label: 'Angular Compilation',
},
{
type: 'doc',
id: 'guides/compatibility',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"@nx/vite": "22.7.5",
"@nx/vitest": "22.7.5",
"@nx/web": "22.7.5",
"@oxc-angular/vite": "^0.0.15",
"@oxc-angular/vite": "^0.0.31",
"@oxc-project/runtime": "^0.117.0",
"@playwright/test": "^1.58.2",
"@schematics/angular": "21.1.5",
Expand Down
21 changes: 17 additions & 4 deletions packages/platform/src/lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,23 @@ export interface Options {
*/
disableTypeChecking?: boolean;
/**
* Opt into the fast compile path. Skips Angular's template type-checking
* and routes compilation through an internal single-pass transform.
*/
fastCompile?: boolean;
* Opt into the fast compile path and select the engine that backs it. The
* fast path skips Angular's template type-checking and routes compilation
* through an internal single-pass transform.
* - `false` (default) / unset: use Angular's full compilation (with
* template type-checking).
* - `true` or `'ts'`: the in-process TS/OXC-AST compiler shipped with
* `@analogjs/vite-plugin-angular`.
* - `'oxc'`: experimental — route AOT and JIT component compilation through
* the native Rust pipeline from `@oxc-angular/vite` (must be installed as
* an optional peer dependency). Also unlocks OXC's `@ng/component` HMR
* contract, library linker (`linkAngularPackage`), and FESM build
* optimizer (`optimizeAngularPackage`).
*
* The OXC engine can also be selected with the `ANALOG_OXC=true` environment
* variable when this option is left unset (explicit values take precedence).
*/
fastCompile?: boolean | 'ts' | 'oxc';
/**
* Compilation output mode used when `fastCompile` is enabled.
* - `'full'` (default): Emit final Ivy definitions for application builds.
Expand Down
4 changes: 4 additions & 0 deletions packages/vite-plugin-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"peerDependencies": {
"@angular-devkit/build-angular": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
"@angular/build": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
"@oxc-angular/vite": "^0.0.31",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
Expand All @@ -35,6 +36,9 @@
"@angular/build": {
"optional": true
},
"@oxc-angular/vite": {
"optional": true
},
"vite": {
"optional": true
}
Expand Down
62 changes: 52 additions & 10 deletions packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
import { routerPlugin } from './router-plugin.js';
import { createHash } from 'node:crypto';
import { fastCompilePlugin } from './fast-compile-plugin.js';
import { oxcLinkerPlugin } from './compiler/oxc-linker-plugin.js';
import {
TS_EXT_REGEX,
createTsConfigGetter,
Expand Down Expand Up @@ -107,11 +108,21 @@ export interface PluginOptions {
disableTypeChecking?: boolean;
fileReplacements?: FileReplacement[];
/**
* Opt into the fast compile path. Skips Angular's template type-checking
* and routes compilation through an internal single-pass transform.
* Defaults to `false`.
* Opt into the fast compile path and select the engine that backs it. The
* fast path skips Angular's template type-checking and routes compilation
* through an internal single-pass transform.
* - `false` (default) / unset: use Angular's full compilation (with
* template type-checking).
* - `true` or `'ts'`: the in-process TS/OXC-AST compiler shipped with this
* package.
* - `'oxc'`: experimental — route component compilation through the native
* Rust pipeline from `@oxc-angular/vite` (must be installed as an optional
* peer dependency).
*
* The OXC engine can also be selected with the `ANALOG_OXC=true` environment
* variable when this option is left unset (explicit values take precedence).
*/
fastCompile?: boolean;
fastCompile?: boolean | 'ts' | 'oxc';
/**
* Compilation output mode used when `fastCompile` is enabled.
* - `'full'` (default): Emit final Ivy definitions for application builds.
Expand Down Expand Up @@ -149,6 +160,15 @@ export function angular(options?: PluginOptions): Plugin[] {
* Normalize plugin options so defaults
* are used for values not provided.
*/
// Resolve the consolidated `fastCompile` option (boolean | 'ts' | 'oxc')
// into an internal enabled flag + engine. `ANALOG_OXC=true` selects the OXC
// engine when the option is unset; explicit config takes precedence.
const oxcEngineFromEnv = process.env['ANALOG_OXC'] === 'true';
const fastCompileValue =
options?.fastCompile ?? (oxcEngineFromEnv ? 'oxc' : false);
const fastCompileEngine: 'ts' | 'oxc' =
fastCompileValue === 'oxc' ? 'oxc' : 'ts';

const pluginOptions = {
tsconfigGetter: createTsConfigGetter(options?.tsconfig),
workspaceRoot: options?.workspaceRoot ?? process.cwd(),
Expand All @@ -170,8 +190,9 @@ export function angular(options?: PluginOptions): Plugin[] {
fileReplacements: options?.fileReplacements ?? [],
useAngularCompilationAPI:
options?.experimental?.useAngularCompilationAPI ?? false,
fastCompile: options?.fastCompile ?? false,
fastCompile: fastCompileValue !== false,
fastCompileMode: options?.fastCompileMode ?? 'full',
fastCompileEngine,
};

let resolvedConfig: ResolvedConfig;
Expand Down Expand Up @@ -820,12 +841,34 @@ export function angular(options?: PluginOptions): Plugin[] {
isTest,
isAstroIntegration,
fastCompileMode: pluginOptions.fastCompileMode,
fastCompileEngine: pluginOptions.fastCompileEngine,
})
: angularPlugin();

// OXC engine only: link pre-compiled Angular libraries (`ɵɵngDeclare*`
// → `ɵɵdefine*`) using OXC's native Rust linker. Without this, those
// libraries fall back to runtime JIT linking which pulls
// `@angular/compiler` into the browser bundle. Skipped on the TS
// engine path, which has its own dts-reader covering the same need.
const linkerPlugin =
pluginOptions.fastCompile && pluginOptions.fastCompileEngine === 'oxc'
? oxcLinkerPlugin()
: (false as unknown as Plugin);

// Both engines use Analog's `JavaScriptTransformer`-backed optimizer.
// OXC's `optimizeAngularPackage` leaves `@angular/core/fesm2022/core.mjs`
// essentially untouched, so unused public re-exports (including the JIT
// runtime) survive tree-shaking and ~150KB extra ships to the client.
// Revisit `oxcOptimizerPlugin` once upstream closes the gap.
const optimizerPlugin = buildOptimizerPlugin({
supportedBrowsers: pluginOptions.supportedBrowsers,
jit,
});

return [
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
compilationPlugin,
linkerPlugin,
!pluginOptions.fastCompile &&
pluginOptions.liveReload &&
liveReloadPlugin({ classNames, fileEmitter }),
Expand All @@ -836,14 +879,13 @@ export function angular(options?: PluginOptions): Plugin[] {
jitPlugin({
inlineStylesExtension: pluginOptions.inlineStylesExtension,
})) as Plugin,
buildOptimizerPlugin({
supportedBrowsers: pluginOptions.supportedBrowsers,
jit,
}),
optimizerPlugin,
routerPlugin(),
angularFullVersion < 190004 && pendingTasksPlugin(),
nxFolderPlugin(),
].filter(Boolean) as Plugin[];
]
.flat()
.filter(Boolean) as Plugin[];

function findIncludes() {
const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
Expand Down
Loading
Loading