Comment Deno 2.x a résolu discrètement les problèmes de compatibilité avec Node et la lassitude liée aux outils
Cet article passe en revue les versions 2.0 à 2.9 de Deno, montrant comment la compatibilité avec npm, les ensembles de permissions et les outils intégrés ont éliminé les obstacles qui poussaient autrefois les développeurs à l’abandonner.
Cette mise à l’épreuve a eu lieu il y a cinq ans.
Le mois dernier, un petit projet secondaire nécessitait un serveur HTTP. Un nouveau répertoire, deno init, et dix minutes plus tard, un serveur fonctionnel était prêt, avec TypeScript, des tests, du formatage et des outils de vérification déjà intégrés. Aucun fichier tsconfig à écrire. Pas de configuration pour Prettier. Pas de configuration pour ESLint. Aucun fichier jest.config.ts en vue. Pas non plus de package.json.
Vérifier la version a montré 2.9.3.
Il s’avère que Deno a sorti dix mises à jour mineures entre octobre 2024 et juillet 2026, chacune résolvant discrètement l’un des problèmes exacts qui avaient conduit à son abandon initial. Rien de tout cela n’a été remarqué sur le moment, car l’outil avait déjà été classé mentalement dans la catégorie « bonne idée, pas prête pour un usage réel », et il n’y avait aucune raison de le reconsidérer.
Voici un aperçu de ce qui a réellement changé, une ancienne plainte à la fois.
Plainte 1 : les paquets npm étaient considérés comme de second ordre
C’était le point bloquant pour de nombreux développeurs, y compris celui-ci. Sous Deno 1.x, tout ce qui n’était pas publié sur deno.land/x ou qui ne disposait pas d’URL de module ES appropriées ne fonctionnait tout simplement pas. La séparation entre le monde Deno et celui de npm semblait destinée à persister.
Deno 2.0, sorti en octobre 2024, a complètement comblé cette lacune. Vous pouvez désormais importer n’importe quel paquet du catalogue de plus de deux millions de paquets de npm directement, en utilisant le spécificateur npm: :
import express from "npm:express@5";
import { PrismaClient } from "npm:@prisma/client";
Si vous préférez continuer à utiliser un package.json, cela est également pris en charge. Deno le analyse, récupère les dépendances depuis le registre npm et peut même générer un dossier local node_modules si vous activez cette option. Les registres privés fonctionnent également via .npmrc, exactement comme dans Node.
La statistique qui a vraiment convaincu : plus de 75 pour cent du propre ensemble de tests de Node s’exécute désormais avec succès sous Deno. Il ne s’agit pas d’un simple substitut superficiel — cela indique une compatibilité réelle et vérifiée.
Le moment décisif a été lorsque Deno a été appliqué à un projet Express existant. Aucune instruction d’import n’a eu besoin d’être modifiée. Un fichier deno.json a été ajouté avec "nodeModulesDir": "auto", deno install a été exécuté, et les installations en mode froid ont pris environ 900 ms, contre plus de 3 secondes avec npm dans les mêmes conditions (testé avec un cache vide sur un projet utilisant React, Vite, Babel et ESLint). Le serveur a fonctionné correctement dès la première tentative.
Plainte 2 : le système de permissions était gênant dans la pratique
Le modèle de permissions semblait logique en théorie. En pratique, cela signifiait de devoir réécrire quelque chose comme ceci à chaque exécution :
deno run --allow-read=./data --allow-write=./data --allow-net=api.example.com --allow-env main.ts
Oublier un drapeau fait mourir le processus. Ajouter une nouvelle dépendance qui lit par hasard une variable d’environnement fait également mourir le processus. Cela ressemblait plus à la configuration de règles de pare-feu qu’à la création d’un projet secondaire.
Deno 2.5, sorti en septembre 2025, a introduit des ensembles de permissions définis directement dans le fichier de configuration. Vous declarez des ensembles nommés sous la clé "permissions" dans deno.json, puis vous les invoquez avec le drapeau -P (abréviation de --permission-set) :
{
"permissions": {
"default": {
"read": ["./src", "./data"],
"write": ["./data"],
"net": ["api.example.com", "0.0.0.0:3000"],
"env": true
},
"test": {
"read": true,
"write": ["./tmp"],
"net": false
}
}
}
À partir de là, l’exécution de deno run -P main.ts charge automatiquement l’ensemble « par défaut » présent dans deno.json du répertoire de travail, ou vous pouvez exécuter deno test -P=test pour appliquer un ensemble distinct uniquement pour les tests. Le code de test et le code en production disposent de scopes d’autorisation différents sans avoir à modifier ne serait-ce qu’un seul paramètre en ligne de commande. Les garanties de sécurité sous-jacentes n’ont pas changé — la complexité liée à leur gestion a simplement disparu.
Il existe également une variable d’environnement DENO_AUDIT_PERMISSIONS, qui génère un fichier de journal JSONL enregistrant chaque vérification d’autorisation effectuée pendant l’exécution du programme. Cela permet de voir précisément ce que vos dépendances tentent d’accéder, sans avoir à examiner leur code source.
Plainte 3 : J’avais toujours besoin d’une multitude d’outils supplémentaires
La bénédiction et le fardeau de Node résident dans le fait que presque toutes les responsabilités sont déléguées à un package distinct. Besoin de mise en forme ? Utilisez prettier. Vérification de la syntaxe ? ESLint et quelques plugins. Tests ? Jest ou Vitest, ainsi qu’un fichier de configuration pour gérer les transformations TypeScript. Vérification des types ? tsc, configuré indépendamment de ce qui exécute votre code. Regroupement des fichiers ? Choisissez parmi une demi-douzaine de bundlers, chacun avec son propre dialecte de configuration.
Deno intègre tout cela sous forme de sous-commandes intégrées. Ces dernières existaient déjà avant la version 2.0 :
deno fmt # formats TS, JS, JSON, HTML, CSS, YAML, SQL
deno lint # built-in linter with quick fixes
deno test # test runner with coverage, snapshots, sharding
deno check # type checking
deno compile # single binary, cross-platform, code signing
deno bench # benchmarking
deno doc # documentation generation
À partir de la version 2.8, six sous-commandes supplémentaires ont été ajoutées :
deno audit # security audit of dependencies
deno audit fix # auto-upgrade vulnerable packages to nearest patched version
deno why # explain why a package is installed (traces dependency paths)
deno transpile # strip types, emit .d.ts declarations
deno pack # build an npm-publishable tarball from JSR/Deno code
deno ci # reproducible install for CI (errors without lockfile)
Et avec la version 2.9, encore plus sont arrivées :
deno desktop # build native desktop apps via webview (experimental)
deno list # show dependency tree (like npm ls)
deno link/unlink # local package linking for development
Celui qui est le plus utilisé est deno compile. En créant une petite application en ligne de commande et en exécutant deno compile --target x86_64-unknown-linux-gnu main.ts, on obtient un binaire autonome. Aucun autre logiciel n’a besoin d’être installé sur la machine cible — il suffit de le copier sur un serveur et de l’exécuter.
Faites attention : le binaire généré inclut V8 ainsi que l’ensemble du runtime Deno, ce qui fait que les applications typiques pèsent entre 60 et 100 MB. Lorsque la taille est un problème,
deno compile --bundle(encore instable dans la version 2.8) applique des techniques de suppression agressives d’éléments inutiles, ce qui peut réduire considérablement la taille des scripts simples. Le blog officiel de Deno a montré qu’un exemple « hello world » utilisant lodash ne pesait que 1,5 MB avec--bundle --minify.
Plainte 4 : migrer un projet réel semblait hors de portée
Le plus grand obstacle à la modification des environnements de exécution n’a généralement rien à voir avec l’environnement lui-même. Il s’agit du fichier de verrouillage, de la structure de node_modules sur laquelle dépendent vos outils existants, ainsi que des appels require() dispersés dans le code réel.
Deno 2.9 a introduit la fonction de génération automatique du fichier de verrouillage. Supposons qu’un projet suive déjà ses dépendances à l’aide d’un des fichiers de verrouillage courants des gestionnaires de paquets — par exemple un package-lock.json de npm, un pnpm-lock.yaml, un yarn.lock ou un bun.lock — mais n’ait jamais généré de deno.lock. L’exécution de deno install dans ce projet crée automatiquement le deno.lock manquant à partir du fichier correspondant qu’il trouve. Les versions résolues sont identiques, de même que les hachages d’intégrité. Il n’y a donc aucun risque de dérive dans la résolution des dépendances.
Pour les situations qui nécessitent réellement un répertoire physique node_modules — des add-ons natifs ou des outils qui scannent directement le système de fichiers — en définissant "nodeModulesDir": "auto", on indique à Deno de créer un tel répertoire. Il existe également une option hoisted-layout ("nodeModulesLinker": "hoisted" dans deno.json, disponible depuis 2.8) destinée aux outils plus anciens conçus autour de la structure de répertoires plate d’npm plutôt que du système de liens symboliques propre à pnpm.
Le shim Node est une touche ingénieuse : dès l’installation de Deno, celui-ci installe automatiquement un binaire substitut node dans votre PATH, sans aucune configuration manuelle. Tant qu’aucun autre élément ne fournit déjà un exécutable node, ce shim intercepte les appels à la CLI de Node, traduit les arguments et les transmet à Deno. Cela signifie que les scripts CI qui appellent encore node dist/server.js continuent de fonctionner sans aucune modification. Vous pouvez désactiver ce comportement en utilisant DENO_DISABLE_NODE_SHIM=1 si vous préférez qu’il ne se produise pas automatiquement.
Les importations avec spécificateur nu — en écrivant import fs from "fs" pour qu’il soit résolu en node:fs — ont été intégrées dans la version 2.0 et sont devenues pleinement stables, fonctionnant sans aucun paramètre ou configuration à partir de la version 2.9. Il n’est pas nécessaire de réécrire vos instructions d’import pour effectuer la migration.
La tentative de migration
Considérons un projet API Hono modeste (Hono étant un framework HTTP léger comparable à Express) comprenant environ 15 routes, une base de données Postgres accessible via Drizzle ORM, ainsi qu’un processeur de tâches en arrière-plan. Il fonctionnait auparavant sous Node 22 avec pnpm. Toute l’expérience a été réalisée à l’aide de Deno 2.9.3.
Voici comment s’est déroulée la conversion :
- Exécutez
deno installdepuis le répertoire racine du projet. Cela génère un fichierdeno.lockdirectement à partir du fichier existantpnpm-lock.yamlen moins de deux secondes. - Ajoutez
"nodeModulesDir": "auto"à l’intérieur d’un fichierdeno.jsonnouvellement créé. - Démarrer l’application avec
deno run -A src/server.ts.
Tout fonctionne correctement. Chacune des 15 routes répond comme prévu, les requêtes basées sur Drizzle s’exécutent selon les attentes, et le travailleur de file d’attente continue de traiter les tâches en arrière-plan.
Cependant, trois éléments ont mal fonctionné :
- Un fichier de test faisait appel à
jest.mock(), qui n’a pas d’équivalent dans le moteur de tests intégré à Deno. Remplacer cela par un mock manuel ne prend pas plus de cinq minutes. - Une dépendance utilisait
__dirnameà l’intérieur d’un fichier CommonJS, mais Deno l’avait chargé en tant que module ES. La solution a consisté à ajouter"type": "commonjs"dans la configuration locale de ce package spécifique.
process.env.NODE_ENV sans importer explicitement process. Cette partie a en fait fonctionné correctement, car Deno expose process comme variable globale depuis la version 2.0 — mais les flags de permission n’incluaient pas env: true pour ce script en particulier, il a donc fallu l’ajouter.Toute la conversion a duré environ 25 minutes du début à la fin. Les temps de démarrage ont diminué d’environ 620 ms à 320 ms, selon des mesures effectuées avec hyperfine sur 50 exécutions. L’utilisation mémoire en veille (RSS) est passée de 142 MB à 64 MB. De plus, quatre fichiers de configuration distincts pouvaient être supprimés : prettier, eslint, jest et tsconfig.
Problèmes restants
Le titre de cet article mentionne « tout ce que je détestais », et les plaintes énumérées précédemment sont réelles, représentant des griefs courants parmi les développeurs Node. Néanmoins, il existe quelques réserves plus récentes à souligner :
- La couverture de l’API Node n’est pas complète. Un taux de réussite de 75 % dans le jeu de tests de Node signifie qu’un quart d’entre eux échouent encore. Cette lacune peut ne pas affecter une charge de travail typique, mais toute personne qui utilise des fonctionnalités particulières de
node:vm, une utilisation programmée denode:inspector, ou des fonctionnalités avancées denode:clusterdevrait vérifier le tableau de bord de compatibilité sur node-test-viewer.deno.dev au préalable.
node_modules. Tous les outils utilisant des bindings en C++ — tels que sharp, bcrypt, sqlite3 et d’autres paquets similaires — exigent à la fois la configuration nodeModulesDir et le flag --allow-ffi. Cela fonctionne, mais c’est une étape supplémentaire de configuration facile à oublier par accident.postinstall — comme les étapes de compilation via node-gyp ou prisma generate — nécessitent une autorisation explicite via --allow-scripts=npm:nom-du-paket. Il s’agit d’une décision de sécurité délibérée, mais elle peut prendre par surprise une équipe en cours de migration.process.versions.node et ajustent leur comportement en conséquence, ou codent directement des chemins de fichiers tels que /node_modules/.cache. La structure des modules basée sur des liens symboliques au style pnpm de Deno pose problème à certains de ces outils. Une option en mode « hoisted » existe pour contourner ce problème, mais il s’agit d’une solution temporaire et non d’une véritable solution.Aucun de ces problèmes n’était suffisamment grave pour entraver la migration décrite ci-dessus. Ils pourraient le devenir dans d’autres projets, il est donc utile de les vérifier à l’avance plutôt que de les découvrir au cours de la migration.
Pourquoi ces correctifs sont passés inaperçus
Dix versions mineures ont été publiées au cours de 21 mois, chacune résolvant discrètement de trois à cinq problèmes. Il n’y a eu ni réécriture majeure, ni événement de lancement spécifique comme un « Deno 3.0 ». L’équipe a introduit des ensembles de permissions détaillés dans la version 2.5, atteint une vitesse d’installation au niveau de npm dans la version 2.8, et ajouté la fonctionnalité de génération de fichiers lockfile dans la version 2.9, considérant chacune de ces améliorations comme une maintenance ordinaire plutôt que comme une nouvelle importante.
Comparez cela à la manière dont les transitions de framework sont généralement médiatisées : un article de blog, une conférence, un guide de migration, une vague de commentaires sur les réseaux sociaux, des opinions tranchées et des réfutations à celles-ci. Deno a évité tout ce cycle de débat public et a simplement diffusé les correctifs directement.
Les développeurs qui ont ignoré ce runtime au début l’ont souvent fait parce qu’ils s’étaient forgé une opinion une fois et ne l’avaient jamais réexaminée au fur et à mesure que les outils se perfectionnaient. C’est une négligence de leur part, et non un défaut du projet lui-même.
Pour ceux dont l’impression de Deno s’est formée avant la version 2.0, l’outil testé à l’époque a pratiquement disparu. Ce qui existe aujourd’hui ressemble moins à « un environnement d’exécution TypeScript intéressant qui ne peut pas interagir avec npm » et plus à « Node sans son fardeau accumulé ». Les frustrations de l’époque étaient justifiées. Elles ont depuis été résolues. Il vaut la peine de jeter un nouveau coup d’œil.
Lectures complémentaires
- Remplacer Jest par le lanceur de tests natif de Node dans Node 24 — Une migration en situation réelle montre comment le lanceur de tests intégré à Node 24 et son support natif pour TypeScript réduisent le temps de CI tout en éliminant quatre dépendances.