Bun Integrates the React Compiler Directly Into Its Bundler, Roughly 20x Faster Than the Babel Plugin

Bun Integrates the React Compiler Directly Into Its Bundler, Roughly 20x Faster Than the Babel Plugin

lschvn

Bun PR #32504, merged on June 20, 2026, turns the upstream React Compiler Rust port into a built-in bun build transform. Turn it on with --react-compiler from the CLI or reactCompiler: true on Bun.build, and Bun will memoize your .jsx and .tsx components and hooks during the build, with no Babel plugin, no config files, and nothing to install. The feature is off by default and marked experimental in both the type definitions and the bundler docs.

This is the first bundler to ship the React Compiler as a native transform. Vite, Next.js with Turbopack, webpack, and Rsbuild all run it through a Babel or SWC plugin today. Bun's path skips that intermediate entirely.

What landed

The integration closes issue #24356, the long-standing feature request for first-class React Compiler support in the bundler, and replaces an earlier PR #31785 that depended on an oxc_react_compiler crate that did not exist at the time. The new PR ports the compiler's Rust workspace directly from facebook/react rather than going through Oxc, which is why the upstream PR description in facebook/react#36173 explicitly invited bundler integrations via the react_compiler_oxc adapter and Bun took a different path.

A follow-up PR #32545 shipped the same day fixes three review comments from the merged PR, including a subtle bug where reactCompilerOutputMode: 'client' would silently enable the compiler even when reactCompiler: false was set. The output mode is now stored separately and only applied when the compiler is on, matching the documented behavior in bun.d.ts.

The architecture: Bun AST straight to HIR

The compiler lives in src/react_compiler/, a single ~62k LOC crate. The bulk of it is a byte-for-byte port of the upstream Rust workspace, with import paths rewritten and the serde and serde_json derives that Bun does not need stripped. The upstream workspace crates that are ported whole: hir/, ssa/, inference/, typeinference/, optimization/, validation/, reactive_scopes/, diagnostics/, and utils/. Hot-path data structures were densified: HashMap<SmallId, _> becomes Vec<_>, HashSet<ValueReason> becomes EnumSet (u16), and points-to sets become SmallVec<[_; 4]>. The IndexMap and IndexSet API is shimmed over arena-backed bun_collections::ArrayHashMap.

The four layers that touch the AST (lowering, codegen, pipeline, and the program/imports glue) are reimplemented against bun_ast, with the type-mapping table in src/react_compiler/DESIGN.md documenting how Bun's AST nodes correspond to the Babel-shaped AST the compiler expects.

The compile hook fires inside visit_stmts(FnBody), between its visit phase and its inline-mangle phase. Candidate detection on S::Function, S::Local, S::ExportDefault, and S::Expr records the binding Ref and the memo/forwardRef wrapper bit; visit_func and arrow-visit copy the function's args, flags, and locations into a Copy PendingCompile struct; the hook calls maybe_compile_pending, which constructs a stack-local G::Fn and runs maybe_compile_node. The compiled body lands in the live stmts buffer so the existing mangle phase runs on it. New arguments and flags flow back through a single CompileResult field. No raw pointers, no extra pass; the non-RC path adds one is_some() check per top-level declaration.

The compiler also honors // eslint-disable react-hooks/* suppressions. The lexer runs one substring check per comment, gated on the feature flag, and propagates the suppression as a flag bit on G::Fn and E::Arrow; the compiler skips any function carrying it.

The numbers

The PR ships a benchmark on a large React codebase (around 860 compiled components, 1400 memo slots). The same code, on the same machine:

Wall timevs Babel plugin
Baseline (reactCompiler: false)394 ms-
reactCompiler: true465 ms (1.18x baseline)~20x faster than Babel
Babel plugin (same input)9.15 s1x

The full --compile standalone executable build, which bundles everything plus the React Compiler pass, runs in 3.62 s with the Rust port versus 13.04 s with the Babel plugin, a 3.6x end-to-end speedup.

These are not synthetic micro-benchmarks. The codebase is real, the components compile to real _c(N) memoization calls with $[0] !== label cache checks, and the react/compiler-runtime import Bun injects resolves against the React 19+ install that ships with the app. Bun notes that the baseline-with-RC overhead (394 ms to 465 ms, ~18%) is from HIR construction and SSA pass; the rest of the bundler (parser, mangle, minify) is unchanged.

What the API looks like

CLI:

bun build ./app.tsx --react-compiler --target browser

Bun.build:

await Bun.build({
  entrypoints: ["./app.tsx"],
  reactCompiler: true,
  // reactCompilerOutputMode: "client", // default for browser target
  // reactCompilerOutputMode: "ssr",   // default for bun/node target
  target: "browser",
});

reactCompilerOutputMode defaults to "client" when target is "browser" and to "ssr" when target is "bun" or "node". SSR mode skips the useMemoCache runtime so server-rendered output stays cache-friendly across requests. compilationMode: "infer" semantics carry over from the upstream compiler, so only components and hooks are compiled; "use no memo" directives are honored, and node_modules is skipped.

What this means for the bundler race

This is the first time the Rust port of the React Compiler has shipped as a build-time transform rather than as a library other tools have to plug into. The Oxc v0.135 integration in mid-June added the compiler as a Rust crate you could call into, but the only bundler to actually wire it up since is Bun. Vite 8 and Vite 8.1 still go through babel-plugin-react-compiler; Next.js with Turbopack uses the SWC port; webpack uses Babel. Bun's choice to port upstream directly into its own AST layer is a deliberate trade: it skips the cross-AST conversion cost and the dependency surface, at the price of having to re-sync against facebook/react periodically.

The maintenance path is wired up. scripts/sync-react-compiler.sh sparse-fetches facebook/react and prints a per-file diff between src/react_compiler/UPSTREAM_PORTED and upstream tip, grouped into whole-crate ports that apply mechanically and AST-boundary ports that re-port via the type-mapping table. --fixtures re-syncs the test corpus. As long as the upstream API stays Babel-AST-shaped, the cost of tracking the port is roughly proportional to how often upstream touches the boundary layers.

Where to watch

Three signals worth tracking over the next few weeks:

  1. The Bun v1.3.15 release notes when they land, which should bundle PR #32504 plus the follow-up and promote the feature from bun build experimental to a stable flag.
  2. The Oxc react_compiler_oxc adapter landing as a stable crate in an Oxc release, which is the path Vite and Rolldown will most likely take to get the same perf numbers without porting upstream.
  3. Any change in the upstream React Compiler's "public API" from "Babel AST + scope info" to a more bundler-native shape, which would let Oxc (and through it Vite, Next.js, Rsbuild) skip their own adapter crates entirely.

Frequently Asked Questions

Related articles

More coverage with overlapping topics and tags.

Oxlint v1.71 and Oxfmt v0.56 Ship the v0.137 Crates Wins, Land 18 New Linter Rules, and Tame React Lifecycle Traversal with a Streamed-Iterator Refactor
tooling

Oxlint v1.71 and Oxfmt v0.56 Ship the v0.137 Crates Wins, Land 18 New Linter Rules, and Tame React Lifecycle Traversal with a Streamed-Iterator Refactor

Oxlint apps_v1.71.0 and oxfmt apps_v0.56.0, both published on 2026-06-22, close out the v0.137 crates cycle. Oxlint v1.71 picks up the new treeshaking pure typed arrays minifier pass (#23469), the prefer-query-selector compound selector fix, 28 lint bug fixes (most of them Yunfei He's fixer-rewrite work), 13 performance entries anchored on a bucketed rule dispatch refactor (#23450, #23452, #23482-#23486, #23489, #23492), and the oxlint Tokio-on-LSP-only startup (#23447). Oxfmt v0.56.0 ships 9 bug fixes (CRLF normalization for `// @ts-ignore` suppressed text in #23701 and #23702, the member-chain panic fix #23698, the default-export-with-type-cast preservation #23697) and 3 formatter performance entries that remove arena copies on bigint, numeric-literal, and string-literal text. v1.71 is the first apps release since v1.70.0 on 2026-06-15, and the last one before the v0.138 crates release that will land on Monday 2026-06-29.
Oxc v0.137 Teaches the Minifier to Treeshake Pure Typed Arrays and Set/Map Literals, Lands an Incremental Scoping Refresh, and Fixes a React Compiler Edge Case
tooling

Oxc v0.137 Teaches the Minifier to Treeshake Pure Typed Arrays and Set/Map Literals, Lands an Incremental Scoping Refresh, and Fixes a React Compiler Edge Case

Oxc release crates_v0.137.0, published on 2026-06-18, ships two new minifier passes (treeshake pure typed arrays and Set/Map array literals via #23469, and inline const values for read-only vars via #22593), a long-running incremental scoping refresh that retires the LiveUsageCollector collector entirely (#23197), a friendly parser error for adjacent JSX elements (#23378), a React Compiler bug fix for computed-key imports (#23586), and two breaking changes to the ESTree config API (#23573, #23574). The minifier pass list also gets a Proxy-aware object-introspection fix (#23483) and a new Map/WeakSet/WeakMap preservation rule for string arguments (#23470). v0.137 is the first crates release since v0.135 on 2026-06-08 and the second since Bun's native React Compiler integration landed on 2026-06-20.
Astro 7.0.0-beta.6 Stabilizes Route Caching and Promotes JSX Whitespace Compression to Default
tooling

Astro 7.0.0-beta.6 Stabilizes Route Caching and Promotes JSX Whitespace Compression to Default

Astro 7.0.0-beta.6 (June 19, 2026) promotes the experimental route caching API to top-level stable, dropping the experimental.cache and experimental.routeRules flags in favor of a new top-level cache config and a cache helper. Beta.5 (June 18) made 'jsx' the default compressHTML mode, changing the rendered output of every site that relied on the legacy whitespace preservation. Beta.6 also pulls in @astrojs/markdown-satteri 0.3.1-beta.2.

Comments

Log in Log in to join the conversation.

No comments yet. Be the first to share your thoughts.