10 points par GN⁺ 2026-03-23 | 2 commentaires | Partager sur WhatsApp
  • Dans l’écosystème npm, l’emballement de l’arbre de dépendances est pointé comme un problème majeur, dû au support d’anciens runtimes, aux structures de paquets atomiques et à l’usage de ponyfills obsolètes
  • De petits paquets utilitaires conservés pour la compatibilité avec des moteurs anciens et la sécurité cross-realm restent inutilement présents même dans des environnements modernes
  • L’architecture atomique visait à accroître la réutilisabilité, mais elle fonctionne en pratique comme une structure inefficace qui augmente les coûts de duplication, de sécurité et de maintenance
  • D’anciens paquets ponyfill pour des fonctionnalités déjà prises en charge par tous les moteurs ne sont pas supprimés, ce qui entraîne des téléchargements inutiles et une charge de gestion supplémentaire
  • La communauté pousse le nettoyage des dépendances inutiles et la bascule vers les fonctionnalités natives via des outils comme e18e, knip et module-replacements

Les trois axes de l’emballement des dépendances JavaScript

  • Avec la croissance de la communauté e18e, les contributions axées sur la performance se multiplient, et des activités de cleanup sont en cours pour retirer les paquets inutiles ou non maintenus
  • Dans l’écosystème npm, l’emballement de l’arbre de dépendances (dependency bloat) est identifié comme un problème majeur, dont les causes principales sont le support d’anciens runtimes, les structures de paquets atomiques et l’usage de ponyfills obsolètes

1. Support des anciens runtimes (y compris sécurité et realms)

  • L’arbre npm contient de nombreux petits paquets utilitaires comme is-string et hasown, maintenus pour trois raisons principales
    • le support de moteurs très anciens (par ex. ES3, IE6/7, premiers Node.js)
    • protection contre l’altération de l’espace de noms global

      • gestion des valeurs cross-realm
  • Support des anciens moteurs

    • Dans un environnement ES3, des fonctionnalités ES5 comme Array.prototype.forEach, Object.keys ou Object.defineProperty n’existent pas
    • Dans ce type d’environnement, il faut les implémenter soi-même ou utiliser un polyfill
    • La meilleure solution est de mettre à niveau l’environnement, mais certains utilisateurs conservent encore d’anciennes versions
  • Protection contre l’altération de l’espace de noms global

    • Node utilise en interne le concept de primordials pour encapsuler les objets globaux au démarrage et les protéger contre les altérations
    • Par exemple, si Map est redéfini, Node lui-même peut se retrouver cassé ; Node conserve donc des références vers les originaux
    • Certains mainteneurs de paquets appliquent aussi cette méthode à des paquets génériques, en utilisant des paquets centrés sur la sûreté comme math-intrinsics
  • Valeurs cross-realm

    • Lorsqu’un objet passe d’un iframe à un autre, les vérifications instanceof peuvent échouer
    • Exemple : window.RegExp !== iframeWindow.RegExp
    • Des frameworks de test comme chai utilisent la méthode Object.prototype.toString.call(val) pour effectuer des vérifications de type entre realms
    • Des paquets comme is-string existent pour cette compatibilité cross-realm
  • Problèmes

    • La plupart des développeurs utilisent aujourd’hui Node moderne ou des navigateurs evergreen, donc cette compatibilité n’est pas nécessaire
    • Pourtant, ces paquets se retrouvent dans le « hot path » des arbres de dépendances génériques, ce qui fait que tout le monde en paie le coût

2. Architecture atomique

  • Certains développeurs estiment qu’il faut découper les paquets en unités aussi petites que possible pour construire des briques réutilisables
  • On se retrouve ainsi avec de nombreux paquets extrêmement granulaires comme shebang-regex, arrify, slash, path-key, onetime ou is-wsl
  • Exemple : shebang-regex ne contient qu’une seule ligne de regex (/^#!(.*)/)
  • Problèmes

    • La plupart des paquets atomiques sont peu réutilisés, voire n’ont qu’un seul consommateur
    • Exemples :
      • shebang-regex → utilisé uniquement par shebang-command
      • cli-boxes → utilisé uniquement par boxen et ink
      • onetime → utilisé uniquement par restore-cursor
    • Dans ces cas, cela revient à du code inline, mais avec les coûts supplémentaires des requêtes npm, de la décompression et de la bande passante
  • Problème de duplication

    • Exemple : dans l’arbre de dépendances de nuxt@4.4.2, is-docker, is-stream, is-wsl et path-key existent en double version
    • En les remplaçant par du code inline, il n’y a plus de conflit de versions ni de coût de résolution, donc le coût de duplication devient quasi nul
  • Extension du risque supply chain

    • Plus il y a de paquets, plus les risques de sécurité et de maintenance augmentent
    • Il y a déjà eu des cas où un mainteneur gérait de nombreux petits paquets, puis s’est fait compromettre son compte, ce qui a endommagé des centaines de paquets en même temps
    • Un code simple comme Array.isArray(val) ? val : [val] peut être géré inline sans nécessiter un paquet séparé
  • Conclusion

    • L’architecture atomique a fini, à rebours de son intention initiale, par se transformer en structure inefficace et risquée
    • Sans bénéfice concret pour la plupart des utilisateurs, tout l’écosystème en supporte le coût

3. Ponyfills obsolètes

  • Un polyfill est du code qui ajoute à un environnement des fonctionnalités qu’un moteur ne prend pas en charge, tandis qu’un ponyfill est une implémentation alternative utilisée via import direct sans modifier l’environnement
  • Exemple : @fastly/performance-observer-polyfill fournit à la fois un polyfill et un ponyfill
  • Problèmes

    • Les ponyfills ont été utiles par le passé, mais ne sont pas retirés même lorsque la fonctionnalité cible est déjà prise en charge par tous les moteurs
    • Exemples :
      • globalthis (pris en charge depuis 2019, 49 millions de téléchargements hebdomadaires)
      • indexof (pris en charge depuis 2010, 2,3 millions de téléchargements hebdomadaires)
      • object.entries (pris en charge depuis 2017, 35 millions de téléchargements hebdomadaires)
    • Ces paquets subsistent dans la plupart des cas simplement parce qu’ils n’ont pas été supprimés
    • Dès que tous les moteurs LTS prennent en charge une fonctionnalité, le ponyfill devrait être retiré

Comment réduire cet emballement

  • En raison de l’imbrication profonde des arbres de dépendances, le nettoyage est difficile, mais il reste possible grâce à la coopération de la communauté
  • Chaque développeur devrait se demander : « Ce paquet est-il vraiment nécessaire ? » et, si ce n’est pas le cas, ouvrir une issue ou chercher un paquet de remplacement
  • Le projet module-replacements fournit une liste de paquets remplaçables par des fonctionnalités natives
  • Utiliser knip

    • knip est un outil de détection des dépendances inutilisées et du code mort
    • Ce n’est pas une solution directe, mais il constitue un bon point de départ pour le nettoyage
  • Utiliser le CLI e18e

    • La commande @e18e/cli analyze permet de détecter les dépendances remplaçables
    • Exemple : migration automatique de chalk vers picocolors
    • À l’avenir, l’outil devrait aussi recommander des fonctionnalités natives comme styleText de Node selon l’environnement
  • Utiliser npmgraph

    • npmgraph.js.org est un outil de visualisation des arbres de dépendances
    • Exemple : dans l’arbre de eslint@10.1.0, la branche find-up est isolée
    • Une simple fonction d’exploration de fichiers n’a pas besoin de 6 paquets ; il est donc possible d’utiliser une alternative plus légère comme empathic
  • Projet module replacements

    • La communauté maintient un jeu de données qui associe paquets remplaçables et fonctionnalités natives
    • Des migrations automatiques sont également prises en charge via le projet codemods

Conclusion

  • L’emballement actuel fait que tout le monde supporte le coût pour répondre aux besoins d’une minorité qui veut conserver une compatibilité ancienne ou des structures atypiques
  • C’était autrefois inévitable, mais c’est aujourd’hui une charge inutile, alors que les moteurs et API modernes ont largement progressé
  • À l’avenir, cette minorité devrait maintenir sa propre pile séparée, tandis que le reste de l’écosystème devrait évoluer vers une base de code légère et moderne
  • Des projets comme e18e et npmx soutiennent déjà cette transition via la documentation et l’outillage, et chaque développeur devrait aussi examiner ses dépendances et se demander : « Pourquoi est-ce nécessaire ? »
  • Tout le monde peut participer au nettoyage

2 commentaires

 
click 2026-03-23

Quand je crée moi aussi une bibliothèque, je fournis encore un build CJS, mais
en 2026, j’aimerais bien que les bibliothèques qui n’ont même pas d’exemple ESM et reposent entièrement sur require soient un peu mises à jour.

 
GN⁺ 2026-03-23
Commentaires sur Hacker News
  • En ce moment, je pense que la meilleure direction est de développer en JavaScript sans dépendances
    Les bibliothèques standard JS/CSS sont excellentes, et l’analyse statique (vérifications JSDoc de TypeScript), les modules ES, les Web Components, etc. sont largement assez puissants
    On dit que cette approche est désavantageuse pour l’évolutivité ou la maintenance, mais d’après mon expérience elle permet au contraire de conserver une structure simple et facile à faire évoluer

    • Moi aussi, j’expérimente cette approche depuis plusieurs années, et j’ai même créé un site de tutoriels, plainvanillaweb.com
      La plupart de ce que font les frameworks ou les outils de build peut être remplacé par les fonctionnalités natives du navigateur et des patterns vanilla
      Le problème, c’est que cette approche reste encore peu familière, et que l’écosystème des tutoriels tourne surtout autour des grands frameworks
      En pratique, même en migrant complètement du code React vers du pur vanilla, on conserve la modularité et le code ne s’allonge que d’environ 1,5x, tandis que les performances s’améliorent grâce à l’absence de dépendances
      Bien sûr, ça ne veut pas dire que les dépendances sont mauvaises. C’est simplement que beaucoup de développeurs sont enfermés dans l’idée reçue selon laquelle elles sont indispensables
    • C’est possible pour une simple page marketing, mais pour une application riche en fonctionnalités, les dépendances sont souvent indispensables
      Par exemple, je crée des sites avec beaucoup de fonctionnalités cartographiques, donc je dois utiliser des bibliothèques sans véritable alternative comme mapbox/maplibre/openlayers
    • J’ai mené un projet de cette manière en 2022, et je n’ai eu aucun problème lié aux CVE ou aux migrations de version
      Le client n’a pas payé un centime en coûts de migration
    • Le rendu de composants est facile, mais le cœur d’un framework, c’est la mise à jour réactive du modèle
      Je me demande comment cela est géré, comme dans cet article
    • Cela fait presque 20 ans que je travaille avec JS, et j’ai fini par me stabiliser sur une approche avec un minimum de dépendances, en construisant le reste moi-même
      Au contraire, il est devenu plus facile de maintenir une grande base de code avec peu de personnes
      Grâce aux outils actuels, implémenter soi-même est bien plus simple qu’avant, et cela s’accorde aussi très bien avec l’agentic engineering
  • L’article est bien écrit, sans être émotionnel, et il explique clairement le problème
    Je pense qu’une partie de cette situation vient du fait que JS ne dispose pas vraiment d’une bibliothèque standard digne de ce nom

    • La bibliothèque standard de JS est devenue assez vaste aujourd’hui, donc je me demande quelles fonctionnalités vous jugez encore manquantes
    • Personnellement, j’aime plutôt les coups de gueule (rant). Ça aide à comprendre non seulement les émotions des gens, mais aussi leurs raisons
  • Bon article, mais je pense que le vrai fond du problème, c’est surtout l’ajout inutile (= bloat)
    J’ai envie de citer Saint-Exupéry : « La perfection est atteinte, non pas quand il n’y a plus rien à ajouter, mais quand il n’y a plus rien à retirer »
    La plupart des logiciels sont écrits en se demandant non pas « comment rendre cela plus élégant ? », mais « comment ajouter cela plus facilement ? »
    Et la réponse est toujours npm i more-stuff

    • Cela me rappelle la règle d’écriture de Kurt Vonnegut : « Chaque phrase doit soit révéler le personnage, soit faire avancer l’action »
      Comme dans l’opposition entre Démosthène et Cicéron, le bon code est celui auquel on ne peut plus rien retirer
    • Tous les logiciels contiennent des éléments inutiles, mais c’est particulièrement visible avec les paquets npm et les webapps
      JS doit tenir compte à la fois de la compatibilité avec les navigateurs passés et futurs, et comme c’est un langage centré sur l’UI, l’accessibilité, l’internationalisation et le support mobile en augmentent le volume
  • Dans bien des cas, cela ressemble à un problème caché de dette technique
    La cause, c’est qu’on ne remonte pas la cible de compilation vers ESx, et qu’on ne met pas à jour les paquets ou les implémentations
    ES5 est déjà pris en charge par tous les navigateurs depuis 13 ans (caniuse.com/es5)

    • En pratique, il y a des gens qui veulent prendre en charge de vieux moteurs JS, et d’autres qui créent d’innombrables mini-paquets
      Les uns comme les autres considèrent leur comportement comme une fonctionnalité, et maintiennent beaucoup de paquets populaires
      Du coup, c’est difficile à faire évoluer. La communauté critique parfois cela, mais eux aussi ont leur logique
    • Cette obsession de conserver la compatibilité en dessous d’ES6 me paraît étrange
      Quand on transpile vers l’ancien avec Babel, le code devient plus volumineux et plus lent, et de toute façon cela ne fonctionne pas sur les vieux navigateurs à cause des limites CSS ou JS
      Il est même arrivé que des polyfills posent problème (un polyfill de l’opérateur d’exponentiation incapable de gérer BigInt)
    • Il ne faut pas seulement supporter les vieux navigateurs, mais aussi les navigateurs bizarres
      Consoles, TV, vieux Android, iPod touch, navigateur intégré de Facebook : il existe toutes sortes d’environnements
      Donc on garde un seul module externe, et on règle le reste via la configuration du transpileur
    • Le web a une forte culture du « on déploie maintenant et on corrigera plus tard », donc les dépendances vieillissantes restent longtemps
    • Comme avec certains choix de conception d’Angular, des structures du passé peuvent produire des inefficacités aujourd’hui
      À une époque, on surchargeait setTimeout et autres pour suivre l’asynchrone, mais maintenant on peut gérer cela de manière bien plus simple avec les signals
  • Je pense que certains auteurs de paquets fragmentent artificiellement l’arbre des dépendances pour augmenter leur nombre de téléchargements
    L’existence de paquets de 7 lignes n’a aucun sens. Les métadonnées du lockfile sont plus grosses que le code
    À une époque, 5 % des dépendances de create-react-app venaient des mini-paquets d’un seul auteur
    Il y a des exemples comme has-symbols, is-string, ljharb

    • Je me demande si ce comportement relève simplement de l’ego, ou s’il y a un véritable bénéfice
      Par exemple, Anthropic offre Claude gratuitement aux mainteneurs open source qui ont beaucoup de téléchargements npm
    • Comme le montre l’article immich.app/cursed-knowledge, certains ajoutent 50 paquets au nom de la « compatibilité descendante »
    • C’est aussi grave du point de vue de la sécurité. Chacun de ces micro-paquets devient une surface d’attaque
      La compétition au nombre de téléchargements ne fait qu’augmenter le risque
    • Il y a déjà eu le cas de quelqu’un qui s’est infiltré dans une organisation pour ajouter son paquet aux dépendances et gonfler son nombre d’étoiles GitHub pour son CV
    • Il y a aussi un problème culturel. Dans la communauté JS, copier-coller directement 7 lignes de code est critiqué comme une réinvention de la roue
      Mais dans d’autres cultures, c’est au contraire considéré comme une bonne pratique
  • Avant de critiquer l’écosystème JS, cela vaut la peine de lire 30 years of br tags
    Cela permet de comprendre le processus d’évolution de JS et de ses outils
    Dire simplement « le problème, ce sont les développeurs JS » montre un manque de réflexion d’ingénierie

    • Chercher à comprendre une mauvaise réalité est une bonne attitude, mais une acceptation excessive relève du défaitisme
      Nous devons toujours réfléchir à de meilleures théories et pratiques
      Comme le monde du logiciel évolue vite, il faut parfois se faire de « fausses funérailles » pour abandonner les pratiques obsolètes
    • J’ai plutôt eu l’impression que cet article ne blâmait pas les développeurs, mais proposait une critique rationnelle de l’état actuel
  • Je maintiens une base de code Node.js vieille de 9 ans, avec seulement 8 dépendances, toutes sans sous-dépendances
    J’utilise d’abord ce que propose Node en natif, et je n’implémente moi-même que ce qui est nécessaire
    C’est beaucoup plus stable et bien moins stressant qu’avant
    La bibliothèque standard de Deno est elle aussi excellente, et avec les fonctions natives du runtime, quelques paquets suffisent pour créer une application
    JS est un langage tout à fait correct si on l’aborde avec prudence

  • Je comprends l’argument sur la sécurité cross-realm de paquets comme is-string, mais dans la pratique ce genre de situation est rare
    Le vrai problème, c’est que npm permet de publier bien trop facilement, et que la philosophie du « découpons tout en modules publiés » s’est étendue à l’excès
    Les consommateurs n’auditent pas l’arbre des dépendances, ils installent simplement, si bien qu’un coût optionnel devient un coût par défaut
    Le problème des ponyfills pourrait se résoudre par l’automatisation
    Par exemple, un bot de style Renovate qui détecterait automatiquement les fonctionnalités déjà prises en charge dans les versions LTS de Node pour les supprimer serait utile

  • Dans notre PWA interne, il n’y a qu’un seul principe :
    « Mettez à jour vers la dernière version de Chrome. Et si le problème persiste, on verra ensuite. »

    • Pour un usage interne, c’est la bonne approche. Si l’entreprise fixe les navigateurs pris en charge, la gestion devient plus simple
      Je comprends que Safari consomme moins de mémoire, mais sur le plan des politiques internes, l’uniformisation est plus efficace
    • Garder les choses simples finit par apporter le plus grand bénéfice
  • J’ai vraiment du mal à comprendre l’idée qu’il faudrait encore prendre en charge « ES3 (niveau IE6/7) »
    Pour des raisons de sécurité, même les sites bancaires devraient bloquer de tels navigateurs obsolètes

    • La plupart du temps, ces équipes utilisent encore des outils de build configurés vers 2015 qu’elles n’ont jamais remplacés
      Mettre à niveau une pile Webpack, Babel et polyfills représente un gros chantier, donc elles laissent tout en l’état
      C’est la culture du « si ce n’est pas cassé, n’y touche pas »
    • Il existe effectivement une personne en particulier qui défend ce support des vieilles versions, et qui maintient beaucoup de paquets bas niveau
    • À ce propos, j’ai aussi entendu dire que Deutsche Bahn utilisait encore Windows 3.1