- No ecossistema npm, o inchaço da árvore de dependências é apontado como um problema importante, causado por suporte a runtimes antigos, estruturas de pacotes atômicos e uso de ponyfills obsoletos
- Pequenos pacotes utilitários mantidos por compatibilidade com engines legados e segurança cross-realm continuam desnecessariamente presentes mesmo em ambientes modernos
- A arquitetura atômica tentou aumentar a reutilização, mas na prática acabou funcionando como uma estrutura ineficiente que eleva custos de duplicação, segurança e manutenção
- Pacotes ponyfill antigos para recursos já suportados por todas as engines não são removidos, gerando downloads desnecessários e maior carga de gerenciamento
- A comunidade está promovendo a limpeza de dependências desnecessárias e a migração para recursos nativos por meio de ferramentas como e18e, knip e module-replacements
Os três pilares do inchaço das dependências JavaScript
- Com o crescimento da comunidade e18e, vêm aumentando as contribuições focadas em desempenho, e estão em andamento atividades de cleanup para remover pacotes desnecessários ou sem manutenção
- No ecossistema npm, o inchaço da árvore de dependências (dependency bloat) é apontado como um problema importante, e os principais fatores são suporte a runtimes antigos, estruturas de pacotes atômicos e uso de ponyfills obsoletos
1. Suporte a runtimes antigos (incluindo segurança e realm)
- Na árvore do npm existem muitos pequenos pacotes utilitários como
is-string e hasown, mantidos por três razões principais
- suporte a engines muito antigas (ex.: ES3, IE6/7, Node.js inicial)
- prevenção de modificação do namespace global
- tratamento de valores cross-realm
-
Suporte a engines antigas
- Em ambientes ES3, recursos do ES5 não existem, como
Array.prototype.forEach, Object.keys e Object.defineProperty
- Nesses ambientes, é preciso implementar manualmente ou usar polyfills
- A melhor solução é atualizar, mas alguns usuários ainda continuam em versões antigas
-
Prevenção de modificação do namespace global
- O Node usa internamente o conceito de primordials, encapsulando objetos globais no momento inicial para protegê-los contra modificação
- Por exemplo, se
Map for redefinido, o próprio Node pode quebrar, então ele preserva a referência original
- Alguns mantenedores aplicam essa abordagem também a pacotes comuns e usam pacotes focados em segurança como
math-intrinsics
-
Valores cross-realm
- Ao passar objetos entre iframes, verificações com
instanceof podem falhar
- Ex.:
window.RegExp !== iframeWindow.RegExp
- Frameworks de teste como
chai fazem checagem de tipos entre realms com Object.prototype.toString.call(val)
- Pacotes como
is-string existem para essa compatibilidade cross-realm
-
Problemas
- A maioria dos desenvolvedores usa Node moderno ou navegadores evergreen, então essa compatibilidade é desnecessária
- Mesmo assim, esses pacotes entram no “hot path” da árvore de dependências geral e todo mundo acaba pagando o custo
2. Arquitetura atômica (Atomic)
- Alguns desenvolvedores defendem que os pacotes devem ser divididos em unidades o menor possível, formando blocos reutilizáveis
- Como resultado, existem muitos pacotes extremamente fragmentados como
shebang-regex, arrify, slash, path-key, onetime e is-wsl
- Ex.:
shebang-regex contém apenas uma única linha de regex (/^#!(.*)/)
-
Problemas
- A maioria dos pacotes atômicos não é reutilizada ou tem apenas um consumidor
- Ex.:
shebang-regex → usado apenas por shebang-command
cli-boxes → usado apenas por boxen, ink
onetime → usado apenas por restore-cursor
- Nesses casos, é equivalente a código inline, mas com custos extras de requisições ao npm, descompactação e largura de banda
-
Problema de duplicação
- Ex.: na árvore de dependências de
nuxt@4.4.2, is-docker, is-stream, is-wsl e path-key aparecem duplicados em 2 versões cada
- Se forem substituídos por código inline, somem o conflito de versões e o custo de resolução, então o custo de duplicação praticamente desaparece
-
Expansão do risco da cadeia de suprimentos
- Quanto maior o número de pacotes, maior o risco de segurança e manutenção
- Houve casos reais em que um mantenedor administrava muitos pacotes pequenos, teve a conta invadida e centenas de pacotes foram comprometidos ao mesmo tempo
- Código simples como
Array.isArray(val) ? val : [val] pode ser tratado inline sem necessidade de pacote separado
-
Conclusão
- A arquitetura atômica, ao contrário do que se pretendia, degenerou em uma estrutura ineficiente e arriscada
- Sem ganho prático para a maioria dos usuários, todo o ecossistema arca com o custo
3. Ponyfills obsoletos
- Polyfill é um código que adiciona ao ambiente recursos não suportados pela engine, enquanto Ponyfill é uma implementação alternativa usada por import direto sem modificar o ambiente
- Ex.:
@fastly/performance-observer-polyfill oferece tanto polyfill quanto ponyfill
-
Problemas
- Ponyfills foram úteis no passado, mas não são removidos mesmo quando o recurso-alvo já é suportado por todas as engines
- Exemplos:
globalthis (suportado desde 2019, 49 milhões de downloads semanais)
indexof (suportado desde 2010, 2,3 milhões de downloads semanais)
object.entries (suportado desde 2017, 35 milhões de downloads semanais)
- Na maioria dos casos, esses pacotes permanecem simplesmente porque ninguém os removeu
- Quando todas as engines LTS suportam o recurso, o ponyfill deve ser removido
Como reduzir o inchaço
- Devido ao aninhamento profundo da árvore de dependências, o trabalho de limpeza é difícil, mas pode avançar com colaboração da comunidade
- Cada desenvolvedor deve se perguntar: “esse pacote é realmente necessário?”; se não for, vale abrir uma issue ou procurar um pacote substituto
- O projeto module-replacements fornece uma lista de pacotes que podem ser substituídos por recursos nativos
-
Uso do knip
- knip é uma ferramenta para detectar dependências não usadas e código morto
- Não é uma solução direta, mas é útil como ponto de partida para a limpeza
-
Uso do e18e CLI
- Com o comando
@e18e/cli analyze, é possível detectar dependências substituíveis
- Ex.: migração automática de
chalk para picocolors
- No futuro, a ferramenta deve recomendar recursos nativos como
styleText do Node conforme o ambiente
-
Uso do npmgraph
- npmgraph.js.org é uma ferramenta de visualização da árvore de dependências
- Ex.: na árvore de
eslint@10.1.0, o ramo find-up está isolado
- Não faz sentido precisar de 6 pacotes para uma simples navegação de arquivos, então é possível usar alternativas menores, como
empathic
-
Projeto module replacements
- A comunidade mantém um dataset de mapeamento entre pacotes substituíveis e recursos nativos
- O projeto de codemods também oferece migração automática
Conclusão
- O inchaço atual é uma estrutura em que todos pagam o custo por causa de uma minoria de usuários que quer manter compatibilidade antiga ou estruturas peculiares
- Isso foi inevitável no passado, mas hoje é um peso desnecessário, já que engines e APIs modernas evoluíram o suficiente
- Daqui para frente, essa minoria deveria manter uma stack separada, enquanto o restante migra para uma base de código leve e moderna
- Projetos como e18e e npmx já dão suporte a isso por meio de documentação e ferramentas, e cada desenvolvedor também precisa revisar suas dependências e perguntar “por que isso é necessário?”
- Todos podem participar da limpeza
2 comentários
Eu também ainda ofereço um build em cjs quando crio bibliotecas, mas espero que, em 2026, bibliotecas que nem sequer têm exemplos em esm e são todas baseadas em
requiredeem uma boa atualizada.Comentários no Hacker News
Hoje em dia, acho que o melhor caminho é desenvolver com JavaScript sem dependências
As bibliotecas padrão de JS/CSS também são excelentes, e análise estática (checagem de JSDoc do TypeScript), módulos ES, web components etc. já são poderosos o suficiente
As pessoas dizem que essa abordagem é ruim para escalabilidade ou manutenção, mas, pela minha experiência, ela na verdade permitiu manter uma estrutura mais simples e fácil de mudar
A maior parte do que frameworks ou ferramentas de build fazem pode ser substituída por recursos nativos do navegador e padrões vanilla
O problema é que isso ainda é um território pouco familiar, então a maior parte do ecossistema de tutoriais gira em torno de grandes frameworks
Na prática, mesmo migrando código React completamente para vanilla, a modularidade se mantém e o tamanho do código aumenta só cerca de 1,5x, enquanto o desempenho até melhora por não haver dependências
Claro, isso não quer dizer que dependências sejam ruins. Só que muitos desenvolvedores estão presos à ideia fixa de que “é obrigatório usar”
Por exemplo, eu crio sites com muitos recursos de mapas, então preciso usar bibliotecas sem alternativa como mapbox/maplibre/openlayers
O cliente também não pagou um centavo sequer de custo de migração
Fiquei curioso para saber como lidam com atualizações de modelo, como neste artigo
Na verdade, ficou mais fácil manter grandes codebases com pouca gente
Com as ferramentas de hoje, implementar por conta própria ficou muito mais fácil do que antes, e isso também combina bem com agentic engineering
O texto foi bem escrito e explica o problema com clareza, sem soar emocional
Acho que parte da causa dessa situação é o fato de JS não ter uma “biblioteca padrão” de verdade
Bom texto, mas acho que a raiz do problema é o próprio acréscimo desnecessário (=bloat)
Dá vontade de citar Saint-Exupéry: “a perfeição é alcançada não quando não há mais nada para adicionar, mas quando não há mais nada para remover”
A maior parte do software é escrita perguntando “como adicionar isso com mais facilidade?” em vez de “como deixar isso mais elegante?”
A resposta é sempre
npm i more-stuffComo no contraste entre Demóstenes e Cícero, código bom é aquele do qual não dá mais para remover nada
JS precisa considerar compatibilidade tanto com navegadores antigos quanto futuros e, por ser uma linguagem centrada em UI, ganha volume com acessibilidade, internacionalização, suporte móvel etc.
Em muitos casos, isso parece um problema de dívida técnica escondida
A causa é não subir o target de compilação para ESx e não atualizar pacotes nem implementações
ES5 já é suportado em todos os navegadores há 13 anos (caniuse.com/es5)
Ambos tratam suas ações como se fossem funcionalidades e mantêm muitos pacotes populares
Por isso é difícil mudar. Às vezes a comunidade critica, mas eles também têm sua própria lógica
Quando você transpila para versões antigas com Babel, o código fica inchado e lento, e mesmo assim muitas vezes não roda em navegadores velhos por limitações de CSS ou de recursos JS
Já houve até caso de polyfill causar problema (polyfill do operador exponencial que não conseguia lidar com BigInt)
Existem consoles, TVs, Androids antigos, iPod touch, navegador embutido do Facebook e vários outros ambientes
Então eu deixo só um módulo externo e resolvo o resto na configuração do transpiler
Antes se sobrescrevia
setTimeoute afins para rastrear assíncronos, mas agora dá para lidar com isso de forma muito mais simples com signalsAcho que alguns autores de pacotes quebram a árvore de dependências artificialmente para aumentar o número de downloads
Não faz sentido existirem pacotes com 7 linhas. Os metadados do lockfile são maiores que o código
No passado, 5% das dependências do create-react-app eram minipacotes de um único autor
Há casos como has-symbols, is-string, ljharb
Por exemplo, a Anthropic oferece Claude grátis para mantenedores de open source com muitos downloads no npm
A competição por downloads acaba ampliando o risco
Mas, em outras culturas, isso é visto justamente como uma boa prática
Antes de criticar o ecossistema JS, vale ler 30 years of br tags
Isso ajuda a entender o processo de evolução de JS e das ferramentas
Dizer simplesmente “o problema são os desenvolvedores JS” é falta de pensamento de engenharia
Precisamos sempre pensar em teorias e práticas melhores
Como o mundo do software muda rápido, às vezes é preciso fazer um “funeral falso” para si mesmo e abandonar práticas ultrapassadas
Estou mantendo uma codebase Node.js com 9 anos e ela tem só 8 dependências, todas sem subdependências
Priorizo os recursos nativos do Node e implemento só o que preciso
Está muito mais estável e me causa muito menos estresse do que antes
A biblioteca padrão do Deno também é excelente, então, junto com os recursos básicos do runtime, dá para fazer apps com poucos pacotes
JS é uma linguagem bem boa se você usá-la com cuidado
Entendo o argumento de segurança entre realms (cross-realm) de pacotes como
is-string, mas, na prática, essas situações são rarasO problema é que o npm facilita demais a publicação, e a filosofia de “vamos quebrar tudo em módulos e publicar” acabou sendo expandida em excesso
O consumidor não audita a árvore de dependências e simplesmente instala, então um custo opcional virou custo padrão
O problema de ponyfill pode ser resolvido com automação
Por exemplo, um bot no estilo Renovate que detecte automaticamente recursos já suportados na versão LTS do Node e os remova seria útil
Há só um princípio para a PWA interna da empresa:
“Atualize para a versão mais recente do Chrome. Se ainda houver problema, aí a gente vê”
Entendo que o Safari use menos memória, mas padronizar por política é mais eficiente
A fala de que “precisamos suportar até ES3 (nível IE6/7)” é realmente difícil de entender
Por motivos de segurança, até sites de banco deveriam bloquear navegadores tão antigos
Atualizar a stack de Webpack, Babel e polyfills é um grande trabalho, então deixam como está
É aquela cultura de “em time que está ganhando não se mexe”