23 points par GN⁺ 2025-08-04 | 3 commentaires | Partager sur WhatsApp
  • L’environnement de développement Node.js a connu ces dernières années une transformation fondamentale, avec une forte compatibilité avec les standards du Web et un renforcement de ses fonctionnalités intégrées
  • L’adoption de systèmes de modules modernes et de modèles asynchrones comme les ESM (ES Modules), le préfixe node: et le top-level await permet d’écrire un code plus intuitif et plus sûr
  • Grâce à des API intégrées comme la Fetch API, AbortController et les Web Streams, la dépendance aux bibliothèques externes diminue et de nombreuses fonctionnalités sont désormais prises en charge nativement
  • Les outils de développement intégrés, comme le runner de tests, le mode Watch et la prise en charge des fichiers d’environnement, améliorent fortement le confort de travail et la productivité
  • Avec le renforcement de l’infrastructure de sécurité et de déploiement, qui va du contrôle des permissions jusqu’au déploiement en exécutable unique, Node.js moderne évolue vers une plateforme professionnelle et polyvalente

Les changements et les progrès de Node.js

  • Node.js est passé de sa structure initiale, centrée sur les callbacks et CommonJS, à un environnement de développement plus standardisé
  • Cette évolution n’est pas un simple changement de forme, mais une transformation globale du paradigme de développement du JavaScript côté serveur

1. Système de modules : la standardisation des ES Modules

  • CommonJS a longtemps été le mode d’utilisation historique de Node.js, mais il présente des limites pour l’analyse statique, le tree shaking, et s’écarte des standards du Web
  • Le modèle ESM (ES Modules) s’est imposé comme le nouveau standard dans Node.js
    • utilisation des syntaxes import et export
    • introduction du préfixe node: pour distinguer explicitement les modules intégrés
      • exemple : import { readFile } from 'node:fs/promises'
      • la distinction entre modules intégrés et paquets npm devient plus claire
  • La prise en charge du top-level await permet désormais d’utiliser await au niveau supérieur d’un module
    • plus besoin d’envelopper le code dans une fonction asynchrone immédiatement invoquée
    • le code devient plus linéaire et plus facile à comprendre

2. API Web intégrées : moins de dépendances externes

  • La Fetch API est intégrée à Node.js, ce qui permet d’effectuer des requêtes HTTP sans dépendance externe comme Axios ou node-fetch
  • Fetch prend en charge nativement les timeouts et l’annulation (AbortSignal.timeout())
    • il est possible de gérer les erreurs de manière cohérente sans bibliothèque dédiée aux timeouts
  • Avec AbortController, on peut mettre en place des modèles d’annulation pour divers traitements asynchrones, qu’il s’agisse de fichiers, de réseau ou d’autres opérations
    • cela fournit une approche standardisée pour gérer une interruption utilisateur ou un dépassement de délai

3. Tests intégrés : un environnement de test professionnel

  • Sans recourir à des frameworks externes comme Jest ou Mocha, le runner de tests intégré à Node.js couvre désormais la plupart des besoins
    • écriture de tests intuitive avec node:test et node:assert
  • Des fonctionnalités pratiques comme le mode Watch des tests et le reporting de couverture sont intégrées
    • les tests se relancent automatiquement à chaque modification du code
    • une fonctionnalité expérimentale de couverture est proposée à partir de Node.js 20

4. Des modèles asynchrones plus évolués

  • Même si async/await est largement utilisé, Node.js moderne met davantage l’accent sur l’exécution parallèle et sur des modèles de gestion d’erreurs plus fins
    • exécution de tâches en parallèle avec Promise.all(), et gestion des erreurs avec informations de contexte dans un unique bloc try/catch
  • L’utilisation d’AsyncIterator facilite le traitement séquentiel des événements et le contrôle de flux

5. Fonctionnalités de flux avancées et compatibilité avec les standards du Web

  • L’API de streams est devenue compatible avec les standards du Web (Streams API)
    • Readable.fromWeb et Readable.toWeb permettent de convertir des streams entre Node.js et le navigateur
  • La fonction pipeline (basée sur des Promises) permet de construire des pipelines de streams de manière intuitive et sûre

6. Worker Threads : paralléliser les tâches intensives en CPU

  • WorkerThreads permet de dépasser les limites du thread unique en JS et d’exploiter plusieurs cœurs
  • Il devient possible d’effectuer des calculs complexes ou de traiter de gros volumes de données sans bloquer la boucle principale

7. Une expérience de développement transformée

  • Le flag --watch permet de détecter les changements de code et de relancer automatiquement l’application sans nodemon
  • Le flag --env-file rend dotenv inutile et permet d’utiliser immédiatement les variables d’environnement
  • La configuration de l’environnement de développement devient plus simple et plus rapide

8. Sécurité et monitoring des performances intégrés

  • Le Permission Model, encore expérimental, permet de limiter les permissions de l’application, comme l’accès au système de fichiers ou au réseau
    • cela facilite l’application du principe du moindre privilège et le respect des exigences de sécurité
  • Avec perf_hooks, on dispose d’outils intégrés de mesure des performances pour analyser et consigner automatiquement les opérations lentes

9. Modernisation du déploiement et du packaging

  • La prise en charge de SEA (Single Executable Application) permet de distribuer Node.js et l’application sous la forme d’un binaire unique
    • le déploiement et l’installation deviennent plus simples, même dans des environnements sans Node.js installé

10. Gestion moderne des erreurs et diagnostic

  • Des classes d’erreurs structurées permettent d’inclure un contexte riche et des informations de diagnostic, tout en transmettant des objets d’erreur cohérents
  • diagnostics_channel permet d’envoyer des données de diagnostic personnalisées basées sur des événements et d’automatiser le monitoring

11. Évolution de la résolution de modules et de la gestion des paquets

  • Les Import Maps permettent de gérer les chemins internes dans des espaces de noms distincts
    • cela facilite la séparation des modules internes et les opérations de refactorisation
  • Le dynamic import permet de charger du code à l’exécution selon l’environnement ou la configuration, et de faire du code splitting

Points clés et perspectives

  • Dans Node.js, le respect des standards du Web, l’exploitation maximale des outils intégrés et l’adoption de modèles asynchrones modernes sont devenus essentiels
  • Avec Worker Threads pour le parallélisme haute performance et des fonctions de diagnostic et de sécurité, la plateforme continue d’évoluer vers un usage expert
  • De nouvelles fonctionnalités comme le déploiement en exécutable unique et les espaces de noms de modules améliorent fortement l’exploitation au quotidien
  • Ces modèles peuvent être introduits progressivement tout en restant compatibles avec le code existant
  • Même après 2025, Node.js continuera d’évoluer, et les modèles modernes présentés ici devraient servir de base à des applications tournées vers l’avenir

3 commentaires

 
sanori 2025-08-07

En commençant à créer un projet avec Deno, je me suis dit « waouh, on peut même faire ça », et on dirait bien que node.js évolue lui aussi dans une direction similaire.

 
dnltmdwhd 2025-08-05

Oh, maintenant plus besoin d'utiliser axios, ça marche directement avec fetch.

 
GN⁺ 2025-08-04
Avis Hacker News
  • Le plus grand changement, ce n’est pas l’ESM mais le fait que fetch et AbortController soient intégrés à Node ; j’ai pu supprimer axios et node-fetch, réduire la taille des bundles Lambda et raccourcir la latence de cold start d’environ 100 ms ; si vous faites npm i axios par réflexe, la release 2025 de Node est le moment d’arrêter
    • Pour toute la stack, je préfère ts-rest, qui couvre à la fois les appels d’API et la validation ; parmi les bibliothèques basées sur zod/json schema, c’est la plus légère tout en offrant une sécurité de types robuste ; on peut aussi y brancher le client HTTP qu’on veut (je choisis fastify sur Bun et sur le moteur Node) ; il y a un surcoût, mais le fait de déplacer la sécurité de types à l’étape de compilation en vaut largement la peine ; je serais curieux d’avoir de meilleures alternatives ou d’autres avis ; j’ai cherché autant que possible, et seul ts-rest semblait réunir à la fois légèreté et sécurité de types
    • Je n’aime pas vraiment la syntaxe de fetch ni le travail supplémentaire de gestion d’exceptions comme await response.json ; avec axios, c’est bien plus intuitif ; même dans les exemples, axios permet simplement de traiter response.data, alors qu’avec fetch il faut vérifier soi-même le statut puis parser le JSON, ce qui est plus fastidieux
    • En tant qu’auteur de bibliothèque, l’adoption de l’ESM a été bien plus difficile et douloureuse, mais c’était une évolution qui valait largement le coup ; fetch en lui-même est excellent, mais c’est grâce à l’ESM que nous avons vraiment gagné énormément
    • Le fetch de Node est bien plus facile et simple qu’axios, donc je le préfère ; je ne savais même pas que certaines personnes utilisaient encore axios
    • J’attends énormément de la bibliothèque de requêtes intégrée Undici ; voir le site officiel d’undici
  • Il est désormais possible d’exécuter Node en limitant les permissions d’accès au système de fichiers ou au réseau comme ceci
    # exemple de restriction d’accès au système de fichiers
    node --experimental-permission \
      --allow-fs-read=./data --allow-fs-write=./logs app.js
    
    # exemple de restriction réseau
    node --experimental-permission \
      --allow-net=api.example.com app.js
    
    Cela semble inspiré de Deno ; c’est vraiment une excellente fonctionnalité, voir la documentation des permissions de Deno
  • Il est désormais possible de styliser du texte directement sans installer chalk ou picocolors
    const { styleText } = require('node:util');
    
    Voir la documentation officielle de styleText
  • J’ai découvert plusieurs choses applicables immédiatement
    1. Node intègre désormais des tests, donc il n’est plus vraiment nécessaire d’utiliser jest
    2. Node intègre aussi une fonction watch, donc nodemon n’est plus nécessaire non plus
    • Je préfère toujours jest, parce qu’on peut utiliser jest-extended
    • Je trouve que le système de test intégré de Node est de qualité inférieure ; on comprend pourquoi après quelques semaines d’usage réel, et même quand on remonte des problèmes, l’équipe Node ne semble pas très intéressée
  • Selon Matteo Collina, le fetch de Node utilise en interne le fetch d’undici ; comme il doit créer des flux web WHATWG, il est intrinsèquement plus lent que l’approche request d’undici,
    vidéo YouTube mentionnée,
    billet de blog sur le fonctionnement d’undici
    • Pour ceux que ça intéresse, les benchmarks sont visibles ici ; j’ai récemment testé en local et sur réseau sur un MacBook Pro M3 Max, et undici était le meilleur en local, mais sur le réseau Axios donnait de meilleurs résultats ; je ne sais pas exactement pourquoi, mais mon expérience avec undici sur l’année et demie écoulée a été excellente ; on peut tout à fait l’utiliser de manière stable en production, mais pour en tirer les meilleures performances, il faut vraiment réfléchir selon le contexte
  • Grâce au transpileur TypeScript natif de Node, la complexité diminue beaucoup pour ceux qui utilisent TS
    • En réalité, il ne fait que supprimer les types, ce n’est pas une vraie transpilation ; des choses comme les enum TS ne fonctionnent pas correctement
    • Ce n’est pas encore suffisant pour un usage réel ; les enum ne me dérangent pas, mais on ne peut pas importer des fichiers locaux sans extension, et on ne peut pas non plus définir des propriétés de classe dans le constructeur
    • Le flag --experimental-strip-types n’est désormais plus nécessaire
  • J’ai souvent l’impression de découvrir de nouvelles fonctionnalités un peu par hasard ; comme avec les navigateurs, on a cette vague impression que « c’est récent » ; quand je ne faisais que du C#, lire la présentation des nouvelles fonctionnalités du langage m’enthousiasmait vraiment, mais aujourd’hui, comme je jongle entre plusieurs langages, il n’est pas facile de suivre un seul langage en détail ; au final, j’apprends surtout de manière aléatoire via les blogs ou mon entourage
    • Je m’intéresse beaucoup aux nouveautés de Node (V8), donc je lis les release notes tous les deux ou trois mois pour repérer ce genre de fonctionnalités ; il m’arrive aussi de lire les proposals ECMA ; j’aimerais vraiment que l’opérateur pipeline soit adopté
  • Après être resté un moment à distance de l’écosystème Node, j’y reviens et je trouve qu’il y a désormais beaucoup de nouvelles fonctionnalités vraiment intéressantes ; à mon avis, c’est le résultat de Deno et Bun qui ont bousculé le marché et poussé l’équipe Node à se dépasser davantage
  • Node devient peu à peu un concurrent de plus en plus sérieux face à Bun.js, Deno et autres ; cette concurrence mutuelle est positive pour l’évolution des runtimes JS
    • Les changements sont lents mais réels, ce qui est appréciable ; cela dit, la fonction shell $ de Bun me manque encore ; utiliser JS comme un langage de script est vraiment pratique, mais je n’ai pas envie de faire tourner deux runtimes sur le serveur
  • Comme pour les navigateurs, j’ai l’impression qu’on peut diviser les nouvelles fonctionnalités de Node en deux catégories
    1. des technologies entièrement nouvelles
    2. des couches de « finition » ajoutées par-dessus des fonctionnalités déjà existantes
      C’est intéressant d’observer à laquelle des deux les gens accordent le plus d’importance
    • Ce qui est une « couche de finition » pour certains peut devenir de l’ergonomie pour d’autres