Splits `oxc_react_compiler`'s public API into two phases so the transform rewrites the caller's program in place instead of returning a whole-program clone:
- `compile(&Program, &Semantic, &Allocator, PluginOptions) -> (Option<CompileOutput>, Diagnostics)` — read-only; `None` means no changes (or error / lint output mode).
- `CompileOutput::transform(self, &mut Program, &Allocator)` — substitutes the compiled functions into the program in place. `CompileOutput` owns only arena data (replacements + `ProgramContext`), so the caller drops its `Semantic` between the two calls, releasing the shared borrow that previously forced `ox_splice_program` to `clone_in_with_semantic_ids` the entire program.
- `lint` is unchanged and structurally never runs the transform phase; the oxlint rule needed no changes.
The old `transform` free function and `TransformResult` are removed; `oxc_transformer` no longer does the `*program = result.program.take()` swap and just applies the output to its `&mut Program`.
The 1806-fixture snapshot corpus is byte-identical, and the transform path drops one whole-program clone per compiled file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Borrow existing semantic data and caller allocator in React Compiler transform/lint paths to avoid hidden rebuild/allocation work.
Transform now returns the compiled program so callers can install it after the semantic borrow ends.
AI-assisted.
Slims down `oxc_react_compiler`'s `PluginOptions` and removes the now-dead timing infrastructure. Four related cleanups:
### Remove `source_code` from `PluginOptions`
It was redundant — `compile` in `lib.rs` always overwrote it with `program.source_text`, and no caller set it. The one consumer (`ProgramContext::new` in `program.rs`) now reads `program.source_text` directly (the oxc `Program` is already in scope there), and the threading block in `lib.rs` is gone.
### `default_plugin_options()` → `impl Default for PluginOptions`
Converted the free function into a manual `Default` impl (the defaults are non-trivial: `should_compile: true`, `target: "19"`, `flow_suppressions: true`, …). All callers now use `PluginOptions::default()`.
### Remove `profiling`
The field was always `false` in practice (no caller set it). Its sole consumer now passes `TimingData::new(false)` — which leads into…
### Remove the timing instrumentation entirely
With `profiling` gone, timing was pure, always-disabled instrumentation: every `context.timing.start/stop` was a no-op and the collected `Vec<TimingEntry>` was always empty and never read (`lib.rs` destructures `CompileResult` with `..`). Removed:
- `timing.rs` (the `TimingData`/`TimingEntry` module) and its `mod` declaration
- ~218 `context.timing.start/stop(...)` calls across `pipeline.rs`
- the `timing` field on `ProgramContext` and on both `CompileResult` variants, plus all initializers
### Verification
- `oxc_react_compiler`: builds, 15/15 tests pass.
- `oxc_linter`: `react_compiler` rule snapshot test passes — **snapshot unchanged** (diagnostics identical).
- `oxc_transformer` react_compiler integration: 2/2 pass; `oxc_benchmark` bench compiles.
- rustc warnings, clippy (`dev-no-debug-assertions`), and `cargo shear` all clean.
## Summary
`transform` now rewrites the program **in place** instead of returning a
new one, and `lint` stays **read-only** — aligning with how the rest of
the oxc transform pipeline mutates the AST.
- `transform(program: &mut Program, allocator, options)` writes the
memoized AST back into `program`. `TransformResult` drops its `program:
Option<Program>` field (and lifetime) and gains `changed: bool`.
- `lint(program: &Program, options)` is unchanged — borrowed, read-only.
- Both share a private `compile(&Program) -> (Option<Program>,
Diagnostics, Vec<LoggerEvent>)` pipeline. Keeping the semantic borrow
contained in that call is what lets `transform` do `*program = compiled`
afterwards — `SemanticBuilder::build` takes `&'a Program<'a>`, so an
inlined build would pin the borrow for the whole arena and block the
write.
- `transform_source` now returns `(Program, TransformResult)`.
## Callers updated
- `oxc_transformer::run_react_compiler` — the `*program = compiled` it
used to do now lives inside `transform`; it just checks `result.changed`
before rebuilding scoping.
- `examples/react_compiler.rs`,
`tasks/benchmark/benches/react_compiler.rs` — pass `&mut program`, gate
output on `result.changed`.
- lib.rs smoke tests — `transform_source(...).program.expect(...)` →
destructure `(program, result)` + `assert!(result.changed, ...)`.
## Verification
- `cargo test -p oxc_react_compiler` (38) and `oxc_transformer
--features react_compiler` (9) pass
- `cargo ck` (workspace, all-features/all-targets, `--locked`), clippy
(`dev-no-debug-assertions`), `cargo fmt --check` — clean
- `oxc_linter` (uses `lint`), benchmark target, and example all build
---
🤖 Generated with [Claude Code](https://claude.com/claude-code) —
AI-assisted, reviewed before submission (per the repo AI usage policy).
## What
Reworks the `oxc_react_compiler` public API:
- Renames the standalone `run` entrypoint to `transform`. It now returns a `TransformResult` instead of taking a `&mut errors` vec and rebuilding `Scoping` internally. Callers use `result.program` (`Some`/`None`) to decide whether to rebuild scoping — the `oxc::Compiler` callsite does exactly this.
- Splits `TransformResult`/`LintResult` diagnostics into separate `errors` and `warnings` (partitioned by `Severity`), and documents `errors` / `warnings` / `events`.
- Adds `#[derive(Default)]` to `TransformResult` for the bail-out paths.
- Inlines the former `transform_impl`; semantic is now built internally, after the cheap prefilter bail-outs (so non-React files skip semantic construction).
- Bumps the `forked_react_compiler` family `0.1.3` → `0.1.4`.
Example and benchmark updated to the new `transform(&program, &allocator, options)` signature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
The `Bench react_compiler` job on `main` panics:
```
thread 'main' panicked at oxc_index-5.0.0/src/indexing.rs:38:10:
index out of bounds: the len is 0 but the index is 2
```
[Failing
run.](https://github.com/oxc-project/oxc/actions/runs/27154333851/job/80153249638)
## Why
#23005 made the full `AstNodes` store opt-in via
`SemanticBuilder::with_build_nodes(true)` (default now off). The react
compiler reads `semantic.nodes()` in `convert_scope.rs`, so it needs the
store.
That PR updated every call site that feeds `transform` —
`transform_source`, `lint_source`, `run`, and the example — to enable
`with_build_nodes(true)` and `with_enum_eval(true)`, but missed this
benchmark. With nodes off, `semantic.nodes()` is an empty `IndexVec` and
the first `NodeId` access panics. PR CI didn't catch it because the
benchmark only runs on `main`.
## How
Match the other call sites: enable `with_build_nodes(true)` and
`with_enum_eval(true)` in the benchmark's `SemanticBuilder`.
Verified locally by building with `--features react_compiler` and
running the bench across all 5 inputs (RadixUIAdoptionSection.jsx,
react.development.js, App.tsx, binder.ts, kitchen-sink.tsx) — all
complete, no panic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes https://github.com/oxc-project/oxc/issues/10048
Integrates the Rust port of the React Compiler ([facebook/react#36173](https://github.com/facebook/react/pull/36173)) into oxc.
## Notes (resolved)
- Published crates can't reference Git URLs, so using this from Rolldown needs the React Compiler crates on crates.io. ✅ Published as a fork at https://crates.io/crates/forked_react_compiler; this PR depends on those `forked_react_compiler*` crates so people can get earlier access.
- The React Compiler crates had no license field. ✅ The published fork carries `license = "MIT"` (per the React repo's MIT license), so Cargo Deny and Security Analysis pass.
## Benchmark
**Wall-clock overhead vs plain transform** (release `transformSync`, same `jsx: automatic`, toggling only `reactCompiler`):
| fixture | without | with | overhead |
| --- | --- | --- | --- |
| RadixUIAdoptionSection.jsx (2.5 KiB) | 0.03 ms | 1.81 ms | +1.8 ms |
| excalidraw `App.tsx` (406 KiB) | 4.09 ms | 14.49 ms | +10.4 ms (3.5×) |
So roughly a fixed ~1.8 ms floor per file plus a per-size cost — about **3.5× the transform time** on a large real-world component.
## Binary size
Linking the React Compiler pulls the whole compiler pipeline (HIR, lowering, inference, SSA, optimization, reactive scopes, validation) plus the oxc⇄Babel AST conversion into the binding. Release build of the napi transform addon (`transform.darwin-arm64.node`, `--release`, stripped), darwin-arm64:
| build | size |
| --- | --- |
| baseline (`main`) | 3.51 MiB |
| with React Compiler | 8.66 MiB |
| **delta** | **+5.14 MiB (+146%, 2.46×)** |