Buns PR #32504, am 20. Juni 2026 gemergt, macht den Upstream-Rust-Port des React Compilers zu einer eingebauten bun build-Transformation. Schalten Sie sie mit --react-compiler auf der CLI oder reactCompiler: true auf Bun.build ein, und Bun memoisiert Ihre .jsx- und .tsx-Komponenten und -Hooks während des Builds, ohne Babel-Plugin, ohne Konfigurationsdateien und ohne etwas zu installieren. Das Feature ist standardmäßig deaktiviert und sowohl in den Typdefinitionen als auch in der Bundler-Dokumentation als experimentell markiert.
Dies ist der erste Bundler, der den React Compiler als native Transformation ausliefert. Vite, Next.js mit Turbopack, webpack und Rsbuild führen ihn heute alle über ein Babel- oder SWC-Plugin aus. Buns Weg überspringt diese Zwischenstation vollständig.
Was geliefert wurde
Die Integration schließt Issue #24356, den langjährigen Feature-Wunsch nach erstklassigem React-Compiler-Support im Bundler, und ersetzt eine frühere PR #31785, die von einer oxc_react_compiler-Crate abhing, die es zu dem Zeitpunkt nicht gab. Die neue PR portiert den Rust-Workspace des Compilers direkt aus facebook/react, statt den Weg über Oxc zu gehen, daher lud die Upstream-PR-Beschreibung in facebook/react#36173 explizit zu Bundler-Integrationen über den react_compiler_oxc-Adapter ein, und Bun wählte einen anderen Weg.
Eine am selben Tag ausgelieferte Folge-PR #32545 behebt drei Review-Kommentare aus der gemergten PR, darunter einen subtilen Bug, bei dem reactCompilerOutputMode: 'client' den Compiler stillschweigend aktivierte, obwohl reactCompiler: false gesetzt war. Der Ausgabemodus wird nun separat gespeichert und nur angewendet, wenn der Compiler aktiv ist, was zum dokumentierten Verhalten in bun.d.ts passt.
Die Architektur: Bun-AST direkt in HIR
Der Compiler lebt in src/react_compiler/, einer einzelnen Crate mit rund 62k LOC. Der Hauptteil ist ein Byte-für-Byte-Port des Upstream-Rust-Workspace, mit umgeschriebenen Import-Pfaden und ohne die serde- und serde_json-Derives, die Bun nicht braucht. Die Crates aus dem Upstream, die vollständig portiert werden: hir/, ssa/, inference/, typeinference/, optimization/, validation/, reactive_scopes/, diagnostics/ und utils/. Die Datenstrukturen auf dem Hot Path wurden verdichtet: HashMap<SmallId, _> wird zu Vec<_>, HashSet<ValueReason> wird zu EnumSet (u16), und Points-to-Sets werden zu SmallVec<[_; 4]>. Die IndexMap- und IndexSet-API ist über arena-basiertes bun_collections::ArrayHashMap überbrückt.
Die vier Schichten, die den AST berühren (Lowering, Codegen, Pipeline und der Program/Imports-Kleber), sind gegen bun_ast neu implementiert, mit der Typ-Mapping-Tabelle in src/react_compiler/DESIGN.md, die dokumentiert, wie Buns AST-Knoten dem Babel-förmigen AST entsprechen, den der Compiler erwartet.
Der Compile-Hook feuert in visit_stmts(FnBody), zwischen dessen Visit-Phase und der Inline-Mangle-Phase. Die Kandidatenerkennung auf S::Function, S::Local, S::ExportDefault und S::Expr zeichnet die Ref des Bindings und das memo/forwardRef-Wrapper-Bit auf; visit_func und der Arrow-Visit kopieren die Argumente, Flags und Locations der Funktion in eine Copy-PendingCompile-Struktur; der Hook ruft maybe_compile_pending auf, das ein stack-lokales G::Fn baut und maybe_compile_node ausführt. Der kompilierte Body landet im laufenden stmts-Buffer, sodass die bestehende Mangle-Phase darauf läuft. Neue Argumente und Flags fließen über ein einziges CompileResult-Feld zurück. Keine rohen Pointer, keine zusätzliche Pass; der Nicht-RC-Pfad fügt pro Top-Level-Deklaration eine einzige is_some()-Prüfung hinzu.
Der Compiler respektiert außerdem // eslint-disable react-hooks/*-Unterdrückungen. Der Lexer führt eine Substring-Prüfung pro Kommentar aus, gegated auf das Feature-Flag, und propagiert die Unterdrückung als Flag-Bit auf G::Fn und E::Arrow; der Compiler überspringt jede Funktion, die dieses Bit trägt.
Die Zahlen
Die PR liefert einen Benchmark auf einer großen React-Codebasis (rund 860 kompilierte Komponenten, 1400 Memo-Slots). Derselbe Code, auf derselben Maschine:
| Wandzeit | vs. Babel-Plugin | |
|---|---|---|
Baseline (reactCompiler: false) | 394 ms | - |
reactCompiler: true | 465 ms (1,18x Baseline) | ~20x schneller als Babel |
| Babel-Plugin (gleiche Eingabe) | 9,15 s | 1x |
Der vollständige --compile-Standalone-Executable-Build, der alles bündelt plus den React-Compiler-Durchlauf, läuft mit dem Rust-Port in 3,62 s gegenüber 13,04 s mit dem Babel-Plugin, eine 3,6-fache Beschleunigung im gesamten Build.
Das sind keine synthetischen Mikro-Benchmarks. Die Codebasis ist real, die Komponenten kompilieren zu echten _c(N)-Memoisierungsaufrufen mit $[0] !== label-Cache-Prüfungen, und der react/compiler-runtime-Import, den Bun einfügt, löst sich gegen die React-19+-Installation auf, die mit der Anwendung mitkommt. Bun weist darauf hin, dass der Baseline-mit-RC-Overhead (394 ms zu 465 ms, ~18%) aus dem HIR-Aufbau und der SSA-Pass kommt; der Rest des Bundlers (Parser, Mangle, Minify) bleibt unverändert.
Wie die API aussieht
CLI:
bun build ./app.tsx --react-compiler --target browser
Bun.build:
await Bun.build({
entrypoints: ["./app.tsx"],
reactCompiler: true,
// reactCompilerOutputMode: "client", // Standard für target browser
// reactCompilerOutputMode: "ssr", // Standard für target bun/node
target: "browser",
});
reactCompilerOutputMode ist standardmäßig "client", wenn target "browser" ist, und "ssr", wenn target "bun" oder "node" ist. Der SSR-Modus überspringt die useMemoCache-Runtime, damit die serverseitig gerenderte Ausgabe über Anfragen hinweg cache-freundlich bleibt. Die Semantik compilationMode: "infer" wird vom Upstream-Compiler übernommen, also werden nur Komponenten und Hooks kompiliert; "use no memo"-Direktiven werden respektiert, und node_modules wird übersprungen.
Was das für das Bundler-Wettrüsten bedeutet
Es ist das erste Mal, dass der Rust-Port des React Compilers als Build-Time-Transformation ausgeliefert wird, statt als Bibliothek, in die sich andere Tools einklinken müssen. Die Oxc-v0.135-Integration Mitte Juni hat den Compiler als aufrufbare Rust-Crate hinzugefügt, aber der einzige Bundler, der ihn seither tatsächlich verdrahtet hat, ist Bun. Vite 8 und Vite 8.1 gehen weiterhin über babel-plugin-react-compiler; Next.js mit Turbopack nutzt den SWC-Port; webpack nutzt Babel. Buns Entscheidung, den Upstream direkt in die eigene AST-Schicht zu portieren, ist ein bewusster Trade-off: keine Cross-AST-Konvertierungskosten und keine Abhängigkeitsoberfläche, zum Preis einer regelmäßigen Resynchronisierung mit facebook/react.
Der Wartungspfad ist eingerichtet. scripts/sync-react-compiler.sh holt per Sparse-Fetch facebook/react und gibt einen Diff pro Datei zwischen src/react_compiler/UPSTREAM_PORTED und dem Upstream-Tip aus, gruppiert in vollständige Crate-Ports (die mechanisch anwendbar sind) und AST-Grenzports (die über die Typ-Mapping-Tabelle neu portiert werden). --fixtures resynchronisiert die Testsuite. Solange die Upstream-API Babel-AST-förmig bleibt, sind die Port-Kosten ungefähr proportional dazu, wie oft der Upstream die Grenzschichten anfasst.
Was zu beobachten ist
Drei Signale, die in den nächsten Wochen interessant sind:
- Die Release-Notes zu Bun v1.3.15, sobald sie erscheinen, die PR #32504 plus die Folge-PR bündeln und das Feature von
bun buildexperimentell auf ein stabiles Flag heben sollten. - Der Oxc-
react_compiler_oxc-Adapter, der als stabile Crate in einem Oxc-Release landet, der Weg, den Vite und Rolldown sehr wahrscheinlich gehen werden, um an dieselben Leistungszahlen zu kommen, ohne den Upstream zu portieren. - Jede Änderung der „Public API" des Upstream-React-Compilers von „Babel-AST + Scope-Info" hin zu einer stärker bundler-nativen Form, die es Oxc (und darüber Vite, Next.js, Rsbuild) erlauben würde, ihre eigenen Adapter-Crates komplett zu überspringen.



