Skip to content

[compiler] Port React Compiler to Rust#36173

Merged
mvitousek merged 435 commits into
react:mainfrom
josephsavona:rust-research
Jun 9, 2026
Merged

[compiler] Port React Compiler to Rust#36173
mvitousek merged 435 commits into
react:mainfrom
josephsavona:rust-research

Conversation

@josephsavona

@josephsavona josephsavona commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

This is an experimental, work-in-progress port of React Compiler to Rust. Key points:

  • Work-in-progress - we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development.
  • No builds available yet, you'll have to do some hacking if you want to try this.
  • All fixtures pass, no known gaps but there may be lurking bugs.
  • The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI. I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level.
  • The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out. We use a Rust representation of the Babel AST as our "public API", as it were, and then each integration (Babel, OXC, SWC) converts to/from their native representation. For now integrations must also provide scope information - in the future React Compiler may compute bindings and references itself from the AST.
  • Internally, the Rust version uses the same architecture as the TypeScript version. The compiler converts from the AST into our own intermediate representation (HIR, short for High-level Intermediate Representation) which uses a control-flow graph (CFG) and single-static assignment (SSA). We go through the same series of passes, with the same overall algorithms. It's very much a pass-by-pass port. The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system.
  • Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, itt does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster.
  • There are 3 integrations right now: an alternative Babel plugin (which will eventually get removed as we integrate into babel-plugin-react-compiler), and examples of what OXC and SWC integrations could look like (see react_compiler_oxc and react_compiler_swc crates).

correctness:

  • all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors.
  • all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids)
  • The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration.

development:

  • yarn snap --rust is the primary test suite, testing that we error or compile as expected. It does not test the inner state of the compiler along the way, though, making it less suitable for finding subtle logic gaps btw the TS and Rust versions. It's also Babel based, making it less easy to test OXC and SWC integrations.
  • compiler/scripts/test-e2e.sh is an e2e test of all 3 variants (babel wrapper around Rust, OXC/SWC integrations) against the TS implementation. This does a partial comparison, focused on final output code only (doesn't test error details etc). Useful for getting the swc and oxc integrations closer to parity.
  • compiler/script/test-rust-port.sh does detailed testing of the internal compiler state after each pass, in addition to checking the final output code. This is the key script used to port the compiler, ensuring not just that the output was the same but that each pass was capturing all the same detail. This script can be pointed at any directory of JS files, which we expect to use for internal testing at Meta.

For Partners

We're excited to partner with teams to integrate the Rust version of React Compiler into other tools, like OXC and SWC. If you're interested in working with us on this, the best place to start is by taking a look at the react_compiler_swc and react_compiler_oxc crates. These give you an idea of the API shape that we're thinking of.

Note that the conversion from any AST into our HIR is complex, and we can only maintain one version. Hence we've aligned on using a Babel-like AST as our public API. Another key point is that we don't yet implement our own scope analysis (since the TS version of the compiler relied on Babel's scope analysis), so for now we require that the scope data be serialized. It's a denormalized graph, and some metadata has to be stored to associate nodes with scopes. We're open to feedback about the AST and scope representation - we iterated a bit just to get things to work, but it can be more optimal.

Key changes that we are considering:

  • Currently the compiler returns Option<Program>, which is Some if anything changed. This requires replacing the entire program. We plan to change this to return a series of patches to apply, in a form that is reasonably usable and efficient for all the integrations we care about (Babel, OXC, SWC, etc).
  • The Rust representation of the Babel AST is fine enough, but we could make it more optimal by doing arena allocation. We also plan to change the string representation to smol_str.
  • The scope representation, and association of data btw AST and scope, is very much a first pass approach that is good enough. We expect to implement our own scope resolution, though, so we hopefully won't need to iterate on the scope representation and can just throw it away.

In terms of the shape of the integration, we anticipate that each integration would have the following:

  • Implementor repo (OXC, SWC, etc): lightweight code transform and lint pipeline integration that delegates to crates/react_compiler_<name> from our repo
  • Our repo: one crate per implementor, eg react_compiler_swc, react_compiler_oxc, where most of the logic lives.

This setup lets us make changes to the integration layer easily within our repo. Feedback appreciated!

Joe Savona added 30 commits March 21, 2026 11:17
…s in reactive printer

Add HirFunctionFormatter callback to reactive DebugPrinter so FunctionExpression
and ObjectMethod values can print their inner HIR functions with full detail.
Bridge debug_print.rs formatting into the reactive printer via format_hir_function_into.
Remove blank line output for unprinted outlined functions that caused
Environment section misalignment. 1285/1717 fixtures now pass.
…unction value blocks

Port the TS logic that converts StoreLocal to LoadLocal when the last instruction
of a value block stores to an unnamed temporary. This fixes identifier/place
mismatches in the reactive function output. 1459/1717 fixtures now pass.
In BuildReactiveFunction, for-loops should use the update block as the continue
target when present, falling back to the test block. Matches TS
terminal.update ?? terminal.test pattern.
BuildReactiveFunction is implemented with 1458/1717 fixtures passing (85%).
Major fixes to match the TypeScript BuildReactiveFunction behavior:

- Add valueBlockResultToSequence for for/for-of/for-in init and for-of test values,
  which wraps value block results in SequenceExpressions with proper lvalue assignment
- Fix for-of continue_block to use init (not test), matching TS scheduleLoop call
- Add reachable() checks for if, switch, while, and label terminal fallthroughs
- Add loopId checks for all loop types (do-while, while, for, for-of, for-in) to
  verify loop blocks aren't already scheduled before traversal
- Add alternate != fallthrough check for if terminals (matching TS branch semantics)
- Fix switch case processing order to reverse (matching TS reverse-iterate-then-reverse)
- Fix switch to skip already-scheduled cases instead of pushing None blocks
- Fix value block catch-all to not propagate parent fallthrough (TS passes null)
- Clean up dead code in value block catch-all

Pass rate: 1635/1717 (95.2%). Remaining 82 failures are all earlier-pass issues.
Ported 15 reactive passes and visitor/transform infrastructure from TypeScript to Rust.
Includes assertWellFormedBreakTargets, pruneUnusedLabels, assertScopeInstructionsWithinScopes,
pruneNonEscapingScopes, pruneNonReactiveDependencies, pruneUnusedScopes,
mergeReactiveScopesThatInvalidateTogether, pruneAlwaysInvalidatingScopes, propagateEarlyReturns,
pruneUnusedLValues, promoteUsedTemporaries, extractScopeDeclarationsFromDestructuring,
stabilizeBlockIds, renameVariables, and pruneHoistedContexts. 1603/1717 tests passing (93.4%).
…-port.ts

The .replace(/\(generated\)/g, '(none)') normalization was effectively a no-op:
both TS and Rust event items go through the same formatLoc in the test harness,
producing identical (generated) strings. The HIR debug printers output "generated"
without parentheses, so the regex never matched HIR output either.
Reorder the 4 create_temporary_place_id calls in apply_early_return_to_scope
to match the TypeScript allocation order (sentinelTemp first, then symbolTemp,
forTemp, argTemp). The Rust port had them in a different order, causing
IdentifierIds to be assigned differently and producing 33 test divergences
in PropagateEarlyReturns output.
…S behavior

In TypeScript, `buildReverseGraph` (Dominator.ts:237) calls `fn.env.nextBlockId`
to create a synthetic exit node, which increments the block ID counter as a
side-effect. The Rust port reads `env.next_block_id_counter` without incrementing.

This causes block ID offsets: for a simple function, TS allocates 3 extra block
IDs (one each from ValidateHooksUsage, ValidateNoSetStateInRender, and
InferReactivePlaces) that Rust doesn't, causing all subsequent block IDs to
differ by 3.

Fix by changing the 3 callers to use `env.next_block_id().0` instead of
`env.next_block_id_counter`, consuming the ID to match TS behavior. This
reduces block ID divergences from ~1505 to ~117 fixtures (remaining divergences
are from recursive dominator calls within inner function validation).
…ew docs

Aggregate top issues from ~95 per-file reviews into 20260321-summary.md.
Key findings: ~55 panic!() calls that should be Err(...), type inference
logic bugs, severely compressed validation passes, weakened SSA invariants,
and JS semantics divergences in ConstantPropagation. Removes stale
aggregated summary docs (SUMMARY.md, README.md, etc.) while keeping
per-file reviews.
…re guidelines

Corrected several recommendations that were inconsistent with rust-port-architecture.md:
removed "at minimum panic!()" as acceptable for invariants (must be Err), marked tryRecord
as unnecessary in Rust since Result handles the concern more cleanly, fixed incorrect
claim that obj.class is invalid JS, and clarified that invariant violations must propagate
via Err rather than accumulate on env.
…eps, names scope, unify shapes, phi/cycle errors

Fix 5 bugs in InferTypes:
- 2a: Resolve types for captured context variables in apply phase (FunctionExpression/ObjectMethod)
- 2b: Resolve types for StartMemoize deps with NamedLocal kind
- 2d: Merge unify/unify_with_shapes so shapes are always available for property resolution
- 3a: Return Err(CompilerDiagnostic) for empty phi operands and cycle detection instead of silent return
Also updated pipeline.rs to handle the new Result return type.

Note: Bug 2c (shared names map) was already correct — inner functions use a fresh HashMap.
…on-null assertion

Changed unwrap_or(0) to .expect() for unsealed_preds lookup. TS uses a
non-null assertion (!) which maps to unwrap/panic per the architecture guide.
Silently defaulting to 0 could produce incorrect SSA IDs.
…ThatInvalidateTogether

Changed 'while index <= entry.to.saturating_sub(1)' to 'while index < entry.to'
to match TS semantics. The old code would incorrectly process index 0 when
entry.to was 0 (saturating_sub(1) returns 0, and 0 <= 0 is true).
…and number formatting

- Added 'delete' and 'await' to is_reserved_word (6a)
- Changed integer overflow guard from n.abs() < 1e20 to n.abs() < (i64::MAX as f64)
  to prevent potential issues with large integers near the threshold (6c)
- js_to_number already handles empty/whitespace strings correctly (6b was already fixed)
…ompilationMode and PanicThreshold

Created CompilationMode (Infer/Annotation/All) and PanicThreshold
(AllErrors/CriticalErrors/None) enums with serde support. Updated all
string comparisons in program.rs to use enum pattern matching.
…tch TS non-null assertion"

This reverts commit e3c80a2.
…ms for CompilationMode and PanicThreshold"

This reverts commit 88bf21f.
Mark completed items (2a-2d, 3a, 5b, 6a-6c, 7a-7c), note reverted items
(5c plugin enums broke serde, 8b enter_ssa fallback was correct), and
update remaining work items with findings from implementation.
… and consolidate pipeline error handling

Converted all CompilerError.invariant() and CompilerError.throwTodo() panics to
Err(CompilerDiagnostic) returns across 29 files, matching the architecture guide.
Added From<CompilerDiagnostic> for CompilerError impl to enable clean ? propagation,
replacing 17 verbose .map_err() blocks in pipeline.rs. Restored weakened SSA invariant
checks in rewrite_instruction_kinds_based_on_reassignment.rs.
…flatten(), convert remaining assert! calls

Replaced .ok().flatten() with ? in callers that return Result to properly
propagate invariant errors from environment shape resolution. Converted 10
remaining assert!/assert_eq! calls in build_reactive_function.rs to
Err(CompilerDiagnostic) returns. Simplified lower_expression's function
lowering to use .expect() since the error path is unreachable.
… Compiler

Copies the full react_compiler_oxc crate. Includes OXC 0.121 AST conversion,
reverse conversion, scope handling, prefilter, and diagnostics.
… Compiler

Copies the full react_compiler_swc crate. Includes SWC AST conversion,
reverse conversion, scope handling, prefilter, diagnostics, and integration tests.
Copies codegen_reactive_function.rs (~2800 lines) from the prior working branch.
Converts ReactiveFunction tree back into Babel-compatible AST with memoization
(useMemoCache) wired in. Includes pruneHoistedContexts fix for inner functions.
Connects codegen_reactive_function to the compilation pipeline:
- Added codegen module and pub use to reactive_scopes lib.rs
- Added react_compiler_ast dependency to reactive_scopes Cargo.toml
- Updated pipeline.rs to call codegen_function after PruneHoistedContexts
- Mapped codegen results (memo stats, outlined functions) to CodegenFunction
- Fixed build_reactive_function calls to handle Result return type
Extend the Rust port test script to capture and compare the final JavaScript
code produced by each compiler's Babel plugin, in addition to the existing
debug log entry comparison. The code is formatted with prettier before diffing.
Results are reported separately with their own pass/fail counts and diff output.
Add react_compiler_e2e_cli binary crate for testing SWC and OXC frontends
via stdin/stdout, codegen helpers (emit functions) to both react_compiler_swc
and react_compiler_oxc, and a test-e2e.ts orchestrator that compares output
from all 3 Rust frontends (Babel/NAPI, SWC, OXC) against the TS baseline.
@chenmijiang

Copy link
Copy Markdown

cool

kdy1 added a commit to swc-project/swc that referenced this pull request Jun 15, 2026
**Description:**

Add experimental SWC support for the Rust React Compiler from
react/react#36173.

This adds the `swc_ecma_react_compiler` bridge, SWC <-> React Compiler
AST/scope conversion, `.swcrc` `jsc.transform.reactCompiler`
configuration, diagnostics forwarding, JS/WASM option types, and tests
ported from the upstream SWC integration.

**TODO:**

- Wait for the React Compiler Rust crates to be published, then replace
the temporary git dependencies with published crate versions.


**Related issue:**
- react/react#36173

---------

Co-authored-by: DongYun Kang <[email protected]>
robobun added a commit to oven-sh/bun that referenced this pull request Jun 15, 2026
Adds experimental React Compiler support to the bundler, wired as a
source-to-source pre-parse pass in ParseTask. Enable with
`bun build --react-compiler` or `Bun.build({ reactCompiler: true })` —
no Babel plugin or extra installs; compiled output imports
react/compiler-runtime (ships with react >= 19).

The compiler is the Rust port from react/react#36173, vendored at a
pinned commit via the lolhtml-style fetch-only dep
(scripts/build/deps/react-compiler.ts) and compiled into libbun_rust.a
as path deps of the new bun_react_compiler crate. The wrapper parses
with oxc, pre-scans for syntax the vendored AST converter cannot handle
yet (its todo!() arms would abort under panic=abort), runs the compiler
with compilationMode: infer / panicThreshold: none semantics, and falls
back to the original source on any bailout. node_modules files are
skipped, "use no memo" is honored.

A patch disables oxc_codegen's default sourcemap feature: the optional
oxc_sourcemap dep declares crate-type cdylib, which fails to link under
bun's static-relocation rustflags (same class of fix as lolhtml's
rlib-only patch).

The react crates pull serde_json into workspace subgraphs that never
had it, and serde_json's impl PartialEq<Value> for {u16,i32,...} makes
`errno != E::EEXIST as _`-style comparisons ambiguous. Fixed the nine
latent sites (bun_install, node_fs, win_watcher, dns options) to the
canonical `err.get_errno() == E::X` idiom.
Dunqing added a commit to Dunqing/react that referenced this pull request Jun 17, 2026
These 6 .js (+ .expect.md) were introduced only by the Rust-port commit
03e7755 (react#36173) and are absent from its parent — they are HIR-diff
categorization probes (headers: "HIR Pattern: LOC_DIFF (45 files, 13%)",
"Round 2/3", "IDENTIFIER_DIFF", "NUMERIC_FORMAT_DIFF") committed into the
fixtures dir by mistake. They test no React semantics and only inflate the
failing-fixture count with non-bugs. Removed: hir_loc_diff, round2_loc_diff,
todo-hir_numeric_format, todo-pattern1b_type_to_primitive,
todo-hir_identifier_diff, todo-round3_promote_used_temps.
Corpus 1803 -> 1797.
@javache

javache commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

I think this drops some details on error messages

Before:

Error: Found extra effect dependencies

Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.

  3 |             useEffect(() => {
  4 |               console.log(propA);
> 5 |             }, [propA, propB]);
    |                        ^^^^^ Unnecessary dependency `propB`
  6 |           }
  7 |         `

After:

[ReactCompilerError] Found extra effect dependencies·
    Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.

javache added a commit to javache/react that referenced this pull request Jun 29, 2026
The Rust port (react#36173) changed CompileError events to carry plain
serialized detail objects instead of CompilerError class instances. The
ESLint integrations previously called detail.printErrorMessage(source,
{eslint: true}), which printed the source code frame(s) and location for
each error detail. The replacement printErrorMessage() only emitted the
reason and description, so code frames were dropped from lint output.

Restore the per-detail printing in both ESLint integrations by exporting
printCodeFrame from CompilerError and reusing it. The detail loop handles
both detail shapes that flow through LoggerEvents: a `details` array
(CompilerDiagnostic and the Rust compiler) and a legacy flat `loc`
(deprecated CompilerErrorDetail), so it works with the Rust compiler as
well as the TypeScript one.
javache added a commit that referenced this pull request Jun 30, 2026
)

## Summary

The Rust port (#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.

This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.

This PR restores the previous behaviour:

- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
  - a legacy flat `loc` (deprecated `CompilerErrorDetail`).

`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.

## Test plan

- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.
github-actions Bot pushed a commit that referenced this pull request Jun 30, 2026
)

## Summary

The Rust port (#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.

This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.

This PR restores the previous behaviour:

- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
  - a legacy flat `loc` (deprecated `CompilerErrorDetail`).

`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.

## Test plan

- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.

DiffTrain build for [9c1f097](9c1f097)
github-actions Bot pushed a commit that referenced this pull request Jun 30, 2026
)

## Summary

The Rust port (#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.

This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.

This PR restores the previous behaviour:

- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
  - a legacy flat `loc` (deprecated `CompilerErrorDetail`).

`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.

## Test plan

- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.

DiffTrain build for [9c1f097](9c1f097)
github-actions Bot pushed a commit to code/lib-react that referenced this pull request Jul 3, 2026
…ct#36901)

## Summary

The Rust port (react#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.

This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.

This PR restores the previous behaviour:

- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
  - a legacy flat `loc` (deprecated `CompilerErrorDetail`).

`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.

## Test plan

- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.

DiffTrain build for [9c1f097](react@9c1f097)
github-actions Bot pushed a commit to code/lib-react that referenced this pull request Jul 3, 2026
…ct#36901)

## Summary

The Rust port (react#36173) changed `CompileError` `LoggerEvent`s to carry
plain serialized detail objects instead of
`CompilerError`/`CompilerDiagnostic` class instances. As a result, the
ESLint integrations could no longer call
`detail.printErrorMessage(source, {eslint: true})` and were given a
replacement `printErrorMessage()` helper that only emitted the `reason`
and `description`.

This regressed error printing: the **source code frame(s) and
`file:line:column` location** that used to appear for each error detail
were dropped from lint output.

This PR restores the previous behaviour:

- Export `printCodeFrame` from `CompilerError` and reuse it from both
ESLint integrations instead of duplicating it.
- Rebuild the full message (reason, description, per-detail code frames,
and hints) in `printErrorMessage`.
- Handle **both** detail shapes that flow through `LoggerEvent`s:
- a `details` array (`CompilerDiagnostic` and the **Rust** compiler),
and
  - a legacy flat `loc` (deprecated `CompilerErrorDetail`).

`formatDetailForLogging` emits one or the other, so the previous
unconditional iteration over `error.details` would have thrown
`TypeError: not iterable` on the flat-`loc` path. Normalizing to a list
fixes that and keeps the code working with both the TypeScript and Rust
compilers.

## Test plan

- `tsc --noEmit` on both ESLint packages is clean (no new errors vs.
baseline).
- Verified the detail loop handles the Rust compiler's `details` array
shape and the legacy flat `loc` shape.

DiffTrain build for [9c1f097](react@9c1f097)
camc314 pushed a commit to oxc-project/oxc that referenced this pull request Jul 3, 2026
…22942)

Closes #10048

Integrates the Rust port of the React Compiler ([react/react#36173](react/react#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×)** |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.