Accueil / Articles

Déplacer un projet Node.js vers Bun : mécanismes internes du temps d’exécution, Lambda et migration

Comprendre ce que Bun remplace dans une chaîne d’outils Node.js, pourquoi il démarre rapidement, comment il exécute TypeScript et sur AWS Lambda, ainsi que la manière de migrer un projet étape par étape.

Éditeur : REACTAPP.TOP6 min de lecture1493 mots

Sources: Everything You Need to Know About Bun: A Beginner’s Guide, Migration, and Architecture

Un service typique Node.js dépend de quatre ou cinq outils distincts avant d’exécuter ne serait-ce qu’une seule ligne de logique métier : un gestionnaire de paquets, un exécuteur TypeScript, un framework de tests et un bundler. Bun regroupe tous ces éléments en un seul exécutable qui remplit les fonctions de runtime, de gestionnaire de paquets, de bundler et d’exécuteur de tests, visant à remplacer Node.js sans modification. Ce guide explique ce que Bun remplace réellement, d’où provient sa vitesse, la manière dont il gère TypeScript et le déploiement serverless, ainsi que comment transférer un projet existant sans surprise. Pour une comparaison plus approfondie basée sur des benchmarks incluant également Deno, consultez Node.js, Deno et Bun comparés.

Ce que Bun remplace dans une chaîne d’outils typique

Exécuter du JavaScript en dehors du navigateur nécessite un environnement d’exécution, et depuis plus d’une décennie, c’est Node.js qui le fournit. Cependant, un projet Node moderne rassemble généralement plusieurs outils indépendants autour de lui :

  • npm ou Yarn pour installer et gérer les paquets ;
  • tsc, ts-node ou tsx pour compiler ou exécuter TypeScript ;
  • Jest ou Vitest pour les tests unitaires ;
  • esbuild, webpack ou Rollup pour créer des bundles destinés à la production.

Bun intègre toutes ces fonctionnalités dans un seul binaire. Son installation fournit à la fois un environnement d’exécution, un gestionnaire de paquets, un outil de bundling, un exécuteur de tests ainsi que la possibilité d’exécuter directement TypeScript, sans configuration supplémentaire. Le véritable avantage réside dans l’élimination des éléments intermédiaires : moins de fichiers de configuration et moins de incohérences de version entre les outils.

Évaluer les avantages et inconvénients par rapport à Node.js

La décision dépend principalement de la vitesse et de l’intégration d’une part, et de la maturité de l’écosystème d’autre part.

Où Bun excelle

  • Une chaîne d’outils intégrée. Il n’y a pas de lanceur de tests, d’outil de bundling ou de transpilateur TypeScript séparés à configurer.
  • Des installations rapides. Selon les mesures propres à Bun, bun install est jusqu’à 30 fois plus rapide que npm ; vos résultats dépendront du projet et de l’état du cache.
  • Démarrages rapides en mode froid. Les processus démarrent en quelques millisecondes, ce qui est particulièrement important dans les environnements serverless où la latence de démarrage en mode froid est facturée et ressentie par les utilisateurs.
  • TypeScript sans configuration. Les fichiers .ts et .tsx s’exécutent directement, sans étape de compilation ni scripts de chargement.
  • Utilisation moindre de la mémoire. Les chiffres indiqués pour les serveurs HTTP montrent généralement une consommation de RAM 25 % à 40 % inférieure par rapport à Node.js ; mesurez votre propre charge de travail avant de vous fier à ces données.
  • Batteries incluses. Les fichiers .env s’chargent automatiquement sans dotenv, et l’environnement de exécution intègre des clients intégrés pour SQLite, Postgres, S3 et Redis.
  • Où Node.js reste supérieur

    • Maturité. Node.js dispose d’une expérience en environnement de production de plus de 15 ans ainsi que de versions officielles de support à long terme sur lesquelles les entreprises planifient leurs actions. Bun est plus récent et travaille encore à combler les écarts de compatibilité dans certains cas particuliers.
    • Add-ons natifs. La grande majorité des paquets npm (souvent estimée à plus de 95 %) fonctionnent sans modification, mais les paquets plus anciens qui compilent du code C++ via node-gyp peuvent encore rencontrer des problèmes.

    Pourquoi Bun démarre et fonctionne rapidement

    Ce n’est pas simplement « Zig bat C++ »

    Bun est écrit en Zig, et il est tentant d’attribuer sa vitesse au langage. En pratique, le Zig compilé et le C++ compilé fonctionnent à peu près à la même vitesse sur le CPU. La différence provient de la conception. Node.js intègre de nombreuses abstractions accumulées au fil des ans, tandis que Bun a réimplémenté les opérations d’entrée/sortie de fichiers, le traitement des chaînes de caractères et les réseaux à partir de zéro, en mettant l’accent sur l’évitement des allocations mémoire inutiles.

    Un moteur JavaScript différent

    Node.js s’exécute sur V8, le moteur développé par Google pour Chrome. Bun utilise JavaScriptCore, le moteur conçu par Apple pour Safari. JavaScriptCore est optimisé pour un démarrage rapide et une gestion agressive de la mémoire, ce qui explique en partie les performances de démarrage rapide de Bun. Chaque moteur présente des points forts dans certains types de tâches ; par conséquent, les services à long terme et gourmands en ressources informatiques doivent faire l’objet de tests de performance plutôt que d’être considérés comme automatiquement améliorés.

    Les avantages ne se limitent pas à macOS

    Bien que JavaScriptCore ait été développé pour Safari, tant celui-ci que Bun sont compilés nativement pour Linux sur x86_64 et ARM64, ainsi que pour Windows. Les caractéristiques de performance se transposent aux serveurs cloud ordinaires, quel que soit le système d’exploitation.

    Exécuter Bun sur AWS Lambda

    Bun peut servir des backends HTTP via son API native Bun.serve() ou à l’aide de frameworks bien connus tels qu’Express, Fastify et Hono. Sur AWS Lambda, ses démarrements rapides en mode froid permettent de réduire à la fois la latence et les coûts d’exécution. Lambda ne propose pas de runtime Bun géré à choisir dans la console, ce qui pousse les équipes à opter généralement pour l’une de ces trois approches :

    • Couche de runtime personnalisée. Publier le binaire bun en tant que couche Lambda et l’exécuter sur l’image Linux de base provided.al2023.
    • Image de conteneur. Construire la fonction à partir de l’image Docker officielle oven/bun, la publier sur Amazon ECR et la déployer en tant que conteneur.
    • Compiler avec Bun, exécuter sur Node. Utiliser bun build pour regrouper le code en un seul fichier .js, puis exécuter ce fichier sur le runtime Node.js géré par Lambda.

    La troisième option est la plus conservatrice : vous bénéficiez du rapide empaquetage de Bun tout en conservant l’environnement d’exécution pris en charge par AWS en production. Vous renoncez aux API spécifiques à l’environnement d’exécution de Bun ainsi qu’à son avantage en termes de démarrage rapide, ce qui la rend adaptée aux équipes souhaitant adopter Bun progressivement.

    Comment Bun exécute TypeScript

    Vous exécutez les fichiers directement avec la commande bun, qu’il s’agisse de JavaScript ou de TypeScript : bun index.js, bun index.ts ou bun --watch index.ts pour une relance automatique en cas de modification des fichiers.

    Lorsque vous exécutez un fichier .ts, le transpileur de Bun supprime les annotations de type en temps réel et le fait entièrement en mémoire. Il ne crée pas de dossier dist/, de répertoire .build/ ni de fichiers .js temporaires sur le disque.

    Il convient de noter que le retrait des types n’est pas identique à leur vérification. Afin de garantir un démarrage instantané, Bun ne effectue aucune vérification statique des types en temps de exécution, de sorte que le code contenant des erreurs de type continuera de fonctionner. La vérification des types se fait dans votre éditeur, comme VS Code ou Cursor, ainsi que dans les processus CI, où vous pouvez exécuter bunx tsc --noEmit. Considérez cette étape CI comme obligatoire.

    Migrer un projet Node.js existant étape par étape

    Puisque Bun suit les API de Node et les conventions npm, un projet simple se migre généralement avec quelques modifications seulement. Faites-le sur une branche et exécutez votre suite complète de tests à la fin.

    Étape 1 : Supprimer les anciens modules et fichiers de verrouillage

    Partez d’un arbre de dépendances propre afin que Bun résolve tout automatiquement : supprimez le dossier node_modules ainsi que le fichier de verrouillage utilisé par le projet, qu’il s’agisse de package-lock.json, yarn.lock ou pnpm-lock.yaml.

    La suppression de l’ancien fichier de verrouillage signifie que les versions sont à nouveau déterminées à partir des plages indiquées dans package.json, ce qui vous permet d’obtenir des versions plus récentes que celles utilisées précédemment. Si une reproductibilité parfaite est essentielle, vérifiez si votre version de Bun peut migrer un fichier de verrouillage existant et examinez les différences entre les versions résolues.

    Étape 2 : Installer avec Bun

    Exécutez bun install. Bun lit votre package.json existant, télécharge les packages et crée un fichier bun.lock que vous devez soumettre au contrôle de version.

    Étape 3 : Mettre à jour les scripts dans package.json

    Remplacez les références à node, ts-node ou tsx dans la section scripts :

    • start : "bun src/index.js" remplace "node src/index.js" ;
    • dev : "bun --watch src/index.ts" remplace "ts-node-dev src/index.ts" ;
    • test : "bun test" remplace "jest" ou "vitest".

    L’exécuteur de tests est largement compatible avec l’API de Jest, mais les tests qui dépendent fortement du mocking ou des configurations spécifiques à Jest pourraient nécessiter des ajustements.

    Étape 4 : Changer les définitions TypeScript

    Pour les projets TypeScript, remplacez les définitions de type Node par celles de Bun en exécutant bun remove @types/node suivi de bun add -d @types/bun.

    Mettez ensuite à jour la valeur de types dans compilerOptions de tsconfig.json. Les guides anciens y font référence à "bun-types" ; avec le package actuel @types/bun, cette valeur est généralement "bun" ; vérifiez donc la documentation TypeScript de Bun pour la version que vous installez.

    Étape 5 : Remplacer les modules incompatibles

    Les modules standard de Node tels que node:fs, node:path et node:http fonctionnent sans modification. Les packages qui dépendent de bindings natifs en C++ sont généralement à l’origine des problèmes, et plusieurs d’entre eux disposent de substituts simples :

    • remplacer bcrypt par bcryptjs ou @node-rs/bcrypt ;
    • remplacer sqlite3 par bun:sqlite intégré à Bun ;
  • Supprimez les appels à dotenv, car Bun charge automatiquement .env.
  • Soyez prudent avec ce dernier changement. Si le même code doit encore s’exécuter sous Node, par exemple dans l’installation Lambda uniquement destinée à la compilation mentionnée ci-dessus, conservez dotenv ou chargez explicitement la configuration afin que le comportement ne diffère pas entre les environnements d’exécution.

    Points clés

    • La principale valeur de Bun réside dans sa consolidation : un seul binaire remplace le gestionnaire de paquets, l’exécuteur TypeScript, le framework de tests et le bundler.
    • Sa vitesse provient d’une réimplémentation optimisée en termes d’allocation mémoire et du démarrage rapide de JavaScriptCore, et non du fait que Zig soit intrinsèquement plus rapide que C++.
    • Bun supprime les types TypeScript en mémoire mais ne les vérifie jamais, il est donc nécessaire de conserver tsc --noEmit dans les processus CI.
    • Au sein de Lambda, vous pouvez choisir entre une couche personnalisée, une image de conteneur, ou bien compiler avec Bun puis exécuter sur un Node.js géré.
    • La migration est principalement mécanique, mais veillez à la résolution répétée du fichier de verrouillage, aux extensions natives et à tout comportement dépendant du chargement automatique de .env.