Si vous démarrez un projet JavaScript aujourd’hui, une question autrefois assez simple peut devenir étrangement philosophique avant même d’avoir écrit une ligne utile.
Avec quoi allez-vous l’exécuter ?
Node.js reste l’évidence historique. Mais il y a aussi Deno, Bun, Cloudflare Workers et son runtime workerd, LLRT chez AWS, QuickJS dans quantité de projets embarqués, Hermes dans React Native, JavaScriptCore sous Bun, V8 sous Node et Deno,1718 sans compter les moteurs construits pour des téléviseurs, des microcontrôleurs, des applications natives ou simplement pour voir jusqu’où l’expérience peut aller.
Jamie Brandon a passé plus d’un an à cartographier cette ménagerie dans un article publié en juillet 2025.1 Un mois plus tard, Theo Browne en a fait une longue lecture commentée, avec la même question de départ : pourquoi diable avons-nous besoin de tout ça si, au bout du compte, le code reste du JavaScript ?2
Vu depuis un package.json, la situation ressemble facilement à une maladie de l’écosystème. JavaScript avait déjà plusieurs gestionnaires de paquets, plusieurs bundlers, plusieurs frameworks et plusieurs manières très sérieuses de transformer un bouton en composant. Il fallait apparemment aussi plusieurs machines virtuelles pour exécuter console.log().
Mais la liste devient beaucoup moins absurde dès qu’on cesse de comparer les logos et qu’on regarde la machine sur laquelle le code doit vivre.
Un processus serveur qui tourne pendant trois semaines n’a pas le même problème qu’une fonction qui doit démarrer pendant une requête. Une application iPhone n’a pas les mêmes contraintes qu’un serveur Linux. Un microcontrôleur disposant de quelques dizaines de kilo-octets ne peut évidemment pas embarquer ce qu’un ordinateur de bureau considère comme une dépense raisonnable. Un réseau edge veut charger du code près de l’utilisateur sans lancer une petite machine virtuelle complète pour chaque script.
Ce ne sont pas des variations marketing du même problème.
Ce sont des problèmes différents qui parlent la même langue.
Avant les runtimes, il faut séparer le moteur du reste
Une partie de la confusion vient du vocabulaire.
V8, JavaScriptCore, SpiderMonkey, QuickJS ou Hermes sont d’abord des moteurs JavaScript. Leur travail central consiste à comprendre le langage et à exécuter le programme. Selon leur architecture, ils interprètent du bytecode, compilent certaines parties juste à temps, produisent du code en avance, gèrent la mémoire et optimisent les fonctions qui deviennent chaudes.
Un runtime ajoute le monde autour.
Il décide comment lire un fichier, ouvrir une socket, attendre un timer, charger un module, résoudre un paquet, exposer des variables d’environnement, parler au système d’exploitation, gérer des permissions ou répondre à une requête HTTP. Node utilise V8, mais Node n’est pas V8.17 Deno utilise aussi V8,18 tout en proposant historiquement un modèle de sécurité, un système de modules et un ensemble d’outils différents. Bun utilise JavaScriptCore, le moteur de Safari, puis reconstruit autour de lui un environnement qui vise une forte compatibilité avec Node.78
Cette distinction explique déjà pourquoi remplacer un moteur ne remplace pas automatiquement une plateforme.
Deux runtimes peuvent partager V8 et avoir une manière radicalement différente d’exécuter votre application. Deux autres peuvent utiliser des moteurs différents tout en essayant de faire tourner le même projet Node sans modification.
La compétition se déroule donc sur plusieurs couches en même temps, ce qui est une façon très efficace de produire des discussions Internet où tout le monde a raison à propos d’un objet différent.
Node a gagné un serveur qui existait déjà
Quand Node.js apparaît en 2009,1 son geste important n’est pas simplement de sortir JavaScript du navigateur. Des moteurs JavaScript embarqués dans d’autres logiciels existaient déjà.
Node donne surtout à JavaScript une forme de serveur généraliste.
Le programme démarre, garde un processus vivant, écoute des connexions, accède au système de fichiers, charge des bibliothèques natives et attend du travail. Le modèle fonctionne particulièrement bien quand le coût du démarrage est amorti sur une longue durée. Le moteur peut optimiser le code au fil de son exécution, les connexions peuvent rester ouvertes, des caches peuvent vivre en mémoire et l’environnement ressemble suffisamment à un système Unix classique pour que des décennies d’outils sachent quoi en faire.
Cette combinaison a été assez bonne pour devenir une cible massive. L’écosystème npm s’est construit autour. Les frameworks ont supposé ses API. Les addons natifs ont supposé ses interfaces. Les services cloud ont proposé « Node.js » comme environnement prêt à l’emploi.
Pendant quelques années, demander quel runtime JavaScript utiliser côté serveur revenait pratiquement à demander quel Node utiliser.
Puis le serveur lui-même a commencé à changer de forme.
Cloudflare n’avait pas besoin d’un meilleur Node
L’edge computing rend le problème plus visible que presque n’importe quel autre environnement.
Une plateforme comme Cloudflare Workers veut exécuter de petits morceaux de code dans de nombreux points de présence répartis dans le monde. Elle veut pouvoir charger le code d’un client très vite, traiter une requête, puis laisser la même infrastructure exécuter le code d’un autre client sans réserver un processus entier et une pile système complète à chacun.
Cloudflare a donc construit Workers autour des isolates de V8 plutôt qu’autour d’un modèle où chaque fonction possède son propre processus Node. Sa documentation décrit un même runtime capable d’héberger des centaines ou des milliers d’isolates, chacun disposant de sa mémoire séparée.3
Le moteur n’a pourtant pas changé. C’est toujours V8.
Ce qui change, c’est l’unité d’isolation, le cycle de vie et l’API disponible.
workerd, le runtime open source qui partage le code de Workers, se décrit explicitement comme « server-first » et basé sur les standards du Web. Son interface pousse fetch(), Request, Response, les streams et d’autres primitives déjà connues du navigateur, plutôt que d’essayer de recréer tout un système Unix dans chaque exécution.4
Cette décision achète quelque chose et en abandonne autre chose.
Démarrer un petit isolate dans un moteur déjà vivant coûte beaucoup moins que lancer un environnement complet. En échange, le code n’a pas naturellement la même liberté qu’un processus Node traditionnel. Le système de fichiers, les exécutables arbitraires, la durée de vie de l’état ou certaines hypothèses sur le processus ne fonctionnent pas de la même manière.
Dans sa vidéo, Theo passe plusieurs minutes à dessiner cette différence. Son observation utile n’est pas que V8 serait « mauvais pour l’edge ». Cloudflare utilise précisément V8. Le point est que prendre le modèle de processus habituel de Node et le reproduire partout à l’edge serait une mauvaise utilisation des ressources.2
Le runtime change parce que l’ordinateur logique a changé.
LLRT et QuickJS choisissent encore une autre réponse
AWS a exploré une direction presque opposée avec LLRT, Low Latency Runtime.
Au lieu de conserver un gros moteur partagé puis d’isoler des scripts à l’intérieur, LLRT cherche un runtime suffisamment léger pour que le coût de son propre démarrage devienne beaucoup moins gênant. Le projet est écrit en Rust et repose sur QuickJS.5
QuickJS n’essaie pas de battre V8 sur toutes les dimensions.
La documentation de Fabrice Bellard le présente comme un moteur petit et facilement embarquable, composé de quelques fichiers C, sans dépendance externe, avec un temps de création très faible pour une instance de runtime.6 La version de juin 2026 supporte l’essentiel d’ES2025 et continue d’assumer ce compromis : faible poids, démarrage rapide, interpréteur simple, intégration facile.
C’est exactement le genre de décision qui rend la phrase « quel moteur est le plus rapide ? » presque inutilisable sans préciser ce qu’on mesure.
Sur un programme qui tourne longtemps et exécute la même logique des millions de fois, un moteur avec des couches d’optimisation sophistiquées peut amortir une phase de chauffe et gagner énormément ensuite. Sur une fonction minuscule qui démarre, fait trois appels et disparaît, le temps passé avant le premier travail utile peut compter davantage que le débit maximal obtenu dix secondes plus tard.
LLRT reste explicitement expérimental. AWS Labs publie ses propres benchmarks et ses propres promesses de coût, qu’il serait imprudent de transformer en loi physique.5 Le projet reste néanmoins une démonstration très propre du raisonnement : si votre problème principal est le cold start d’une petite fonction serverless, vous pouvez vouloir un moteur conçu autour de ce problème plutôt qu’un moteur de navigateur devenu serveur généraliste par une longue chaîne d’histoire.
Sur un téléphone, « rapide » signifie encore autre chose
Hermes raconte la même histoire avec des contraintes différentes.
React Native utilisait initialement JavaScriptCore. En 2019, Meta a présenté Hermes, un moteur conçu spécifiquement pour les applications React Native.10 Lorsqu’il est devenu le moteur par défaut en 2022, l’équipe expliquait qu’elle optimisait trois métriques très concrètes : le temps avant interaction, la taille de l’application et la consommation mémoire.11
L’une des idées importantes d’Hermes est de compiler le JavaScript en bytecode en avance et d’embarquer ce résultat avec l’application. Le téléphone n’a donc pas besoin de refaire autant de travail de compilation au lancement.11
Ce choix serait étrange pour certains usages serveur dynamiques. Pour une application mobile que l’on construit avant de la distribuer et dont on veut afficher l’interface le plus vite possible après un tap sur une icône, il devient beaucoup plus logique.
En février 2026, React Native 0.84 a encore déplacé cette couche en faisant d’Hermes V1 le moteur par défaut, avec une nouvelle génération du compilateur et de la VM.12
Le détail intéressant est moins de savoir si Hermes gagne tel benchmark que de voir une équipe construire un moteur JavaScript entier parce que le contexte d’exécution justifie suffisamment d’optimisations particulières.
Le langage reste ECMAScript. La machine, elle, impose ses priorités.
Puis il y a les ordinateurs qui n’ont presque rien
L’article de Jamie devient franchement amusant lorsqu’il quitte les serveurs et les téléphones pour descendre jusqu’aux microcontrôleurs.1
Node paraît léger sur un Mac moderne. Il cesse rapidement de l’être lorsque la RAM se compte en kilo-octets.
Duktape, JerryScript, Espruino, Moddable ou Elk ont été construits pour des environnements où la taille du moteur est une contrainte matérielle directe. Certaines de ces machines contrôlent des capteurs, des objets domestiques, des petits écrans ou des cartes embarquées. Leur objectif n’est pas de démarrer Next.js plus vite. Il est parfois simplement de réussir à faire entrer un interpréteur et le programme dans le silicium disponible.
À ce niveau, le fait que JavaScript fonctionne du tout devient presque plus intéressant que sa place dans un benchmark.
Cela révèle une raison importante de la multiplication des moteurs : JavaScript dispose d’un bassin immense de développeurs et de bibliothèques, donc il existe une valeur économique ou pratique à amener ce langage dans un nouvel environnement, même si cela exige de reconstruire la machine qui l’exécute.
La plateforme suit les développeurs.
C’est aussi ce qui explique des projets plus étranges : moteurs JavaScript écrits en Rust, en Java, en Zig, moteurs intégrés dans des bases de données, des outils graphiques, des téléviseurs ou des applications qui utilisent JavaScript comme langage de script. Parfois le runtime est un produit. Parfois c’est une pièce invisible dans une autre machine.
Les classer tous dans une seule course serait comme demander si un moteur de Formule 1 est « meilleur » que celui d’un groupe électrogène. Ils font tous tourner quelque chose, ce qui ne suffit pas à organiser un championnat raisonnable.
La partie la plus étrange : les concurrents copient Node
Si l’histoire s’arrêtait ici, on aurait simplement une explosion de niches.
Mais depuis quelques années, un mouvement inverse se produit au-dessus des moteurs.
Deno est le meilleur exemple.
Ryan Dahl a présenté Deno en 2018 avec une critique assez nette1 de plusieurs décisions accumulées dans Node : node_modules, le système de modules, certaines API historiques, l’absence d’un modèle de permissions explicite. Deno proposait des imports par URL, TypeScript intégré et une surface plus proche des standards Web.
Puis le monde réel a rappelé une petite chose désagréable : l’écosystème Node est gigantesque.
Deno a progressivement ajouté la compatibilité npm, package.json, node_modules, CommonJS et les modules node:. Sa documentation actuelle dit que la majorité du code Node peut fonctionner sans modification et suit publiquement le passage de la propre suite de tests de Node.9
Ce n’est pas un échec de Deno.
C’est probablement le signe le plus clair de ce que Node a réellement gagné.
Node a surtout produit un contrat implicite entre des millions de programmes, au-delà de sa propre base d’utilisateurs. Un nouveau runtime peut proposer un meilleur modèle de permission, un meilleur outil de formatage ou une autre architecture interne. S’il veut accueillir les applications existantes, il doit néanmoins comprendre le dialecte opérationnel que Node a laissé derrière lui.
Bun fait le même choix de manière encore plus frontale. Sa documentation le présente comme un runtime JavaScriptCore conçu pour être un remplacement de Node et traite les incompatibilités avec des projets Node comme des bugs à corriger.78
Voilà une situation assez remarquable.
Les moteurs divergent.
Les runtimes se concurrencent.
Et pourtant ils passent une quantité croissante d’énergie à faire fonctionner le même code.
Node-API transforme même Node en interface indépendante de V8
Cette convergence descend jusqu’aux extensions natives.
Historiquement, un addon Node écrit en C ou C++ pouvait dépendre assez directement des détails de V8. C’est puissant, mais cela attache le module à une version et à un moteur précis.
Node-API a été créé pour casser cette dépendance.
La documentation Node le décrit comme une API pour addons natifs indépendante du moteur JavaScript sous-jacent et stable au niveau ABI à travers les versions de Node.13 Le code natif manipule des valeurs abstraites plutôt que de s’accrocher directement aux structures de V8.
À l’origine, le bénéfice évident était de rendre les addons moins fragiles quand Node mettait V8 à jour.
Mais l’abstraction s’est révélée plus générale.
Deno supporte aujourd’hui des addons Node-API.9 Bun a réimplémenté une grande partie de l’interface au-dessus de JavaScriptCore.8 D’autres runtimes et frameworks peuvent faire le même travail.
La conséquence est presque comique : une API portant le nom de Node devient un des moyens de ne plus dépendre du moteur historique de Node.
C’est une petite inversion, mais elle montre où se déplace la valeur. Plus l’écosystème se stabilise autour d’interfaces communes, moins le code applicatif a besoin de savoir quelle machine virtuelle exacte se trouve dessous.
Le Web est l’autre grand langage commun
L’autre force de convergence vient du navigateur.
fetch, URL, Request, Response, ReadableStream, TextEncoder, Web Crypto et de nombreuses autres API sont nées ou ont été standardisées pour le Web. Elles ont ensuite migré côté serveur.
Ce déplacement est important parce qu’il donne aux runtimes une surface commune qui ne dépend pas de Node.
workerd revendique explicitement cette orientation vers les standards Web.4 Deno en a fait un principe depuis ses débuts. Node lui-même a intégré au fil du temps des API du Web. Bun expose à la fois des API Web et une compatibilité Node.7
Le résultat n’est pas une compatibilité parfaite. Un navigateur ne possède toujours pas le même système de fichiers qu’un serveur. Un Worker Cloudflare n’est pas un processus Linux. Une fonction Lambda n’a pas la durée de vie d’un démon. Les permissions, les sockets, les timers, les modules et les limites de ressources continuent de produire des différences très réelles.
Mais une partie croissante du code ordinaire peut parler un vocabulaire commun.
La guerre des runtimes commence alors à ressembler moins à plusieurs langages concurrents qu’à plusieurs systèmes d’exploitation essayant d’implémenter les mêmes appels les plus utiles.
WinterTC accepte officiellement qu’il n’y aura probablement pas un seul gagnant
Cette convergence est devenue suffisamment sérieuse pour produire son propre travail de standardisation.
WinterCG avait été créé pour définir une base d’API communes entre environnements JavaScript côté serveur. En janvier 2025, ce travail a évolué vers un comité technique Ecma, TC55, surnommé WinterTC.14
Le mandat est très explicite : standardiser une « minimum common API » basée autant que possible sur les standards existants du Web, puis définir des niveaux de conformité vérifiables pour les runtimes serveur.15
C’est une décision assez différente d’une tentative de couronner un runtime universel.
Elle part du principe que Node, Deno, Workers et les autres vont continuer à exister, puis demande comment rendre cette pluralité moins coûteuse pour les développeurs.
En juin 2026, TC55 a même publié ECMA TR/114, Runtime keys, un rapport qui définit des identifiants cohérents pour nommer les différents runtimes dans les fichiers de configuration, manifests et mécanismes de détection.16
C’est un petit standard, presque administratif.
Mais il raconte bien l’époque : il existe désormais assez de runtimes JavaScript qu’un organisme de standardisation doit définir comment les nommer proprement.
Et au lieu de signaler l’effondrement de l’écosystème, ce travail sert précisément à faire circuler le code entre eux.
Pour les librairies, le runtime devient une cible qu’il faut nommer
La multiplication des runtimes produit tout de même un coût très concret pour les personnes qui fabriquent des outils.
Une bibliothèque ne peut pas toujours supposer que process, le système de fichiers, les sockets ou une API particulière existent. Un framework doit parfois choisir un adaptateur selon l’endroit où il tourne. Un paquet peut vouloir fournir un chemin différent pour Node, Deno ou Bun. Et les outils de build doivent distinguer un environnement sans transformer chaque projet en collection de tests bricolés sur le nom d’une variable globale.
C’est précisément le genre de problème auquel le rapport Runtime keys essaie de donner une réponse minuscule mais stable : des identifiants canoniques comme node, deno, bun ou workerd, avec une gouvernance commune pour éviter que chaque outil invente ses propres noms.16
Le document prend même soin de préciser que ces clés servent à informer et identifier un runtime, pas à remplacer la détection de capacités.16 C’est une distinction saine. Savoir qu’un programme tourne sous Bun ne garantit pas qu’une API précise soit présente dans cette version. Tester la capacité reste plus robuste lorsque c’est possible.
Cette petite couche administrative montre aussi une maturation de l’écosystème. Au début d’une nouvelle plateforme, chacun expose ses primitives et espère attirer assez de développeurs pour devenir la convention. Quand plusieurs plateformes survivent, le travail moins spectaculaire commence : nommer les choses, documenter les intersections, définir ce qui peut être commun et laisser le reste diverger.
Ce n’est pas la fin de la fragmentation. C’est la manière de la rendre supportable.
La fragmentation se déplace sous la ligne de flottaison
On peut donc regarder la dernière décennie de deux manières.
La première donne le vertige : V8, SpiderMonkey, JavaScriptCore, QuickJS, Hermes, Node, Deno, Bun, workerd, LLRT, runtimes embarqués, forks spécialisés, moteurs de recherche, frameworks natifs et quelques projets dont le nom semble avoir été choisi cinq minutes avant de pousser le dépôt GitHub.
La seconde est presque l’inverse.
Un développeur peut aujourd’hui écrire une fonction avec fetch, utiliser un paquet npm, charger un addon via Node-API et découvrir que plusieurs runtimes très différents essaient activement de faire fonctionner ce programme.
La diversité augmente dans l’implémentation pendant que la surface commune augmente elle aussi.
C’est peut-être la tendance la plus importante à retenir.
Nous avons déjà vu ce type de déplacement ailleurs en informatique. Le matériel peut devenir plus varié pendant qu’une couche logicielle absorbe les différences. Les processeurs changent, mais un programme compilé à travers les bons outils ne demande pas au développeur de penser en permanence aux détails du pipeline CPU. Les réseaux sont extraordinairement hétérogènes, mais HTTP offre une abstraction commune suffisamment bonne pour construire le Web.
JavaScript semble suivre une trajectoire comparable à son échelle.
Le moteur devient une décision d’infrastructure.
Le runtime devient une décision de plateforme.
Et l’application essaie de rester au-dessus.
Cela ne veut pas dire que le choix n’a plus d’importance
L’idée d’une convergence peut facilement devenir un autre slogan idiot : « écrivez une fois, exécutez partout ».
Nous avons déjà fait cette blague avec suffisamment de technologies.
Les différences restent importantes dès qu’un programme utilise autre chose que la couche commune.
Un service qui lance ffmpeg, ouvre des sockets particulières, dépend de threads natifs, conserve beaucoup d’état en mémoire ou utilise un addon très spécifique ne se déplacera pas magiquement vers un Worker edge parce qu’il appelle aussi fetch().
À l’inverse, une fonction conçue pour un environnement isolé et éphémère peut faire des hypothèses de sécurité ou de cycle de vie qui ne correspondent pas à un serveur Node traditionnel.
Même la compatibilité Node de Bun et Deno est un travail continu, précisément parce que Node n’est pas une petite spécification figée. C’est une plateforme accumulée pendant plus de quinze ans, avec des comportements, des bugs devenus contrats, des modules natifs et des outils qui inspectent parfois des détails que personne n’avait prévu de standardiser.89
La portabilité a donc un gradient.
Le JavaScript pur est très portable. Les API Web communes le sont souvent. Les API Node très répandues le deviennent progressivement. Puis chaque pas vers les capacités spécifiques d’une plateforme réduit la distance que le programme peut parcourir sans adaptation.
Ce n’est pas un défaut. C’est simplement le prix d’utiliser les avantages particuliers de la machine choisie.
Le bon runtime dépend davantage du cycle de vie que du benchmark
Pour un projet concret, cette histoire donne une règle plus utile qu’un classement de performances.
Avant de demander « lequel est le plus rapide ? », il faut demander combien de temps le programme vit, ce qu’il a le droit de toucher et quelle partie de son coût importe réellement.
Un serveur API généraliste avec beaucoup de dépendances Node et des processus durables a de bonnes raisons de rester sur Node. Un projet qui veut remplacer progressivement l’outillage Node peut regarder Deno ou Bun en fonction de ses contraintes. Une fonction exécutée à l’edge doit être pensée autour du modèle d’isolation et des limites de la plateforme qui l’héberge. Une Lambda minuscule très sensible au démarrage peut rendre un projet comme LLRT intéressant à tester, en gardant son statut expérimental en tête. Une application React Native bénéficie d’un moteur conçu autour du démarrage et de la mémoire mobile sans que son équipe ait à « choisir Hermes » comme elle choisirait un serveur.
Il n’y a rien de très satisfaisant à répondre « ça dépend ».
Malheureusement, les ordinateurs n’ont jamais promis d’organiser leurs compromis pour rendre les fils Hacker News plus simples.
La bonne nouvelle est ailleurs : le choix du runtime devient moins souvent un choix de langage et davantage un choix d’exécution.
Node pourrait gagner la guerre même si tout le monde n’exécute plus Node
C’est peut-être la conclusion la plus étrange de cette prolifération.
Deno a été créé en partie pour corriger Node, puis a appris à lire package.json et à exécuter ses paquets. Bun utilise un autre moteur, mais investit massivement dans la compatibilité Node. Node-API permet aux extensions natives de se détacher du moteur V8. WinterTC cherche une base qui fonctionne au-dessus de plusieurs environnements. Pendant ce temps, les API du Web continuent de traverser la frontière navigateur/serveur.
Le résultat possible n’est donc pas la victoire d’un runtime unique.
C’est un monde où Node devient une compatibilité, le Web devient une compatibilité, et le runtime devient progressivement un détail que l’on choisit pour ses contraintes physiques.
Cela ne fera pas disparaître Node, Deno, Bun ou Workers. Au contraire. Ils peuvent continuer à se différencier précisément parce que la couche commune limite le coût de cette différence.
Jamie terminait son inventaire en expliquant qu’il n’existe pas de « meilleur » runtime parce que le démarrage, les performances longues, la taille, les API et l’accès natif tirent dans des directions différentes.1 Theo reprenait la même idée dans sa vidéo : les environnements ne cherchent tout simplement pas à optimiser le même ordinateur.2
Un an plus tard, le détail le plus intéressant est que l’écosystème semble avoir accepté cette réponse.
Il ne cherche plus seulement à fabriquer un vainqueur.
Il construit les standards qui permettront d’en avoir beaucoup sans obliger chaque programme à s’en soucier.