React Compiler の Rust 版が Vite にネイティブ統合 — Babel が抜けてビルドが 17 倍速くなった話

はじめに

React Compiler は、useMemo / useCallback / React.memo を人間が手で貼る作業をコンパイラに肩代わりさせる仕組みだ。1.0 が出て採用は進んだが、実運用ではひとつ引っかかりがあった。動かすには Babel が要る。Vite 側は JSX も TypeScript も Fast Refresh も、とっくに Rust 製の oxc に載せ替えていたのに、React Compiler のためだけに Babel をビルドパイプラインに戻す必要があった。

その最後の一本が抜けた。oxc チームが oxc-transform-react を公開し、@vitejs/plugin-react v6.1.0 が実験的サポートとして取り込んだ。設定は react({ compiler: true }) の一行。実測レポートでは、コンパイラ部分が 14.3 秒から 0.81 秒になったという数字が出ている。

この記事では、何がどう繋がって速くなったのか、Babel を実際に消せる条件は何か、そして「一度は Rolldown に入って、抜かれた」という経緯までを整理する。

何が出たのか:3 つのパッケージの関係

まず登場人物を整理する。混乱しやすいのは、React Compiler の Rust 移植が複数の場所で並行して進んでいたためだ。

React Compiler 本体は元々 Babel プラグイン(babel-plugin-react-compiler)として提供されていた。React チーム自身も 2026 年に入って Rust への移植を進めていたが、oxc チームは別のアプローチを取った。React Compiler を fork せず、oxc に vendoring する——つまり自前の AST に直接組み込む形にした。これにより、Babel AST への中間変換を挟まずに済む。

そこから 2 つの成果物が出ている。

  • oxlint の React Compiler ルール:コンパイラの検証パスをそのまま使い、Rules of React 違反を検出する 22 ルール。ESLint プリセットに対応し、大半は correctness 分類。
  • oxc-transform-react:自動メモ化の変換を行う Node.js ネイティブバインディング。2026 年 8 月 4 日に v0.0.1 が公開された。

そして 8 月 19 日、Boshen 氏による PR #1419 が @vitejs/plugin-react にマージされ、oxc v0.145.0 のリリースに続いて v6.1.0 が出た。ここで compiler オプションが追加されている。

重要なのは責務の持ち方だ。compiler: true を有効にすると、oxc-transform-react が React Compiler・TypeScript/JSX・Fast Refresh の 3 つをワンパスで処理し、Rolldown 側の組み込み React 変換は競合を避けるために無効化される。変換パスが 1 本に集約されるので、複数ツールが同じファイルを何度もパースし直す構図が消える。

なぜ速くなるのか:Babel が抜けるという意味

数字を先に置く。oxc 側の公表値は「Babel 版のおよそ 10 倍以上」で、1 ファイルあたり 100ms 前後かかっていたものが 10ms 前後になる、という粒度だ。master.dev の実測レポートではもう少し極端で、コンパイラ部分が 14.3 秒 → 0.81 秒(17.6 倍)、他のビルド処理も含めた全体では 22.1 秒 → 9.3 秒(2.4 倍)と報告されている。

全体で 2.4 倍に留まるのは当然で、コンパイラ以外の工程は元から Rust 製だからだ。逆に言えば、Babel がボトルネックとして残っていた分がまるごと消えたというのがこの変化の正体になる。

ここで効いているのは単純な言語差だけではない。Babel 版の構成では、Rolldown/oxc がパースした結果を Babel AST に変換し、React Compiler を通し、また戻す、という往復が発生していた。oxc に vendoring したことでこの中間変換が不要になり、パースが 1 回で済む。oxc は現行の transform が最初の Rust 移植版よりさらに 2 倍速いとしているが、その改善分もこの構造変更に負うところが大きい。

正しさの検証方法も付記しておく価値がある。oxc チームは 100 以上の大規模リポジトリ・10 万ファイル超に対して、最新の実験版 Babel 実装との出力差分を突き合わせて conformance を確認したとしている。コンパイラの挙動が変わると、メモ化が過剰にも過少にもなり得る領域なので、この規模の突き合わせがあるかどうかは採用判断に直結する。

バイナリサイズを巡る一度の撤退

この統合が「別パッケージ」になっている理由には経緯がある。

oxc は 2026 年 6 月にネイティブのビルド時 transform を一度 Rolldown 側にマージしたが、Rolldown のバイナリが約 17% 膨らんだことを理由に、Rolldown/Vite のメンテナが直後に取り外している。React Compiler を使わないユーザー全員に、React 専用のコンパイラのサイズを負担させる形になるためだ。

その後の最適化——Babel AST への中間変換の削除、正規表現エンジンの差し替え——でバイナリは 8.66 MiB から 3.97 MiB まで縮み、最終的に「optional peer dependency として切り出された別パッケージ」という形に落ち着いた。PR の記述もそのまま「React Compiler を別パッケージに保つことで、フレームワーク固有のコンパイラのサイズと複雑さを Rolldown に持ち込まずに済む」としている。

バンドラのコアに何を入れて何を入れないかという線引きの話で、結論としては使う人だけが 4 MiB を払う設計になった。oxc-transform-react を明示的に install しないと compiler: true が効かないのは、この判断の帰結だ。

エンジニアへの影響:今日から何が変わるか

導入手順は素直だ。Vite 8 以降 + @vitejs/plugin-react v6.1.0 以降で、

npm install -D oxc-transform-react
// vite.config.js
plugins: [react({ compiler: true })]

React Router の framework mode のように @vitejs/plugin-react を直接使わない構成では、@acusti/vite-plugin-react-compiler が代替として案内されている。

消せる依存は以下あたり。ここが実務上の一番の収穫で、@babel/preset-typescript まで含めて Babel 系がまとめて落ちる。

  • @rolldown/plugin-babel
  • vite-plugin-babel
  • babel-plugin-react-compiler
  • @babel/preset-typescript

残っている制約も押さえておきたい。master.dev のレポートでは、以下のパターンで依然としてコンパイラが bailout する(=最適化を諦めて素通しする)と報告されている。

  • try ブロック内の throw 文
  • 論理代入演算子(??= / &&= / ||=)

一方で、try/catch 内の条件分岐、ネストしたクロージャでの分割代入した props の再代入、計算されたオブジェクトキーは、最近の改善で扱えるようになっている。bailout はビルドエラーではなく「そのコンポーネントだけ最適化されない」形で現れるので、期待したメモ化が効いていない場合はこのリストを疑うのが早い。

そして最大の注意点として、この機能は experimental 扱いである。React Compiler と Fast Refresh はクライアント専用のままで、サーバー環境では同じパッケージが TypeScript/JSX 処理のみを担う。また Vite 本体の Discussion #22949 では、unplugin-oxc で JSX 変換を独自に差し込むと Vite の依存最適化パイプラインを迂回してしまい、React が ?v=hash 付きと無しの二重解決になって hook エラーを起こす、という失敗例が共有されている。プラグインを自前で組み替えず、公式の経路に乗せるのが現時点の正解だ。

まとめ

  • oxc が React Compiler を fork ではなく vendoring する形で取り込み、oxc-transform-react として公開した。
  • @vitejs/plugin-react v6.1.0 の compiler: true で有効化でき、React Compiler・TS/JSX・Fast Refresh をワンパス処理する。
  • 速度は公表値で Babel 比 10 倍以上、実測レポートではコンパイラ部分 17.6 倍・全体 2.4 倍。中間 AST 変換の消滅が効いている。
  • 一度 Rolldown 本体に入ったが約 17% のバイナリ増を理由に撤退し、3.97 MiB の optional 別パッケージとして再登場した。
  • throw-in-try と論理代入演算子は依然 bailout する。experimental であり、プラグイン構成の自作は依存二重解決を招く。

React まわりのツールチェインから Babel が抜ける流れは、これで実質的に最後の一本を残すのみだった箇所が埋まった形になる。experimental が取れるまでは本番投入を急ぐ理由はないが、CI のビルド時間が数十秒単位で効いている規模のプロジェクトなら、ブランチを切って compiler: true を測ってみる価値は十分にある。

ソース