- 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
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
requiresoient un peu mises à jour.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
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
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
Le client n’a pas payé un centime en coûts de migration
Je me demande comment cela est géré, comme dans cet article
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
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-stuffComme dans l’opposition entre Démosthène et Cicéron, le bon code est celui auquel on ne peut plus rien retirer
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)
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
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)
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
À 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
Par exemple, Anthropic offre Claude gratuitement aux mainteneurs open source qui ont beaucoup de téléchargements npm
La compétition au nombre de téléchargements ne fait qu’augmenter le risque
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
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
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 rareLe 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. »
Je comprends que Safari consomme moins de mémoire, mais sur le plan des politiques internes, l’uniformisation est plus efficace
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
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 »