Bun s’est imposé en dix-huit mois comme l’alternative la plus crédible à Node.js, et Bun 2.0 relance la question que tout le monde se pose : faut-il vraiment changer de runtime en 2026 ? Plutôt que de relayer des promesses marketing, j’ai sorti le chronomètre. J’ai benchmarké Bun 2.0 face à Node.js 24 sur cinq scénarios proches de vrais projets, pas des micro-benchmarks de laboratoire : une API REST, du traitement de fichiers, des WebSockets, une suite de tests et l’installation de dépendances. Le tout sur la même machine, un MacBook Pro M3 doté de 36 Go de RAM, pour éliminer toute variable matérielle. Voici ce que les chiffres racontent, et ce qu’ils taisent.
Pourquoi Bun va plus vite : JavaScriptCore, Zig et APIs natives
La première chose à comprendre, c’est que Bun ne se contente pas d’optimiser Node.js : il repart d’une base technique différente. Là où Node.js embarque le moteur V8 de Google, Bun s’appuie sur JavaScriptCore, le moteur de WebKit développé par Apple. Les deux moteurs ont des philosophies distinctes : V8 mise historiquement sur une compilation JIT très agressive et un débit maximal sur du code chaud, tandis que JavaScriptCore privilégie un démarrage rapide et une empreinte mémoire plus contenue. Pour un runtime qui doit lancer des processus courts, exécuter des tests ou démarrer un serveur en quelques millisecondes, ce profil de performance fait une vraie différence ressentie au quotidien.
Le second pilier, c’est le langage d’implémentation. Bun est écrit en Zig, un langage système bas niveau pensé pour le contrôle mémoire fin et l’absence de coût caché à l’exécution. Cela permet à ses auteurs de réécrire des primitives habituellement déléguées à des bibliothèques JavaScript ou à des bindings, au plus près des appels système. Quand vous lisez un fichier avec l’API Bun.file(), vous ne traversez pas plusieurs couches d’abstraction : l’implémentation parle directement à l’OS. C’est exactement ce qui explique l’écart sur le traitement de fichiers, où Bun a transformé 500 documents Markdown en HTML en 980 ms contre 3200 ms pour Node, soit un rapport de 3,2x.
Le tableau de benchmark : cinq scénarios, des écarts nets
Avant d’entrer dans le détail de chaque scénario, voici la synthèse chiffrée des cinq tests. Tous ont été exécutés plusieurs fois sur la même machine pour lisser la variance, et je reporte ici les valeurs représentatives. Gardez en tête que ces chiffres décrivent des charges spécifiques : ils donnent une direction claire, pas une vérité universelle valable pour toutes les applications.
| Scénario | Node.js 24 | Bun 2.0 | Écart |
|---|---|---|---|
| API REST (req/sec soutenu) | 8200 req/s | 14500 req/s | +45% pour Bun |
| Fichiers (500 Markdown vers HTML) | 3200 ms | 980 ms | 3,2x plus rapide |
| WebSockets (1000 connexions) | 420 ms | 180 ms | 2,3x plus rapide |
| WebSockets (broadcast 1000 msg) | 890 ms | 340 ms | 2,6x plus rapide |
| Tests (200 tests) | 4,8 s (Vitest) | 1,9 s (bun test) | 2,5x plus rapide |
| Installation des dépendances | 18,2 s (npm) | 3,1 s (bun) | 6x plus rapide |
Ce que ce tableau montre, c’est une régularité : Bun ne gagne pas par hasard sur un cas isolé, il prend l’avantage sur tous les scénarios, avec des marges allant de 45 % à un facteur six. Les écarts les plus impressionnants concernent les opérations où Node délègue beaucoup au userland JavaScript ou à des outils tiers, comme l’installation de paquets et l’exécution des tests. Sur le pur débit HTTP, l’écart de 45 % reste significatif mais plus mesuré, ce qui est cohérent avec le fait que V8 excelle sur le code chaud.
API REST et serveur HTTP avec Bun.serve
Sur le scénario API REST, mille requêtes mêlant GET et POST, Bun a soutenu 14500 requêtes par seconde contre 8200 pour Node.js, soit environ 45 % de mieux. Une partie de ce gain vient de l’API Bun.serve(), qui expose un serveur HTTP natif sans passer par le module http de Node ni par un framework. L’implémentation gère le parsing des requêtes et la sérialisation des réponses au plus près du moteur, et accepte désormais un objet routes déclaratif qui évite d’écrire un routeur à la main. Pour un développeur habitué à Express, le changement est minime, mais la couche d’abstraction en moins se ressent sur le débit.
Voici un serveur Bun avec routes, tel qu’on l’écrirait pour un petit service en production :
// server.ts — lancé avec: bun run server.ts
const server = Bun.serve({
port: 3000,
routes: {
"/api/health": new Response("ok"),
"/api/users/:id": (req) => {
const { id } = req.params;
return Response.json({ id, name: "Alice" });
},
"/api/users": {
POST: async (req) => {
const body = await req.json();
return Response.json({ created: body }, { status: 201 });
},
},
},
fetch(req) {
return new Response("Not found", { status: 404 });
},
});
console.log(`Serveur prêt sur http://localhost:${server.port}`);
Notez l’absence totale de dépendances : pas d’import de framework, pas de middleware à configurer, le support TypeScript est immédiat puisque le fichier .ts s’exécute directement. C’est cette densité fonctionnelle qui, mise bout à bout sur tout un projet, allège la maintenance autant que la latence.
Traitement de fichiers et WebSockets : là où l’écart explose
Le traitement de fichiers est le domaine où Bun a creusé l’écart le plus net en valeur absolue. Convertir 500 fichiers Markdown en HTML a pris 980 ms à Bun contre 3200 ms à Node, un rapport de 3,2x. L’explication tient à l’API Bun.file(), qui ne crée pas immédiatement un flux coûteux mais représente une référence paresseuse vers le fichier, lue de la façon la plus directe possible par l’OS. Sur des charges qui ouvrent, lisent et ferment des milliers de descripteurs, ces économies par opération s’additionnent vite, et le profil est précieux pour la génération de site statique, le traitement de logs ou les pipelines de build.
Côté temps réel, le constat est similaire. L’établissement de 1000 connexions WebSocket a pris 180 ms à Bun contre 420 ms à Node, soit 2,3x plus rapide. Le test de diffusion, l’envoi de 1000 messages en broadcast, a confirmé la tendance avec 340 ms pour Bun face à 890 ms pour Node, environ 2,6x. Là encore, Bun intègre un serveur WebSocket natif directement dans Bun.serve(), via des handlers d’ouverture, de message et de fermeture, sans dépendance externe de type ws. Moins de couches entre le socket et votre code, c’est moins de latence par message.
Compatibilité Node, npm et TypeScript natif
Le point qui rassure le plus, c’est que Bun ne vous demande pas de tout réapprendre. Il vise une compatibilité large avec l’écosystème Node : il lit votre package.json, installe dans node_modules, respecte la résolution de modules attendue et implémente une grande partie des API Node intégrées. Concrètement, la majorité des paquets npm fonctionnent sans modification, et vos scripts définis dans package.json restent exécutables. Cette compatibilité n’est pas parfaite à cent pour cent, certaines API exotiques ou comportements de bas niveau peuvent différer, mais pour le code applicatif courant la transition est étonnamment indolore.
L’autre confort majeur est le support TypeScript et JSX natif. Bun exécute directement les fichiers .ts et .tsx sans étape de compilation préalable, sans ts-node, sans babel, sans pipeline de transpilation. Vous écrivez du TypeScript, vous lancez bun run, et le code tourne. Cela élimine toute une catégorie de configuration et de dépendances de build qui, dans un projet Node classique, finissent par alourdir le démarrage et la maintenance. Combiné au test runner intégré, un projet TypeScript moderne peut ainsi démarrer avec quasiment aucune dépendance d’outillage.
bun install et le lockfile binaire
Le résultat le plus spectaculaire du benchmark concerne l’installation des dépendances : bun install a terminé en 3,1 s là où npm install a demandé 18,2 s, un facteur six. Cet écart n’est pas anecdotique pour qui enchaîne les installations, en local lors des changements de branche comme en CI à chaque pipeline. La rapidité de bun install vient d’une combinaison de facteurs : un cache global efficace, une stratégie d’installation fortement parallélisée et une gestion du système de fichiers écrite pour minimiser les opérations redondantes. Sur les machines de développement comme sur les runners d’intégration continue, ce gain se transforme en minutes économisées chaque jour.
Bun gère aussi son propre lockfile. Historiquement au format binaire pour des raisons de performance de lecture et d’écriture, il garantit un arbre de dépendances reproductible et déterministe d’une machine à l’autre. Ce format binaire a l’avantage de la vitesse mais l’inconvénient d’être peu lisible dans un diff Git, ce qui a poussé l’écosystème à proposer une option de lockfile textuel. Dans tous les cas, le principe reste le même : verrouiller précisément les versions installées pour que ce qui tourne chez vous tourne à l’identique en production.
Pour illustrer la sobriété d’un projet Bun, voici un package.json minimal qui s’appuie uniquement sur les capacités natives du runtime :
{
"name": "mon-service",
"type": "module",
"scripts": {
"dev": "bun run --watch server.ts",
"start": "bun run server.ts",
"test": "bun test"
}
}
Les limites de Bun : node-gyp, addons natifs et maturité
Tout n’est pas favorable à Bun. Le premier point de friction réel concerne les modules natifs compilés en C++ via node-gyp : ces paquets embarquent du code qui doit être compilé et lié contre l’ABI du runtime, et c’est historiquement sur ce terrain que la compatibilité de Bun a été la plus délicate. Si votre application dépend de ce type d’addon natif, fréquent dans le chiffrement bas niveau, le traitement d’image ou certains pilotes de bases de données, vous devez impérativement valider que la dépendance fonctionne avant toute migration. Un benchmark flatteur ne sert à rien si une brique critique refuse de se charger.
Au-delà du natif, Node.js conserve des atouts structurels que Bun, plus jeune, ne peut pas répliquer du jour au lendemain. Node bénéficie d’un écosystème mûri sur une quinzaine d’années, d’une présence universelle dans les chaînes CI/CD et les images Docker, d’une intégration profonde dans tous les hébergeurs, et d’un outillage de debugging éprouvé. Quand quelque chose casse en production à trois heures du matin, la profondeur de la documentation, le nombre de réponses sur les forums et la robustesse des outils d’introspection comptent autant que la vitesse brute. Cette maturité est un actif invisible dans un benchmark, mais déterminant dans la durée.
Stratégie de migration, observabilité et verdict
Ma recommandation tient en deux temps. Pour un nouveau projet, Bun est aujourd’hui un choix très pertinent : vous obtenez d’emblée un serveur HTTP et WebSocket natif, un test runner intégré, le support TypeScript sans configuration et une installation de dépendances six fois plus rapide, sans empiler de dépendances d’outillage. Vous démarrez plus léger et vous allez plus vite, à condition d’avoir vérifié au préalable que vos quelques dépendances critiques sont compatibles. C’est le scénario où le rapport bénéfice-risque penche le plus clairement en faveur de Bun.
Pour une application existante en production sur Node, le calcul est différent. La migration n’est pas une priorité en soi, sauf si vous avez identifié un goulot précis que les chiffres de Bun pourraient lever : charge fortement orientée fichiers ou temps réel, ou temps d’installation en CI devenus pénalisants. Dans ce cas, procédez par étapes : adoptez d’abord bun install comme gestionnaire de paquets sans changer de runtime, mesurez, puis envisagez de faire tourner certains services ou la suite de tests sous Bun. Cette progressivité vous laisse reculer à tout moment.
Enfin, ne négligez jamais l’observabilité avant de basculer un service en production. Vérifiez que vos outils de métriques, de traçage et de logs s’intègrent correctement, que vos sondes de santé répondent comme attendu et que votre CI exécute la même suite de tests qu’avant. Le verdict sur la performance est sans ambiguïté : sur les cinq scénarios testés, Bun 2.0 devance Node.js 24, parfois nettement. Mais la performance n’est qu’une variable. La maturité, la compatibilité native et la sérénité d’exploitation pèsent tout autant, et c’est en croisant les deux que vous trancherez pour votre contexte.
Commentaires (0)
Laisser un commentaire
Les commentaires sont modérés. Questions WordPress, cybersécurité ou dev web bienvenues.