Como reduzir dependências em um projeto
Como reduzir dependências em um projeto, com exemplos práticos, erros comuns e cuidados importantes para aplicar com segurança.
Você instalou um pacote para não escrever três linhas
Era para ser rápido: precisava checar se uma string estava vazia, deu um npm install num pacote qualquer, segui em frente. Seis meses depois, um audit acusa uma vulnerabilidade de severidade alta numa dependência transitiva três níveis abaixo daquele pacotinho que existe só para fazer str.trim().length === 0. Você não escolheu aquela dependência. Ela veio de carona. E agora ela é problema seu, no seu deploy, no seu pager.
Este texto é sobre reduzir dependências de forma consciente, com exemplos práticos e os cuidados para não exagerar na faxina. O risco maior nem é a dependência que você vê — é a abandonada, que ninguém atualiza e que um dia vira porta de entrada; sobre isso vale ler como identificar dependências abandonadas.
Toda dependência é um passivo, não um ativo
A gente trata package.json como lista de compras: achou útil, adiciona. Mas cada dependência é um passivo que você assume para sempre. Ela aumenta a superfície de ataque (supply chain), pesa no bundle, exige manutenção quando lança breaking change e abre espaço para conflito de versão com outra dep que pede a mesma lib em outra major.
O caso do left-pad, lá em 2016, deveria ter encerrado o assunto. Um pacote de onze linhas que preenchia uma string com zeros à esquerda foi removido do npm e quebrou builds do mundo inteiro, incluindo projetos enormes. Onze linhas. A lição não é "nunca use dependências", é "saiba o que você está terceirizando e por quê". Terceirizar isArray é entregar a chave de casa para alguém que você nunca viu.
Quando internalizar e quando puxar um pacote
A pergunta que eu faço antes de instalar qualquer coisa: o custo de manter isso na minha base é maior ou menor que o custo de manter a dependência? Para libs pequenas e estáveis, internalizar quase sempre ganha.
- Internalize quando é pouca lógica, sem casos extremos perversos, e que você entende por completo. Um debounce, um slugify simples, um formatador de moeda básico. Dez a trinta linhas testadas valem mais que uma dependência viva.
- Puxe o pacote quando o domínio é traiçoeiro e cheio de armadilhas: parsing de data e fuso horário, criptografia, parser de PDF, manipulação de timezone. Aqui, escrever do zero é arrogância. Use a lib madura, com comunidade e testes que você jamais cobriria sozinho.
A regra de bolso: se você consegue escrever e testar em uma tarde, e o domínio não tem pegadinha de borda, internalize.
Audite o que você realmente usa
Boa parte das dependências de um projeto antigo não é mais usada por ninguém. O código que importava foi removido, mas o package.json continua carregando o peso. Antes de cortar, descubra de onde cada coisa vem:
# por que essa dependência está aqui? quem a puxou?
npm ls cross-spawn
# árvore completa, só de produção
npm ls --omit=dev --all
# o que está desatualizado
npm outdated
npm ls <pacote> mostra a cadeia: muitas vezes você descobre que não instalou aquilo diretamente, ele é transitivo de outra coisa. Esse é o ponto cego mais perigoso — você audita as suas vinte dependências diretas e ignora as trezentas transitivas que vieram junto. A árvore transitiva é onde mora o risco real de supply chain.
Para inspecionar o package.json e o lockfile com calma, formatar o JSON ajuda a enxergar a estrutura sem se perder em chaves aninhadas; eu uso o Formatador de JSON para deixar package-lock.json legível antes de rastrear de onde uma versão veio.
Separe dev de prod de verdade
Um erro silencioso mas comum: ferramenta de teste, linter, bundler ou tipo do TypeScript instalado como dependência de produção. Isso infla a árvore que vai para o runtime e, em alguns cenários de build, acaba no artefato final.
# isso é build-time, não runtime
npm install --save-dev vitest eslint @types/node
# isso roda em produção
npm install zod
A pergunta certa: esse pacote precisa existir quando a aplicação está rodando em produção? Se a resposta é não — é ferramenta, é só build —, então --save-dev, sempre. Manter essa fronteira limpa reduz a superfície que de fato importa e deixa o npm ls --omit=dev honesto.
A biblioteca padrão primeiro
Antes de instalar qualquer coisa, vale a pergunta chata: a linguagem já não faz isso? Array.prototype.flat, Object.fromEntries, structuredClone, crypto.randomUUID, Intl.NumberFormat — muita coisa que justificava um pacote há cinco anos hoje é nativa. Em Python e Go o efeito é ainda mais forte: a stdlib cobre HTTP, JSON, hashing e datas sem nenhuma dependência externa.
Cada vez que você resolve algo com a biblioteca padrão, você elimina um passivo de manutenção, um vetor de supply chain e um nó na árvore de versões. A stdlib não é abandonada, não tem CVE surpresa de um mantenedor sumido, e versiona junto com o runtime. É a dependência mais barata que existe, porque você já a tem.
Perguntas frequentes
Reduzir dependências melhora a segurança de verdade?
Sim, e de forma direta. Menos dependências significam menos código de terceiros executando no seu processo, menos transitivas para auditar e menos chance de um pacote comprometido entrar pela cadeia. Você não elimina o risco de supply chain, mas reduz drasticamente a superfície dele.
Como sei se uma dependência ainda é usada no projeto?
Combine a árvore (npm ls) com uma busca pelo nome do pacote no código-fonte. Se nada importa diretamente e ela não é transitiva de algo necessário, é candidata a remoção. Ferramentas de análise de imports também ajudam a flagrar dependências órfãs.
Internalizar código não é reinventar a roda?
Para domínios complexos, sim, e aí você não deveria fazer isso. Mas copiar trinta linhas testadas de um utilitário trivial não é reinventar a roda, é assumir responsabilidade por algo que você já entende por completo. O equilíbrio está em reservar dependências para o que é genuinamente difícil.
Vale a pena se preocupar com o tamanho do bundle no backend?
No backend o bundle pesa menos, mas a árvore transitiva ainda importa para tempo de instalação, superfície de ataque e tempo de cold start em ambientes serverless. Mesmo sem o problema de download do navegador, menos dependências significam deploy mais rápido e menos coisa para dar errado.
O que levar para o próximo install
Antes do próximo npm install, faça a pergunta: isso é um passivo que vale a pena assumir? Prefira a biblioteca padrão, internalize o que é trivial e estável, e reserve dependências para domínios genuinamente difíceis. Audite a árvore transitiva, não só as suas diretas, e mantenha a fronteira dev/prod limpa. Dependência não é grátis — ela é uma promessa de manutenção que você faz para o futuro. Faça menos promessas.
- 01 Licenças MIT, Apache e GPL: diferenças práticas para devs MIT, Apache 2.0 e GPL não são a mesma coisa. Entenda permissiva vs copyleft, proteção de patentes e compatibilidade antes de colocar código em produção.
- 02 Como investigar falta de espaço em disco no Linux Um método de diagnóstico para descobrir por que o disco está cheio: df para achar o mount, du para o diretório, e os casos que confundem como inodes e arquivos deletados.