- En el ecosistema de npm, la inflación del árbol de dependencias se señala como un problema principal, y proviene del soporte a runtimes antiguos, la estructura de paquetes atómicos y el uso de ponyfills obsoletos
- Pequeños paquetes utilitarios que se mantienen por compatibilidad con motores antiguos y por seguridad entre realms (cross-realm) siguen quedándose innecesariamente incluso en entornos modernos
- La arquitectura atómica buscaba aumentar la reutilización, pero en la práctica funciona como una estructura ineficiente que incrementa los costos de duplicación, seguridad y mantenimiento
- Los paquetes ponyfill obsoletos para funciones que ya son compatibles con todos los motores no se eliminan, lo que provoca descargas innecesarias y una mayor carga de gestión
- La comunidad está impulsando la limpieza de dependencias innecesarias y la migración a funciones nativas mediante herramientas como e18e, knip y module-replacements
Los tres pilares de la inflación de dependencias en JavaScript
- Con el crecimiento de la comunidad e18e, han aumentado las contribuciones centradas en rendimiento, y se están realizando actividades de limpieza para depurar paquetes innecesarios o sin mantenimiento
- En el ecosistema de npm, la inflación del árbol de dependencias (dependency bloat) se señala como un problema principal, y el soporte a runtimes antiguos, la estructura de paquetes atómicos y el uso de ponyfills obsoletos se consideran causas clave
1. Soporte para runtimes antiguos (incluyendo seguridad y realms)
- En el árbol de npm existen muchos paquetes utilitarios pequeños como
is-string y hasown, que se mantienen por las siguientes tres razones
- Soporte para motores muy antiguos (por ejemplo, ES3, IE6/7, Node.js inicial)
-
Prevención de alteraciones del namespace global
- Manejo de valores cross-realm
-
Soporte para motores antiguos
- En entornos ES3 no existen funciones de ES5 como
Array.prototype.forEach, Object.keys y Object.defineProperty
- En esos entornos hay que implementarlas directamente o usar un polyfill
- La mejor solución es actualizar, pero algunos usuarios todavía siguen en versiones antiguas
-
Prevención de alteraciones del namespace global
- Node usa internamente el concepto de primordials para envolver los objetos globales en una etapa inicial y protegerlos de modificaciones
- Por ejemplo, si se redefine
Map, incluso Node puede romperse, así que Node conserva la referencia original
- Algunos mantenedores aplican este enfoque también a paquetes generales y usan paquetes centrados en seguridad como
math-intrinsics
-
Valores cross-realm
- Al pasar objetos entre iframes, puede ocurrir que la comprobación con
instanceof falle
- Ejemplo:
window.RegExp !== iframeWindow.RegExp
- Frameworks de pruebas como
chai realizan verificaciones de tipo entre realms con Object.prototype.toString.call(val)
- Paquetes como
is-string existen por esta compatibilidad cross-realm
-
Problemas
- La mayoría de los desarrolladores usa Node moderno o navegadores evergreen, así que esta compatibilidad no es necesaria
- Sin embargo, estos paquetes quedan incluidos en la “ruta crítica” del árbol general de dependencias, y todos terminan pagando el costo
2. Arquitectura atómica (Atomic)
- Algunos desarrolladores sostienen que los paquetes deben dividirse en unidades lo más pequeñas posible para construir bloques reutilizables
- Como resultado, existen muchos paquetes extremadamente fragmentados como
shebang-regex, arrify, slash, path-key, onetime e is-wsl
- Ejemplo:
shebang-regex contiene solo una línea de expresión regular (/^#!(.*)/)
-
Problemas
- La mayoría de los paquetes atómicos no se reutiliza o tiene un solo consumidor
- Ejemplos:
shebang-regex → solo lo usa shebang-command
cli-boxes → solo lo usan boxen e ink
onetime → solo lo usa restore-cursor
- En estos casos es equivalente a código inline, pero añade costos extra en solicitudes a npm, descompresión y ancho de banda
-
Problema de duplicación
- Ejemplo: en el árbol de dependencias de
nuxt@4.4.2, is-docker, is-stream, is-wsl y path-key aparecen duplicados en 2 versiones cada uno
- Si se reemplazan por código inline, desaparecen los conflictos de versión y los costos de resolución, así que el costo de duplicación es casi nulo
-
Expansión del riesgo en la cadena de suministro
- Cuantos más paquetes haya, mayores son los riesgos de seguridad y mantenimiento
- De hecho, hubo un caso en el que un mantenedor administraba muchos paquetes pequeños, su cuenta fue comprometida y cientos de paquetes quedaron afectados al mismo tiempo
- Código simple como
Array.isArray(val) ? val : [val] puede manejarse inline sin necesidad de un paquete aparte
-
Conclusión
- La arquitectura atómica, contrario a su intención, se ha deformado en una estructura ineficiente y riesgosa
- Sin beneficios reales para la mayoría de los usuarios, todo el ecosistema termina asumiendo el costo
3. Ponyfills obsoletos
- Un Polyfill es código que agrega al entorno funciones que el motor no soporta,
mientras que un Ponyfill es una implementación alternativa que se usa mediante import directo sin modificar el entorno
- Ejemplo:
@fastly/performance-observer-polyfill ofrece tanto polyfill como ponyfill
-
Problemas
- Los ponyfills fueron útiles en el pasado, pero siguen ahí aunque la función objetivo ya esté soportada por todos los motores
- Ejemplos:
globalthis (con soporte desde 2019, 49 millones de descargas semanales)
indexof (con soporte desde 2010, 2.3 millones de descargas semanales)
object.entries (con soporte desde 2017, 35 millones de descargas semanales)
- Estos paquetes permanecen en su mayoría simplemente porque nadie los ha quitado
- Cuando todos los motores LTS soportan una función, el ponyfill debería eliminarse
Cómo reducir la inflación
- Debido al anidamiento profundo del árbol de dependencias, el trabajo de limpieza es difícil, pero puede mejorar con colaboración comunitaria
- Cada desarrollador debería preguntarse “¿realmente necesito este paquete?” y, si no es así, abrir un issue o buscar un paquete alternativo
- El proyecto module-replacements ofrece una lista de paquetes que pueden sustituirse por funciones nativas
-
Uso de knip
- knip es una herramienta para detectar dependencias sin usar y código muerto
- No es una solución directa, pero sirve como punto de partida para la limpieza
-
Uso de la CLI de e18e
- Con el comando
@e18e/cli analyze se pueden detectar dependencias reemplazables
- Ejemplo: migración automática de
chalk a picocolors
- En el futuro también se planea recomendar funciones nativas como
styleText de Node según el entorno
-
Uso de npmgraph
- npmgraph.js.org es una herramienta para visualizar árboles de dependencias
- Ejemplo: en el árbol de
eslint@10.1.0, la rama de find-up está aislada
- No se necesitan 6 paquetes para una simple función de búsqueda de archivos, así que se puede usar una alternativa más pequeña como
empathic
-
Proyecto module replacements
- La comunidad mantiene un dataset de mapeo entre paquetes reemplazables y funciones nativas
- También se admiten migraciones automáticas mediante el proyecto codemods
Conclusión
- La inflación actual es una estructura en la que todo el mundo paga el costo por unos pocos usuarios que quieren mantener compatibilidad antigua o estructuras peculiares
- En el pasado pudo haber sido inevitable, pero ahora que los motores y APIs modernos han madurado lo suficiente, ya es una carga innecesaria
- Hacia adelante, esos pocos deberían mantener un stack aparte, mientras que el resto debería pasar a una base de código ligera y moderna
- Proyectos como e18e y npmx ya lo están apoyando mediante documentación y herramientas,
y cada desarrollador también debería revisar sus dependencias y preguntarse “¿por qué necesito esto?”
- Todos podemos ayudar a limpiar
2 comentarios
Yo también sigo ofreciendo una build en
cjscuando hago librerías,pero me gustaría que para 2026 las librerías que ni siquiera tienen ejemplos en
esmy están basadas por completo enrequirese actualizaran un poco.Comentarios en Hacker News
Creo que hoy en día la mejor dirección es desarrollar con JavaScript sin dependencias
Las librerías estándar de JS/CSS también son excelentes, y el análisis estático (verificación JSDoc de TypeScript), los módulos ES y los web components son lo bastante potentes
La gente dice que este enfoque perjudica la escalabilidad o el mantenimiento, pero en mi experiencia más bien permitió mantener una estructura simple y fácil de cambiar
La mayor parte de lo que hacen los frameworks o las herramientas de build puede reemplazarse con funciones integradas del navegador y patrones vanilla
Pero este enfoque sigue siendo terreno poco conocido, y el problema es que casi todo el ecosistema de tutoriales gira alrededor de los grandes frameworks
De hecho, incluso si migras código de React a vanilla por completo, la modularidad se mantiene y el código solo crece alrededor de 1.5 veces; al no tener dependencias, el rendimiento incluso mejora
Claro, eso no significa que las dependencias sean malas. Pero muchos desarrolladores están atrapados en la idea fija de que “hay que usarlas sí o sí”
Por ejemplo, yo hago sitios con mucha funcionalidad de mapas, y tengo que usar librerías sin reemplazo como mapbox/maplibre/openlayers
El cliente tampoco pagó ni un centavo en costos de migración
Me da curiosidad cómo se maneja la actualización del modelo, como en este artículo
De hecho, se volvió más fácil mantener codebases grandes con poca gente
Gracias a las herramientas actuales, implementarlo uno mismo es mucho más fácil que antes, y además encaja bien con la agentic engineering
El artículo está bien escrito, explica el problema con claridad y sin ponerse emocional
Creo que parte de esta situación se debe a que JS nunca tuvo una librería estándar realmente sólida
Buen artículo, pero creo que la raíz del problema es el agregado innecesario (=bloat) en sí
Quiero citar a Saint-Exupéry: “La perfección se alcanza no cuando no hay nada más que añadir, sino cuando no hay nada más que quitar”
La mayor parte del software se escribe preguntando “¿cómo agregamos esto más fácilmente?” en vez de “¿cómo lo hacemos más elegante?”
Y la respuesta siempre es
npm i more-stuffComo en el contraste entre Demóstenes y Cicerón, el buen código es el que ya no puede quitarse más
JS tiene que considerar compatibilidad tanto con navegadores pasados como futuros, y al ser un lenguaje centrado en UI, se infla por accesibilidad, internacionalización, soporte móvil, etc.
En muchos casos esto parece un problema de deuda técnica oculta
La causa es no subir el target de compilación a ESx y no actualizar paquetes o implementaciones
ES5 ya lleva 13 años soportado por todos los navegadores (caniuse.com/es5)
Ambos consideran que lo que hacen es una funcionalidad, y mantienen muchos paquetes populares
Por eso es difícil que cambie. A veces la comunidad los critica, pero ellos también tienen su propia lógica
Si transpilas a versiones viejas con Babel, el código se vuelve más grande y más lento, y al final tampoco funciona en navegadores antiguos por las limitaciones de CSS o JS
Incluso hubo casos donde un polyfill causó problemas (un polyfill del operador exponencial que no podía manejar BigInt)
Hay entornos muy diversos: consolas, TVs, Android antiguos, iPod touch, el navegador integrado de Facebook, etc.
Por eso dejo un solo módulo externo y resuelvo todo lo demás con configuración del transpiler
Antes se sobrescribían cosas como
setTimeoutpara rastrear asincronía, pero ahora con signals se puede hacer de forma mucho más simpleCreo que algunos autores de paquetes dividen artificialmente el árbol de dependencias para aumentar el número de descargas
No tiene sentido que exista un paquete de 7 líneas. Los metadatos del lockfile pesan más que el código
Antes, 5% de las dependencias de create-react-app eran mini paquetes de un solo autor
Hay casos como has-symbols, is-string, ljharb
Por ejemplo, Anthropic ofrece Claude gratis a mantenedores de open source con muchas descargas en npm
La competencia por descargas más bien aumenta el riesgo
Pero en otras culturas eso más bien se considera algo bueno
Antes de criticar el ecosistema JS, conviene leer 30 years of br tags
Eso ayuda a entender el proceso de evolución de JS y sus herramientas
Decir simplemente “los desarrolladores de JS son el problema” demuestra falta de pensamiento ingenieril
Siempre deberíamos pensar en mejores teorías y prácticas
Como el mundo del software cambia rápido, hace falta celebrarnos “funerales falsos” para abandonar prácticas obsoletas
Mantengo una codebase de Node.js de 9 años, y solo tiene 8 dependencias, todas sin dependencias transitivas
Priorizo usar funciones integradas de Node y solo implemento yo mismo lo necesario
Es mucho más estable y estresante mucho menos que antes
La librería estándar de Deno también es excelente, así que junto con las funciones base del runtime se pueden hacer apps con apenas unos cuantos paquetes
JS es un lenguaje bastante bueno si se aborda con cuidado
Entiendo el argumento de seguridad entre realms en paquetes como
is-string, pero en la práctica esas situaciones son rarasEl problema es que npm permite publicar con demasiada facilidad y la filosofía de “partamos todo en módulos para distribuirlo” se sobredimensionó
Los consumidores no auditan el árbol de dependencias; solo instalan, así que un costo opcional termina volviéndose costo por defecto
El problema de los ponyfills podría resolverse con automatización
Por ejemplo, serviría un bot estilo Renovate que detecte automáticamente y elimine funciones ya soportadas por versiones LTS de Node
En la PWA interna de la empresa hay un solo principio:
“Actualicen a la última versión de Chrome. Si aun así hay problemas, entonces lo vemos”
Entiendo que Safari use menos memoria, pero a nivel de política es más eficiente unificarlo
De verdad cuesta entender eso de “hay que soportar hasta ES3 (nivel IE6/7)”
Incluso por seguridad, hasta los sitios bancarios deberían bloquear navegadores tan viejos
Actualizar el stack de Webpack, Babel y polyfills es un trabajo grande, así que lo dejan tal como está
Es una cultura de “si no está roto, no lo arregles”