Bun intègre le React Compiler directement dans son bundler, environ 20x plus rapide que le plugin Babel

Bun intègre le React Compiler directement dans son bundler, environ 20x plus rapide que le plugin Babel

lschvn

La PR #32504 de Bun, fusionnée le 20 juin 2026, transforme le portage Rust amont du React Compiler en transformation intégrée à bun build. Activez-la avec --react-compiler côté CLI ou reactCompiler: true sur Bun.build, et Bun mémoïsera vos composants et hooks .jsx et .tsx pendant la build, sans plugin Babel, sans fichier de configuration, sans rien à installer. La fonctionnalité est désactivée par défaut et marquée expérimentale à la fois dans les définitions de types et dans la documentation du bundler.

C'est le premier bundler à livrer le React Compiler comme transformation native. Vite, Next.js avec Turbopack, webpack et Rsbuild passent tous par un plugin Babel ou SWC aujourd'hui. Le chemin de Bun saute complètement cette étape intermédiaire.

Ce qui a été livré

L'intégration ferme l'issue #24356, la demande de fonctionnalité de longue date pour un support de première classe du React Compiler dans le bundler, et remplace une PR #31785 antérieure qui dépendait d'une crate oxc_react_compiler qui n'existait pas à l'époque. La nouvelle PR porte l'espace de travail Rust du compilateur directement depuis facebook/react plutôt que de passer par Oxc, ce qui explique pourquoi la description de la PR amont facebook/react#36173 invitait explicitement les intégrations de bundlers via l'adaptateur react_compiler_oxc et que Bun a pris un chemin différent.

Une PR de suivi #32545 livrée le même jour corrige trois commentaires de revue issus de la PR fusionnée, dont un bug subtil où reactCompilerOutputMode: 'client' activait silencieusement le compilateur même lorsque reactCompiler: false était positionné. Le mode de sortie est désormais stocké séparément et n'est appliqué que lorsque le compilateur est activé, ce qui correspond au comportement documenté dans bun.d.ts.

L'architecture : AST Bun directement vers HIR

Le compilateur vit dans src/react_compiler/, une unique crate d'environ 62 k LOC. L'essentiel est un portage à l'octet près de l'espace de travail Rust amont, avec les chemins d'import réécrits et les derives serde et serde_json dont Bun n'a pas besoin retirés. Les crates amont qui sont portées intégralement : hir/, ssa/, inference/, typeinference/, optimization/, validation/, reactive_scopes/, diagnostics/ et utils/. Les structures de données du chemin chaud ont été densifiées : HashMap<SmallId, _> devient Vec<_>, HashSet<ValueReason> devient EnumSet (u16), et les points-to sets deviennent SmallVec<[_; 4]>. L'API IndexMap et IndexSet est adaptée au-dessus de bun_collections::ArrayHashMap à base d'arène.

Les quatre couches qui touchent l'AST (lowering, codegen, pipeline, et la colle program/imports) sont réimplémentées contre bun_ast, avec la table de correspondance de types dans src/react_compiler/DESIGN.md qui documente comment les nœuds AST de Bun correspondent à l'AST de forme Babel que le compilateur attend.

Le hook de compilation se déclenche dans visit_stmts(FnBody), entre sa phase de visite et sa phase de mangle inline. La détection de candidats sur S::Function, S::Local, S::ExportDefault et S::Expr enregistre la Ref du binding et le bit wrapper memo/forwardRef ; visit_func et la visite des arrow functions copient les arguments, flags et locations de la fonction dans une struct Copy PendingCompile ; le hook appelle maybe_compile_pending, qui construit un G::Fn local à la pile et exécute maybe_compile_node. Le corps compilé atterrit dans le buffer stmts vivant pour que la phase de mangle existante s'exécute dessus. Les nouveaux arguments et flags remontent via un unique champ CompileResult. Pas de pointeurs bruts, pas de passe supplémentaire ; le chemin non-RC ajoute un seul is_some() par déclaration de niveau racine.

Le compilateur honore aussi les suppressions // eslint-disable react-hooks/*. Le lexer exécute une vérification de sous-chaîne par commentaire, gardée par le drapeau de fonctionnalité, et propage la suppression comme un bit de flag sur G::Fn et E::Arrow ; le compilateur saute toute fonction qui le porte.

Les chiffres

La PR livre un benchmark sur une grosse base de code React (environ 860 composants compilés, 1400 slots de mémo). Le même code, sur la même machine :

Temps muralvs plugin Babel
Référence (reactCompiler: false)394 ms-
reactCompiler: true465 ms (1,18x la base)~20x plus rapide que Babel
Plugin Babel (même entrée)9,15 s1x

La build complète de l'exécutable autonome via --compile, qui bundle tout plus la passe React Compiler, s'exécute en 3,62 s avec le portage Rust contre 13,04 s avec le plugin Babel, soit 3,6x plus rapide en bout en bout.

Ce ne sont pas des micro-benchmarks synthétiques. La base de code est réelle, les composants compilent vers de vrais appels de mémoïsation _c(N) avec des vérifications de cache $[0] !== label, et l'import react/compiler-runtime que Bun injecte se résout contre l'installation de React 19+ livrée avec l'application. Bun note que le surcoût baseline+RC (394 ms à 465 ms, ~18%) provient de la construction HIR et de la passe SSA ; le reste du bundler (parser, mangle, minify) est inchangé.

À quoi ressemble l'API

CLI :

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

Bun.build :

await Bun.build({
  entrypoints: ["./app.tsx"],
  reactCompiler: true,
  // reactCompilerOutputMode: "client", // par défaut pour target browser
  // reactCompilerOutputMode: "ssr",   // par défaut pour target bun/node
  target: "browser",
});

reactCompilerOutputMode vaut par défaut "client" quand target est "browser" et "ssr" quand target est "bun" ou "node". Le mode SSR ignore le runtime useMemoCache pour que la sortie rendue côté serveur reste compatible avec un cache entre requêtes. La sémantique compilationMode: "infer" est héritée du compilateur amont, donc seuls les composants et hooks sont compilés ; les directives "use no memo" sont honorées, et node_modules est ignoré.

Ce que cela signifie pour la course aux bundlers

C'est la première fois que le portage Rust du React Compiler est livré comme transformation au moment du build plutôt que comme bibliothèque dans laquelle d'autres outils doivent se brancher. L'intégration Oxc v0.135 mi-juin a ajouté le compilateur comme crate Rust appelable, mais le seul bundler à l'avoir effectivement câblé depuis est Bun. Vite 8 et Vite 8.1 passent toujours par babel-plugin-react-compiler ; Next.js avec Turbopack utilise le portage SWC ; webpack utilise Babel. Le choix de Bun de porter l'amont directement dans sa propre couche AST est un arbitrage délibéré : il évite le coût de conversion cross-AST et la surface de dépendance, au prix d'avoir à resynchroniser périodiquement avec facebook/react.

Le chemin de maintenance est câblé. scripts/sync-react-compiler.sh récupère par sparse-fetch facebook/react et affiche un diff fichier par fichier entre src/react_compiler/UPSTREAM_PORTED et la pointe amont, groupé en ports de crate entiers (qui s'appliquent mécaniquement) et en ports côté frontière AST (qui se re-portent via la table de correspondance de types). --fixtures resynchronise le corpus de tests. Tant que l'API amont reste de forme AST Babel, le coût du suivi du portage est à peu près proportionnel à la fréquence à laquelle l'amont touche les couches de frontière.

À surveiller

Trois signaux intéressants à suivre sur les prochaines semaines :

  1. Les notes de release de Bun v1.3.15 lorsqu'elles arriveront, qui devraient regrouper la PR #32504 plus la PR de suivi et promouvoir la fonctionnalité de bun build expérimental à un drapeau stable.
  2. L'adaptateur Oxc react_compiler_oxc livré comme crate stable dans une release Oxc, qui est le chemin que Vite et Rolldown prendront très probablement pour obtenir les mêmes chiffres de performance sans porter l'amont.
  3. Tout changement dans l'« API publique » amont du React Compiler depuis « AST Babel + scope info » vers une forme plus native aux bundlers, qui permettrait à Oxc (et via lui Vite, Next.js, Rsbuild) de se passer entièrement de leurs propres crates d'adaptation.

Questions fréquentes

Articles connexes

Plus de couverture avec des sujets et tags en commun.

Oxlint v1.72 et Oxfmt v0.57 livrent le cycle crates v0.138, unifient l'AstBuilder et retirent le repli Prettier pour CSS/GraphQL
tooling

Oxlint v1.72 et Oxfmt v0.57 livrent le cycle crates v0.138, unifient l'AstBuilder et retirent le repli Prettier pour CSS/GraphQL

Oxlint apps_v1.72.0 et oxfmt apps_v0.57.0, tous deux publiés le 2026-06-29, clôturent le cycle v0.138 prédit dans les [notes de version v1.71](/articles/2026-06-23--oxlint-v1-71-oxfmt-v0-56). La version crates (crates_v0.138.0, aussi 2026-06-29) unifie les anciennes et nouvelles API AstBuilder (#23876, #23834, #23831, avec les méthodes héritées marquées `#[deprecated]`), renomme `AllocatorAccessor` en `GetAllocator` et fait prendre `&self` à sa méthode `allocator` (#23675, #23676), fait prendre `&GetAllocator` aux méthodes `Str` et `Ident` (#23781), ajoute `transformer_plugins: Support typeof define keys` pour vue-i18n et macros similaires (#23605, Alexander Lichter), et livre l'entrée de perf phare du cycle : `minifier: memoize value_type to remove its O(n^2) re-walk on long binary chains` (#23929, Dunqing), qui transforme une chaîne d'addition arithmétique de 20 000 termes de 6 118 ms à 4,7 ms (≈1300x) et le bundle antd.js réel de 78,1 ms à 65,4 ms. Oxlint v1.72.0 livre 3 fonctionnalités (suggestion React pour `no-unknown-property` #23936, unification AstBuilder #23875, schéma pour `eslint/no-restricted-import` #23642), 18 corrections de bugs, et 23 entrées de performance. Oxfmt v0.57.0 comporte deux changements BREAKING qui retirent le repli Prettier pour les fichiers CSS/LESS/SCSS et GraphQL : `Format parser:css,less,scss files + css-in-js by oxc_formatter_css` (#23321, leaysgur) et `Support draft syntax with removing prettier fallback` (#23326, leaysgur), tous deux construits sur les nouveaux crates `oxc_formatter_css` (#23320) et `oxc_formatter_graphql` (#23317).
Deno 2.9 livre un démarrage à froid 1,98x plus rapide, 2,2 à 3,1x moins de RSS en charge, le minimum-release-age npm activé par défaut, la politique de confiance no-downgrade et les tests par snapshot natifs
runtimes

Deno 2.9 livre un démarrage à froid 1,98x plus rapide, 2,2 à 3,1x moins de RSS en charge, le minimum-release-age npm activé par défaut, la politique de confiance no-downgrade et les tests par snapshot natifs

Deno 2.9 (Bartek Iwańczuk, publié le 2026-06-25 sur deno.com/blog/v2.9) est la plus grande release Deno du cycle. Le démarrage à froid passe de 34,2 ms à 17,3 ms (1,98x), le pic RSS sur la charge realworld de Deno.serve baisse d'un facteur 2,2 (142 Mo → 64 Mo) et de 3,1x sur les corps de 1 Mio (197 Mo → 63 Mo), et le débit Deno.serve grimpe de 1,27x en realworld (56,8k → 72,4k req/s), 1,11x en plaintext, et 1,18x sur les corps de 1 Mio. Durcissement supply chain : le npm minimum-release-age est activé par défaut avec une fenêtre de 24h (PR #35458), et une nouvelle politique de confiance opt-in no-downgrade (PR #34927) refuse de résoudre toute version dont la preuve de confiance (publication échelonnée, trusted publishing, attestation de provenance) est plus faible que la meilleure preuve sur toute version précédemment publiée du même paquet. Parité du runner de tests : t.assertSnapshot() natif (#35139), Deno.test.each (#34938), --shard pour fan-out CI (#35057), retry et repeats (#35053), sélection change-aware --changed et --related (#35199), et seuils de coverage (#35056). Interop lockfile : deno install seed deno.lock depuis package-lock.json, pnpm-lock.yaml, yarn.lock ou bun.lock (#34296, #35330, #35346, #35350, #35394), pnpm-workspace.yaml auto-migre vers deno.json / package.json (#34993), et les marqueurs de conflit de fusion git dans deno.lock se résolvent automatiquement (#34726). Plus : deno desktop sort de l'expérimental (PR #33441 du 16 juin), sous-commandes deno link / deno unlink / deno list / deno watch, --unsafe-proto stable (#34738), Web Locks API (#31166), Happy Eyeballs v2 (RFC 8305) (#31726), navigator.userAgentData (#34743), la proposition WebCrypto Modern Algorithms (ML-KEM, ML-DSA, SLH-DSA, ChaCha20-Poly1305, famille SHA-3, KMAC, Argon2) (#34447, #34448, #34914, #35223), compat Node 26.3.0 (#34746, #34747), Node-API v10 (#35270), et imports de modules CSS sous --unstable-raw-imports (#35093). Plus de 165 PRs mergent dans ce cycle.
Node.js 26.4.0 'Current' livre le sous-système node:vfs (Matteo Collina), les package maps pour les hooks de loader ESM (Maël Nison), la compression de certificat TLS, TCP_KEEPINTVL/TCP_KEEPCNT, et argon2 stable
runtimes

Node.js 26.4.0 'Current' livre le sous-système node:vfs (Matteo Collina), les package maps pour les hooks de loader ESM (Maël Nison), la compression de certificat TLS, TCP_KEEPINTVL/TCP_KEEPCNT, et argon2 stable

Node.js 26.4.0 (Current), publié le 2026-06-24 par @aduh95, apporte huit changements SEMVER-MINOR : un sous-système node:vfs minimal qui permet de monter des systèmes de fichiers virtuels fournis par l'application (PR #63115, Matteo Collina) ainsi qu'un suivi qui redirige node:fs/promises vers les instances VFS montées (PR #63537), les package maps pour les hooks de loader ESM qui routent les spécificateurs bruts à travers les hooks de loader (PR #62239, Maël Nison), certificateCompression TLS qui câble la compression zlib et zstd de la RFC 8879 à travers la configuration de build OpenSSL (PR #62217, Tim Perry), le support de TCP_KEEPINTVL et TCP_KEEPCNT dans net.Socket.setKeepAlive (PR #63825, Guy Bedford), des buffers fournis par l'appelant dans fs.readFile / fs.readFileSync (PR #63634, Matteo Collina), closeIdleConnections qui ferme désormais aussi les sockets pre-request (PR #63470, semimikoh), net.BlockList promu au statut Release Candidate (PR #63050), et crypto argon2 + KEM encap/decap marqués stable (PR #63924, Filip Skokan). La release ajoute aussi WebCrypto cSHAKE (PR #63988), listEndpoints QUIC (PR #63536) et des handles X509Certificate (PR #63191), dgram connectSync / bindSync (PRs #63838 + #63932, Guy Bedford), net.BoundSocket pour un binding TCP précoce (PR #63951), un chemin expérimental d'appel FFI rapide pour AArch64 et x86_64 (PRs #63068 + #63941, Paolo Insogna), npm 11.17.0 (PR #63857), sqlite 3.53.2 et libffi 3.6.0.

Commentaires

Connexion Connectez-vous pour participer à la conversation.

Pas encore de commentaires. Soyez le premier à partager vos pensées.