10 puntos por GN⁺ 2026-03-23 | 2 comentarios | Compartir por WhatsApp
  • 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

 
click 2026-03-23

Yo también sigo ofreciendo una build en cjs cuando hago librerías,
pero me gustaría que para 2026 las librerías que ni siquiera tienen ejemplos en esm y están basadas por completo en require se actualizaran un poco.

 
GN⁺ 2026-03-23
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

    • Yo también llevo años experimentando con este enfoque, e incluso hice un sitio de tutoriales llamado plainvanillaweb.com
      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í”
    • Si es una página de marketing sencilla, quizá sí, pero en una app con muchas funciones las dependencias suelen ser indispensables
      Por ejemplo, yo hago sitios con mucha funcionalidad de mapas, y tengo que usar librerías sin reemplazo como mapbox/maplibre/openlayers
    • En 2022 hice un proyecto con este enfoque y no tuve ningún problema relacionado con CVE ni con migraciones de versión
      El cliente tampoco pagó ni un centavo en costos de migración
    • Renderizar componentes es fácil, pero el núcleo de un framework es ofrecer actualizaciones reactivas del modelo
      Me da curiosidad cómo se maneja la actualización del modelo, como en este artículo
    • Llevo casi 20 años trabajando con JS, y al final terminé asentándome en un enfoque de dejar solo las dependencias mínimas y hacer el resto por mi cuenta
      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

    • Hoy en día la librería estándar de JS ya es bastante amplia, así que me pregunto qué funcionalidades ves todavía como faltantes
    • A mí más bien me gustan los rants. Ayudan a entender no solo las emociones de la gente, sino también por qué las sienten
  • 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-stuff

    • Me recuerda la regla de escritura de Kurt Vonnegut: “Cada oración debe revelar carácter o hacer avanzar la acción”
      Como en el contraste entre Demóstenes y Cicerón, el buen código es el que ya no puede quitarse más
    • Todo software tiene partes innecesarias, pero en especial los paquetes npm y las webapps
      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)

    • En la práctica hay gente que quiere soportar motores JS antiguos y gente que crea montones de mini paquetes
      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
    • Me parece extraña la obsesión por mantener compatibilidad por debajo de ES6
      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)
    • No solo hace falta soportar navegadores viejos, también navegadores raros
      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
    • La web tiene una cultura fuerte de “despliega ahora y arréglalo después”, así que las dependencias envejecidas permanecen mucho tiempo
    • Como con ciertas decisiones de diseño de Angular, a veces estructuras del pasado terminan causando ineficiencias en el presente
      Antes se sobrescribían cosas como setTimeout para rastrear asincronía, pero ahora con signals se puede hacer de forma mucho más simple
  • Creo 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

    • Me pregunto si eso es solo orgullo personal o si realmente deja alguna ganancia
      Por ejemplo, Anthropic ofrece Claude gratis a mantenedores de open source con muchas descargas en npm
    • Como dice el artículo immich.app/cursed-knowledge, también hay gente que agrega 50 paquetes usando la “compatibilidad hacia atrás” como excusa
    • También es grave desde el punto de vista de seguridad. Cada uno de esos micro paquetes se vuelve superficie de ataque
      La competencia por descargas más bien aumenta el riesgo
    • Antes hubo incluso alguien que se infiltró en una organización y agregó su propio paquete como dependencia para subir su cantidad de estrellas para el currículum
    • También hay un problema cultural. En la comunidad JS se critica copiar y pegar código de 7 líneas como si fuera “reinventar la rueda
      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

    • Está bien tratar de entender una realidad mala, pero una aceptación excesiva es derrotismo
      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
    • Sentí que este artículo, más que culpar a los desarrolladores, critica el estado actual de forma razonada
  • 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 raras
    El 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”

    • Para uso interno, ese sí es el enfoque correcto. Si la empresa define el navegador soportado, administrarlo se vuelve fácil
      Entiendo que Safari use menos memoria, pero a nivel de política es más eficiente unificarlo
    • Mantenerlo simple al final da la mayor ganancia
  • 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

    • La mayoría de esos equipos son casos de herramientas de build configuradas hacia 2015 y nunca actualizadas
      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”
    • De hecho hay una persona concreta que insiste en ese soporte viejo y mantiene muchos paquetes de bajo nivel
    • Como dato, también escuché que Deutsche Bahn todavía usa Windows 3.1