Bun, un runtime JavaScript tout-en-un

Bun réunit un runtime, un gestionnaire de paquets, un bundler et un test runner dans un seul binaire. Performance, compatibilité Node et SQLite intégré.

L'équipe CodeWorlds14 min de lecture ·

Pratique JavaScript et TypeScript dans des leçons interactives avec un éditeur de code.

Crée un compte gratuit→
Dans cet article13

Bun remplace quatre choses à la fois : il exécute le code comme Node, installe les dépendances comme npm, empaquette comme esbuild, et exécute les tests comme Jest. Tout ça tient dans un seul binaire écrit en Zig et construit sur JavaScriptCore, le même moteur qui fait tourner Safari.

Ce qui change dans le travail quotidien

Un projet JavaScript typique transporte aujourd'hui quatre outils venant de quatre équipes différentes, chacun avec sa propre configuration et son propre cycle de sortie. Bun les réunit en un seul, donc la couche de configuration qui les reliait disparaît.

Le changement le plus visible concerne TypeScript. Bun exécute les fichiers .ts et .tsx directement, sans étape de compilation et sans outil supplémentaire au milieu.

Bash
bun run script.ts

Le même fichier sous Node a besoin soit d'une compilation préalable, soit d'un outil qui intercepte les imports. En pratique, ça veut dire un démarrage plus rapide et moins de choses qui cassent à chaque changement de version.

Le deuxième changement est la vitesse d'installation des dépendances. L'écart avec les gestionnaires de paquets traditionnels se compte en plusieurs multiples, et ça se voit surtout en intégration continue, où l'installation se répète à chaque exécution.

Un gestionnaire de paquets compatible avec l'écosystème

Bun lit le package.json et installe depuis le registre npm, donc il n'exige aucun changement dans la façon de décrire les dépendances.

Bash
bun install
bun add zod
bun add -d vitest
bun remove lodash

Une chose vaut la peine d'être connue pour le travail en équipe. Bun écrit son propre fichier de verrouillage, et son format par défaut a été binaire, ce qui fait qu'un diff de pull request ne dit plus rien. Si le reste de l'équipe utilise un gestionnaire différent, mettez-vous d'accord sur un seul, car deux fichiers de verrouillage en parallèle font que tout le monde finit par avoir une version différente de chaque bibliothèque.

L'installation globale d'outils fonctionne aussi, et bunx exécute un paquet sans l'installer de façon permanente, à l'image de la commande que tu connais déjà avec npm.

Un test runner intégré

Tu lances les tests sans ajouter de bibliothèque, et la syntaxe correspond à ce que la plupart des équipes connaissent déjà.

JavaScript
import { test, expect, describe } from "bun:test"

describe("calculateur de remise", () => {
  test("applique la remise à partir du seuil", () => {
    expect(calculateDiscount(1200)).toBe(120)
  })

  test("n'applique rien en dessous du seuil", () => {
    expect(calculateDiscount(300)).toBe(0)
  })
})
Bash
bun test
bun test --watch
bun test --coverage

En migrant depuis Jest, la plupart des tests unitaires passent sans changement. Les ennuis commencent avec les tests construits sur des simulations de modules élaborées et avec les bibliothèques qui supposent un environnement précis, donc avant de décider, lance ta suite actuelle et regarde ce qui casse.

Serveur HTTP et base de données intégrée

Bun fournit son propre serveur construit sur les objets standards Request et Response, les mêmes que tu connais depuis le navigateur et depuis les routes API de Next.js.

JavaScript
Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url)
    if (url.pathname === "/health") return new Response("ok")
    return Response.json({ error: "not found" }, { status: 404 })
  }
})

S'ajoute à ça un client SQLite intégré au runtime, sans aucun module natif à installer.

JavaScript
import { Database } from "bun:sqlite"

const db = new Database("data.sqlite")
db.run("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, name TEXT)")
db.query("INSERT INTO events (name) VALUES (?)").run("start")

Une base en fichier porte une limite qu'il vaut mieux nommer tout de suite : elle vit sur le disque de cette machine, donc une deuxième instance du service voit sa propre copie, et déployer sur plusieurs machines n'a plus de sens. Quand ça commence à poser problème, Turso propose la même syntaxe SQLite sous forme de service, avec une réplique synchronisée directement dans l'application, au prix de lectures qui montrent un état datant d'un instant plus tôt.

Au-delà de SQLite, le runtime fournit aussi des clients pour des bases de données et des caches populaires, donc un service typique se passe de plusieurs dépendances que l'on installe par réflexe. Moins de dépendances veut dire moins de mises à jour de sécurité à suivre, ce qui compte souvent plus que la vitesse brute sur les petits projets.

Cette configuration convient bien aux petits services et aux outils internes : un binaire, une base de données dans un fichier, rien à installer sur le serveur.

Le bundler et la construction d'un paquet

Le quatrième outil de cet ensemble est un bundler. Il construit aussi bien du code pour le navigateur qu'un fichier serveur unique prêt à déployer.

Bash
bun build ./src/index.ts --outdir ./dist --target node --minify
bun build ./src/app.tsx --outdir ./public --target browser --splitting

Le paramètre target décide comment les modules intégrés sont traités. La version navigateur retire les références au système de fichiers, la version serveur les conserve, et la variante propre à Bun utilise ses propres interfaces.

En pratique, le bundler convient bien aux bibliothèques, aux outils en ligne de commande et aux petites applications. Pour un frontend élaboré avec de nombreux plugins, l'écosystème autour des bundlers plus matures reste plus riche, donc vérifie que les plugins dont tu as besoin ont des équivalents avant de réécrire ta configuration de build.

Par ailleurs, il vaut la peine de connaître l'option qui construit un exécutable unique contenant à la fois le code et le runtime.

Bash
bun build ./cli.ts --compile --outfile tool

Le résultat s'exécute sans rien installer. Pour des outils distribués en interne dans une entreprise, ça résout un problème qui finit sinon par des instructions pour installer la bonne version de Node. Le prix à payer est une taille de fichier qui se compte en dizaines de mégaoctets, puisque le runtime vient avec.

Des scripts à la place d'outils auxiliaires

Bun fonctionne bien comme remplaçant des scripts auxiliaires qui, dans beaucoup de projets, sont écrits en shell et que personne ne comprend plus six mois plus tard.

TSscripts/cleanup.ts
// scripts/cleanup.ts
import { readdir, rm } from "node:fs/promises"

const directories = await readdir("./tmp")
for (const directory of directories) {
  if (directory.startsWith("build-")) {
    await rm(`./tmp/${directory}`, { recursive: true })
    console.log("supprimé", directory)
  }
}

Tu le lances avec bun scripts/cleanup.ts, sans compilation et sans dépendance ajoutée. Du code typé est plus facile à maintenir qu'un script shell, et les différences entre systèmes d'exploitation cessent de poser problème.

Les interfaces intégrées couvrent la plupart des besoins de ce genre de scripts : lecture et écriture de fichiers, appels réseau, lancement de sous-processus, accès à une base de données en fichier. Pour les tâches récurrentes, un rapport généré chaque nuit par exemple, tout tient dans un seul fichier et une seule entrée de planificateur.

Fais juste attention à la gestion des erreurs. Un script qui se termine silencieusement après un appel réseau raté est pire que l'absence de script, parce qu'il donne l'impression que la tâche a bien tourné. Mets un code de sortie différent de zéro en cas d'échec, pour que le planificateur ou le pipeline s'en rende compte.

Compatibilité avec Node et ses limites réelles

La compatibilité augmente à chaque version, et la plupart des bibliothèques populaires fonctionnent sans changement. Les problèmes apparaissent à trois endroits qu'il vaut la peine de vérifier avant de t'engager dans une migration.

Les modules natifs compilés pour Node posent le plus de problèmes, en particulier les anciens pilotes de bases de données et les bibliothèques de traitement d'images. Certaines parties rarement utilisées de l'API standard se comportent parfois différemment dans les cas limites. Les outils qui supposent un environnement particulier, certains plugins de build par exemple, peuvent avoir besoin d'un contournement.

Une vérification pratique prend un quart d'heure : installe les dépendances avec Bun et lance toute la suite de tests. Si elle passe, le risque de migration est faible. Si elle échoue sur un module natif, tu le sais avant de réécrire la moindre configuration de déploiement.

Les versions de la série 1.3 sont stables et utilisées en production, et chacune referme un peu plus l'écart avec Node. Avant de mettre à jour un projet en production, lis la liste des changements, car le développement avance ici nettement plus vite que dans les runtimes plus matures.

D'où vient la différence de vitesse

Ça aide de comprendre d'où vient cet avantage, car toutes les parties d'un projet ne le ressentent pas de la même façon.

La première source, c'est le moteur. Bun utilise JavaScriptCore plutôt que V8, et il démarre plus vite avec une surcharge mémoire modeste. Pour les processus de courte durée, comme les scripts et les fonctions à la demande, le seul temps de démarrage peut représenter une part notable du total.

La deuxième, c'est le fonctionnement du gestionnaire de paquets. L'installation utilise des opérations de fichiers en parallèle et un cache conservé localement, donc réinstaller les mêmes dépendances tourne bien plus vite que la première passe. C'est pour ça que la plus grande différence se voit en intégration continue, où cette étape se répète sans fin.

La troisième, c'est l'absence de couches intermédiaires. Exécuter un fichier TypeScript ne demande aucun processus de compilation séparé, et le test runner ne charge aucun environnement de modules qui lui soit propre.

Ce que cet avantage ne couvre pas : l'exécution du code applicatif sur une longue durée. Une fois qu'un processus tourne pendant des heures, le compilateur du moteur optimise de toute façon les chemins les plus sollicités, et l'écart entre moteurs s'aplatit. Si ton service est lent à cause de requêtes en base ou d'appels réseau, changer de runtime ne corrige rien.

La façon la plus honnête de juger reste donc la mesure sur ton propre projet : temps d'installation des dépendances, durée de la suite de tests, et temps de démarrage du processus. Ces trois chiffres suffisent généralement à trancher, tandis que les comparaisons synthétiques se traduisent rarement dans le travail quotidien.

Bun face à Node et Deno

OutilPoint fortPoint faibleÀ choisir quand
BunTout en un, installations rapides, TypeScript sans compilationÉcosystème plus jeune, incompatibilités occasionnellesNouveau projet, outils internes, scripts
NodeCompatibilité la plus large, pris en charge par tous les hébergeursOutils séparés pour la construction et les testsProjet en production avec de nombreuses dépendances
DenoPermissions restreintes par défaut, outillage intégréModèle de dépendances différent dans les anciennes versionsScripts qui ont besoin d'un contrôle d'accès

Conseil pratique pour une équipe : la façon la plus simple d'entrer, c'est par l'arrière, en adoptant Bun comme gestionnaire de paquets et test runner tandis que la production reste sur Node. Tu gagnes du temps en intégration continue sans risque sur la production, et après quelques semaines tu sais s'il vaut la peine d'aller plus loin.

Déploiement et hébergement

Tous les hébergeurs ne prennent pas en charge Bun nativement, donc vérifie ça avant de choisir. Une image de conteneur règle la question partout où tu contrôles l'environnement d'exécution.

DOCKERFILE
FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "run", "start"]

Le drapeau qui impose la fidélité au fichier de verrouillage compte ici : sans lui, un build peut installer des versions différentes de celles que tu as testées localement.

Une autre option consiste à construire un exécutable unique contenant à la fois le code et le runtime. Pour des outils en ligne de commande distribués en interne dans une entreprise, c'est pratique, puisque la personne qui le reçoit n'installe rien.

Quand tu déploies sur des plateformes comme Vercel, vérifie si la fonction tourne dans un environnement qui prend en charge Bun, ou si seule l'installation des dépendances l'utilise pendant le build. Ce sont deux choses différentes, et il est facile de les confondre.

Introduire Bun dans une équipe existante

Changer d'outil dans un projet sur lequel travaillent plusieurs personnes échoue rarement pour des raisons techniques. Ça échoue parce que la moitié de l'équipe a un environnement configuré différemment, et que personne n'a envie de passer une journée à diagnostiquer l'erreur de quelqu'un d'autre.

Un ordre qui fonctionne ressemble à ça. D'abord, une personne lance la suite de tests sous Bun et note ce qui casse. Si rien ne casse, vous passez à l'étape deux, qui remplace le gestionnaire de paquets en intégration continue, mais pas encore sur les machines des développeurs. Cette étape se défait avec une seule ligne de configuration et montre tout de suite le gain de temps.

L'étape trois concerne les environnements locaux, avec la suppression de l'ancien fichier de verrouillage dans le même commit. Deux fichiers de verrouillage dans un dépôt sont la source de problèmes la plus courante dans ce genre de migration, donc le basculement doit se faire d'un seul coup plutôt que progressivement.

Change le runtime en dernier et séparément, idéalement en commençant par le service qui présente le moins de risque. Une application interne ou une tâche de fond s'y prête mieux que le service principal destiné aux clients.

Note dans la documentation du projet quelle version vous utilisez et comment l'installer. L'outil avance vite, et quelques versions d'écart peuvent changer un comportement que personne n'a testé.

Erreurs courantes

La première est de migrer tout le projet d'un coup, production incluse. Il est plus sage de commencer par l'installation des dépendances et les tests, et de changer le runtime séparément.

La deuxième est d'avoir deux fichiers de verrouillage dans un seul dépôt. Si une partie de l'équipe utilise npm et une autre Bun, les versions des dépendances dérivent en silence jusqu'au premier bug qui ne se reproduit que sur une seule machine.

La troisième est d'omettre le drapeau de fidélité au fichier de verrouillage dans le pipeline. Un build doit installer exactement ce qui a été testé, pas les versions les plus récentes qui correspondent.

La quatrième est de supposer une compatibilité totale sans vérifier. Lancer la suite de tests sous Bun répond à cette question en un quart d'heure.

La cinquième est d'utiliser le SQLite intégré comme base de données de production pour une application à plusieurs instances. Un fichier sur disque ne se partage pas entre processus de la façon qu'attend une application mise à l'échelle horizontalement.

FAQ

Bun va-t-il remplacer Node.js ?

Pour les nouveaux projets et l'outillage interne, c'est souvent plus pratique, tandis que Node reste le choix le plus sûr partout où la compatibilité avec l'écosystème et la prise en charge universelle par les hébergeurs comptent. Beaucoup d'équipes utilisent les deux : Bun pour les installations et les tests, Node en production.

Est-ce que Bun fonctionne avec Next.js et React ?

Installer les dépendances et lancer des scripts fonctionne sans obstacle. Pour faire tourner le serveur de l'application lui-même, vérifie la compatibilité avec ta version du framework, car certaines parties supposent un environnement Node.

Faut-il changer package.json ?

Non, Bun lit le même fichier et installe depuis le registre npm. C'est le fichier de verrouillage qui change, donc mets-toi d'accord avec ton équipe sur un seul gestionnaire plutôt que d'en maintenir deux.

Les tests Jest passeront-ils sans modification ?

La plupart des tests unitaires, oui, puisque la syntaxe correspond. Les simulations de modules élaborées et les tests dépendants de l'environnement ont généralement besoin d'un ajustement. La vérification la plus rapide consiste à lancer ta suite actuelle avec bun test.

Bun est-il prêt pour la production ?

Oui, la série 1.3 tourne en production, bien qu'il soit sage de figer une version précise et de suivre la liste des changements. Le rythme des sorties est plus élevé que dans les runtimes plus matures, donc mettre à jour sans vérifier invite les mauvaises surprises.

La documentation se trouve sur bun.com/docs, et le code source dans le dépôt GitHub.

À lire aussi