Bun integriert den React Compiler direkt in seinen Bundler, rund 20x schneller als das Babel-Plugin

Bun integriert den React Compiler direkt in seinen Bundler, rund 20x schneller als das Babel-Plugin

lschvn

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:

Wandzeitvs. Babel-Plugin
Baseline (reactCompiler: false)394 ms-
reactCompiler: true465 ms (1,18x Baseline)~20x schneller als Babel
Babel-Plugin (gleiche Eingabe)9,15 s1x

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:

  1. 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 build experimentell auf ein stabiles Flag heben sollten.
  2. 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.
  3. 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.

Häufig gestellte Fragen

Verwandte Artikel

Weitere Berichterstattung zu ähnlichen Themen und Tags.

Oxlint v1.72 und Oxfmt v0.57 liefern den crates-v0.138-Zyklus aus, vereinheitlichen den AstBuilder und entfernen den Prettier-Fallback für CSS/GraphQL
tooling

Oxlint v1.72 und Oxfmt v0.57 liefern den crates-v0.138-Zyklus aus, vereinheitlichen den AstBuilder und entfernen den Prettier-Fallback für CSS/GraphQL

Oxlint apps_v1.72.0 und oxfmt apps_v0.57.0, beide am 2026-06-29 veröffentlicht, schließen den v0.138-Zyklus ab, der in den [v1.71-Versionsnotizen](/articles/2026-06-23--oxlint-v1-71-oxfmt-v0-56) vorausgesagt wurde. Das crates-Release (crates_v0.138.0, ebenfalls 2026-06-29) vereinheitlicht die alten und neuen AstBuilder-APIs (#23876, #23834, #23831, mit den Legacy-Methoden als `#[deprecated]` markiert), benennt `AllocatorAccessor` in `GetAllocator` um und lässt dessen `allocator`-Methode `&self` annehmen (#23675, #23676), lässt `Str`- und `Ident`-Methoden `&GetAllocator` annehmen (#23781), fügt `transformer_plugins: Support typeof define keys` für vue-i18n und ähnliche Makros hinzu (#23605, Alexander Lichter) und liefert die herausragende Performance-Eintragung des Zyklus: `minifier: memoize value_type to remove its O(n^2) re-walk on long binary chains` (#23929, Dunqing), die eine arithmetische Additionskette mit 20 000 Gliedern von 6 118 ms auf 4,7 ms (≈1300x) bringt und das reale antd.js-Bundle von 78,1 ms auf 65,4 ms. Oxlint v1.72.0 liefert 3 Features (React-`no-unknown-property`-Vorschlag #23936, AstBuilder-Vereinheitlichung #23875, Schema für `eslint/no-restricted-import` #23642), 18 Fehlerbehebungen und 23 Performance-Eintragungen. Oxfmt v0.57.0 enthält zwei BREAKING-Änderungen, die den Prettier-Fallback für CSS/LESS/SCSS- und GraphQL-Dateien entfernen: `Format parser:css,less,scss files + css-in-js by oxc_formatter_css` (#23321, leaysgur) und `Support draft syntax with removing prettier fallback` (#23326, leaysgur), beide aufbauend auf den neuen Crates `oxc_formatter_css` (#23320) und `oxc_formatter_graphql` (#23317).
Deno 2.9 bringt 1,98x schnelleren Cold Start, 2,2–3,1x weniger RSS unter Last, npm Minimum Release Age als Default, No-Downgrade Trust Policy und eingebaute Snapshot-Tests
runtimes

Deno 2.9 bringt 1,98x schnelleren Cold Start, 2,2–3,1x weniger RSS unter Last, npm Minimum Release Age als Default, No-Downgrade Trust Policy und eingebaute Snapshot-Tests

Deno 2.9 (Bartek Iwańczuk, veröffentlicht am 2026-06-25 auf deno.com/blog/v2.9) ist das größte Deno-Release des Zyklus. Der Cold Start sinkt von 34,2 ms auf 17,3 ms (1,98x), der Spitzen-RSS auf der Deno.serve-Realworld-Last fällt um den Faktor 2,2 (142 MB → 64 MB) und um 3,1x bei 1-MiB-Bodies (197 MB → 63 MB), und der Deno.serve-Durchsatz steigt um 1,27x in Realworld (56,8k → 72,4k req/s), 1,11x in Plaintext und 1,18x bei 1-MiB-Bodies. Supply-Chain-Härtung: Das npm Minimum Release Age wird standardmäßig mit einem 24-Stunden-Fenster aktiviert (PR #35458), und eine neue opt-in No-Downgrade Trust Policy (PR #34927) weigert sich, jede Version aufzulösen, deren Vertrauensbeweis (Staged Publishing, Trusted Publishing, Provenance-Attestation) schwächer ist als der stärkste Beweis einer zuvor veröffentlichten Version desselben Pakets. Test-Runner-Parität: eingebautes t.assertSnapshot() (#35139), Deno.test.each (#34938), --shard für CI-Fan-out (#35057), Retry und Repeats (#35053), Change-Aware --changed und --related (#35199) und Coverage-Thresholds (#35056). Lockfile-Interop: deno install seedet deno.lock aus package-lock.json, pnpm-lock.yaml, yarn.lock oder bun.lock (#34296, #35330, #35346, #35350, #35394), pnpm-workspace.yaml migriert automatisch in deno.json / package.json (#34993), und Git-Merge-Konflikt-Marker in deno.lock lösen sich automatisch auf (#34726). Dazu: deno desktop verlässt den experimentellen Status (PR #33441 vom 16. Juni), Subcommands deno link / deno unlink / deno list / deno watch, stabiles --unsafe-proto (#34738), Web Locks API (#31166), Happy Eyeballs v2 (RFC 8305) (#31726), navigator.userAgentData (#34743), der WebCrypto Modern Algorithms-Vorschlag (ML-KEM, ML-DSA, SLH-DSA, ChaCha20-Poly1305, SHA-3-Familie, KMAC, Argon2) (#34447, #34448, #34914, #35223), Node-26.3.0-Kompatibilität (#34746, #34747), Node-API v10 (#35270) und CSS-Modul-Importe unter --unstable-raw-imports (#35093). Über 165 PRs landen in diesem Zyklus.
Node.js 26.4.0 'Current' bringt das node:vfs-Subsystem (Matteo Collina), ESM-Loader-Package-Maps (Maël Nison), TLS-Zertifikatkompression, TCP_KEEPINTVL/TCP_KEEPCNT und argon2 als stabil
runtimes

Node.js 26.4.0 'Current' bringt das node:vfs-Subsystem (Matteo Collina), ESM-Loader-Package-Maps (Maël Nison), TLS-Zertifikatkompression, TCP_KEEPINTVL/TCP_KEEPCNT und argon2 als stabil

Node.js 26.4.0 (Current), veröffentlicht am 2026-06-24 von @aduh95, liefert acht SEMVER-MINOR-Änderungen: ein minimales node:vfs-Subsystem, das vom Anwendungscode bereitgestellte virtuelle Dateisysteme einhängt (PR #63115, Matteo Collina) sowie ein Folgepatch, das node:fs/promises an eingehängte VFS-Instanzen weiterleitet (PR #63537), Package-Maps für ESM-Loader-Hooks, die Bare-Spezifier durch die Loader-Hooks routen (PR #62239, Maël Nison), TLS certificateCompression, das die zlib- und zstd-Kompression aus RFC 8879 durch die OpenSSL-Build-Konfiguration verkabelt (PR #62217, Tim Perry), TCP_KEEPINTVL- und TCP_KEEPCNT-Unterstützung in net.Socket.setKeepAlive (PR #63825, Guy Bedford), vom Aufrufer bereitgestellte Buffer in fs.readFile / fs.readFileSync (PR #63634, Matteo Collina), closeIdleConnections, das nun auch Pre-Request-Sockets schließt (PR #63470, semimikoh), net.BlockList auf Release-Candidate-Stabilität vorgerückt (PR #63050), und crypto.argon2 + KEM encap/decap als stabil markiert (PR #63924, Filip Skokan). Die Release ergänzt außerdem WebCrypto-cSHAKE (PR #63988), QUIC listEndpoints (PR #63536) und X509Certificate-Handles (PR #63191), dgram connectSync / bindSync (PRs #63838 + #63932, Guy Bedford), net.BoundSocket für frühes TCP-Binding (PR #63951), einen experimentellen schnellen FFI-Aufrufpfad für AArch64 und x86_64 (PRs #63068 + #63941, Paolo Insogna), npm 11.17.0 (PR #63857), sqlite 3.53.2 und libffi 3.6.0.

Kommentare

Anmelden Melden Sie sich an, um an der Diskussion teilzunehmen.

Noch keine Kommentare. Seien Sie der Erste, der seine Gedanken teilt.