Introdução

A pasta node_modules é, sem exagero, um dos maiores problemas do ecossistema JavaScript moderno. Em 2018, Ryan Dahl — o próprio criador do Node.js — subiu ao palco da JSConf EU para apresentar sua famosa palestra “10 Things I Regret About Node.js”. E adivinha o que estava no topo da lista? O node_modules. A forma como as dependências são resolvidas, armazenadas e duplicadas dentro daquela pasta é um resquício de decisões de design de 2009 que nunca foram corrigidas.

Se você é desenvolvedor JavaScript ou TypeScript, certamente já passou pela experiência de rodar npm install em um projeto Angular aparentemente simples e assistir pasmo enquanto centenas de megabytes de dependências transitivas eram despejadas no seu disco. O meme “node_modules é a pasta mais pesada do universo” existe por um motivo muito real. Mas o problema vai além do tamanho: segurança permissiva, phantom dependencies, ataques de supply chain e uma resolução de módulos que desafia a lógica são apenas alguns dos sintomas.

A boa notícia é que dois runtimes modernos surgiram para resolver esses problemas — cada um com sua filosofia. O Deno, criado pelo próprio Ryan Dahl como uma correção dos erros do Node.js, simplesmente eliminou o node_modules. O Bun, criado por Jarred Sumner e escrito em Zig, manteve o node_modules mas o otimizou radicalmente com hardlinks e um lockfile binário.

Neste artigo, você vai entender em profundidade o que há de errado com o node_modules, conhecer as histórias e motivações por trás do Deno e do Bun, ver um comparativo detalhado entre os três runtimes, e — o mais importante — experimentar na prática com dois projetos Angular reais: um rodando com Bun e outro com Deno, sem node_modules. Se você já leu meu artigo sobre pip É Lento, Poetry Complexo: UV Chegou Resolvendo o Python, vai perceber que o mundo JavaScript está passando por uma revolução análoga.

Vamos começar entendendo como chegamos até aqui.

O Legado do Node.js — E Seus Problemas

Para entender por que o node_modules é um problema, precisamos voltar a 2009. Naquele ano, Ryan Dahl apresentou o Node.js ao mundo. A ideia era revolucionária: usar a engine V8 do Chrome para executar JavaScript fora do navegador, com um modelo de I/O não-bloqueante baseado em event loop. De repente, desenvolvedores frontend podiam escrever código no servidor usando a mesma linguagem. O fullstack JavaScript nasceu.

O npm (Node Package Manager) veio junto e rapidamente se tornou o maior registry de pacotes do mundo. Qualquer desenvolvedor podia publicar e consumir bibliotecas com um simples npm install. O ecossistema explodiu. Frameworks como Express, Koa, Hapi e posteriormente NestJS transformaram o Node.js na escolha padrão para APIs e microsserviços. O modelo de callbacks, embora criticado pelo infame callback hell, evoluiu para Promises e depois para async/await — tornando o código assíncrono muito mais legível. Se você programa em C#, vai reconhecer esse padrão: o async/await do JavaScript foi inspirado diretamente pelo que a Microsoft implementou no C# 5.0, como discutimos no artigo sobre programação assíncrona.

Mas enquanto a linguagem e o runtime evoluíram, uma peça fundamental permaneceu intocada por mais de uma década: o node_modules. A forma como o Node.js resolve dependências foi definida nos primórdios e nunca recebeu uma revisão de design. O resultado é uma série de problemas estruturais que afetam projetos de todos os tamanhos — do hello world à aplicação enterprise.

O Node.js abriu caminho para uma geração inteira de desenvolvedores. Mas as decisões de design de 2009 cobraram seu preço. E é exatamente isso que vou mostrar a seguir.

O Buraco Negro Chamado node_modules

Se existe um símbolo universal dos problemas do ecossistema JavaScript, esse símbolo é a pasta node_modules. Vamos dissecar por que ela merece o apelido de “buraco negro”.

Tamanho absurdo

Um projeto Angular simples — como o meu search-ui/ no repositório blog-zocateli-sample — contém apenas 4 arquivos de código-fonte: models.ts, cliente.service.ts, clientes-grid.component.ts e clientes-grid.component.html. Juntos, esses arquivos somam menos de 5 KB de código real. Mas ao rodar npm install, a pasta node_modules gerada ocupa entre 200 e 500 MB. Isso mesmo: a proporção entre código útil e dependências pode chegar a 1:100.000.

Dependências transitivas

Quando você instala 10 pacotes no seu package.json, está na verdade puxando 800 ou mais dependências transitivas. Cada pacote traz suas próprias dependências, que trazem outras, num efeito cascata que rapidamente sai do controle. Você confia nos seus 10 pacotes — mas confia nos outros 790 que vieram de brinde?

Resolução de módulos complexa

O algoritmo de resolução de módulos do Node.js é uma busca em cascata pelas pastas pai. Quando você faz require('lodash'), o Node.js procura em ./node_modules/lodash, depois em ../node_modules/lodash, depois em ../../node_modules/lodash, e assim por diante, até a raiz do sistema de arquivos. Esse mecanismo é lento, difícil de debugar e fonte de bugs sutis.

Duplicação de pacotes

A mesma biblioteca pode existir em múltiplas versões dentro da árvore de node_modules. Um pacote pode depender de [email protected] enquanto outro depende de [email protected] — e ambas as versões são instaladas separadamente, ocupando espaço em dobro.

Phantom dependencies

Este é um problema insidioso. Pacotes que não estão listados no seu package.json podem ser acessíveis pelo seu código simplesmente porque foram instalados como dependência transitiva de outro pacote. Seu código funciona localmente, mas quebra misteriosamente em outro ambiente onde a árvore de dependências é ligeiramente diferente.

Supply chain attacks

A facilidade de publicar e consumir pacotes no npm tem um lado sombrio. Ataques de supply chain se tornaram recorrentes: o caso event-stream (2018) injetou código malicioso de roubo de criptomoedas em uma biblioteca popular; o ua-parser-js (2021) foi comprometido para incluir mineradores de cripto; e o episódio colors.js/faker.js (2022) mostrou que um único mantenedor frustrado pode sabotar milhares de projetos. O node_modules, por não ter nenhum modelo de sandbox de segurança, executa qualquer código que esteja lá dentro com as mesmas permissões do processo Node.js.

Performance e incompatibilidades

O npm install é notoriamente lento. Lock files como package-lock.json geram conflitos frequentes em equipes grandes. Symlinks se comportam diferentemente entre Windows e Unix. Paths longos no Windows (acima de 260 caracteres) podem quebrar projetos inteiros.

💡 Comparação de escala: O projeto search-ui/ tem ~5 KB de código real e ~350 MB de node_modules. É como levar uma mochila de 70 kg para carregar uma caneta.

O node_modules não é apenas inconveniente — é um problema de design fundamental. E dois runtimes decidiram enfrentá-lo de formas radicalmente diferentes.

Deno: O Arrependimento de Ryan Dahl Que Virou Produto

Em maio de 2018, Ryan Dahl subiu ao palco da JSConf EU com uma apresentação que abalou o ecossistema JavaScript. Na palestra “10 Things I Regret About Node.js”, o criador do Node.js admitiu publicamente que cometeu erros graves de design — e que o node_modules era o pior deles. Mas Dahl não se limitou a apontar problemas: ele apresentou o Deno, um novo runtime construído do zero para corrigir esses erros.

O que é o Deno

Deno é um runtime para JavaScript e TypeScript, escrito em Rust, que executa código na engine V8 (a mesma do Chrome e do Node.js). Mas as semelhanças com o Node.js param aí. O Deno foi projetado com uma filosofia completamente diferente.

O que o Deno resolve

TypeScript nativo. No Node.js, você precisa configurar tsconfig.json, instalar o compilador TypeScript, e geralmente configurar um build step separado. No Deno, TypeScript simplesmente funciona — sem configuração, sem dependência extra.

Sem node_modules. Este é o ponto central. O Deno resolve dependências de três formas alternativas: imports via URL, import maps no deno.json, ou cache global. Em nenhum caso uma pasta node_modules é criada no projeto. As dependências ficam em um cache global compartilhado (~/.cache/deno/ no Linux/macOS, %LOCALAPPDATA%/deno no Windows), o que significa que múltiplos projetos reutilizam as mesmas dependências sem duplicação.

Segurança sandbox. Ao contrário do Node.js, que dá acesso irrestrito ao sistema de arquivos, rede e variáveis de ambiente, o Deno opera em um sandbox por padrão. Para ler um arquivo, você precisa da flag --allow-read. Para fazer uma requisição HTTP, --allow-net. Para acessar variáveis de ambiente, --allow-env. Isso mitiga drasticamente o risco de supply chain attacks — mesmo que um pacote malicioso seja importado, ele não pode acessar nada sem permissão explícita.

Toolchain built-in. O Deno inclui formatador (deno fmt), linter (deno lint), test runner (deno test), benchmark runner (deno bench), e até gerador de documentação. No Node.js, cada uma dessas ferramentas é uma devDependency separada — Prettier, ESLint, Jest, cada um com seus próprios arquivos de configuração e centenas de dependências.

Web Standard APIs. O Deno implementa APIs web padrão como fetch, Web Crypto e Streams. Código que funciona no navegador funciona no Deno com mínimas alterações — algo que nunca foi verdade no Node.js.

Compatibilidade Node.js no Deno 2.x

Quando o Deno 1.x foi lançado, a abordagem era purista: sem npm, sem node_modules, sem compatibilidade. Isso limitou severamente a adoção, já que o ecossistema npm tem milhões de pacotes que desenvolvedores precisam usar diariamente.

O Deno 2.x trouxe uma mudança pragmática: agora é possível importar pacotes npm diretamente usando o prefixo npm:. Isso significa que frameworks como Angular, React e bibliotecas como RxJS podem ser usados no Deno sem reescrever nada — mantendo a vantagem de não ter node_modules no projeto.

⚠️ Nota: O Deno 2.x adicionou compatibilidade npm por pragmatismo — o purismo cedeu à realidade do ecossistema. Mas a filosofia de design continua: as dependências ficam no cache global, não na pasta do projeto.

Prós e contras do Deno

Prós: TypeScript zero-config, segurança granular por sandbox, Web Standards, toolchain completo built-in, sem node_modules, Deno Deploy para deploy na edge.

Contras: Ecossistema menor que o Node.js (embora crescendo rapidamente), mudanças de abordagem entre Deno 1.x e 2.x que geraram confusão na comunidade, adoção corporativa ainda crescendo, e alguns frameworks sem suporte oficial completo.

Bun: Velocidade Absurda Escrita em Zig

Se o Deno nasceu de um arrependimento filosófico, o Bun nasceu de uma frustração com performance. Criado por Jarred Sumner, ex-engenheiro da Stripe, em 2022, o Bun 1.0 foi lançado oficialmente em setembro de 2023 com uma proposta ousada: ser o runtime JavaScript mais rápido do planeta — e, de quebra, substituir npm, webpack e Jest com um único binário.

O que é o Bun

Bun é um runtime JavaScript escrito em Zig (uma linguagem de sistemas de baixo nível) que utiliza a engine JavaScriptCore do Safari, em vez do V8 usado pelo Node.js e Deno. Além de runtime, o Bun é simultaneamente package manager, bundler e test runner — tudo em um único executável.

O que o Bun resolve

Velocidade brutal. O Bun é projetado para performance extrema. O bun install é até 25x mais rápido que o npm install em benchmarks reais. O startup do runtime é significativamente mais rápido que Node.js, o que faz diferença especialmente para CLIs e scripts. A engine JavaScriptCore, embora menos documentada que a V8, é otimizada para inicialização rápida.

Drop-in replacement. Ao contrário do Deno, que exigiu um redesign dos projetos, o Bun funciona como substituto direto do Node.js. Na maioria dos casos, você pode simplesmente trocar node por bun e npm por bun nos seus scripts — e tudo continua funcionando. A compatibilidade com o ecossistema npm é altíssima.

TypeScript e JSX nativos. Assim como o Deno, o Bun entende TypeScript e JSX sem configuração adicional. Sem transpilação manual, sem build step extra.

APIs nativas otimizadas. O Bun oferece APIs proprietárias de alta performance: Bun.serve() para HTTP servers, Bun.file() para leitura eficiente de arquivos, e até um driver SQLite built-in. Essas APIs são significativamente mais rápidas que seus equivalentes no Node.js.

O node_modules no Bun

Aqui está a diferença fundamental entre Bun e Deno: o Bun ainda usa node_modules. Essa é uma decisão consciente de design — manter compatibilidade máxima com o ecossistema existente. Mas o Bun otimiza o node_modules de formas importantes:

  • Hardlinks em vez de cópias. Em sistemas de arquivos que suportam hardlinks (ext4, APFS, NTFS), o Bun cria hardlinks para os arquivos dos pacotes em vez de copiá-los. Isso significa que se dois projetos usam a mesma versão do React, o arquivo físico no disco é compartilhado entre eles — reduzindo significativamente o espaço em disco.
  • Lockfile binário. O bun.lockb é um arquivo binário, não JSON como o package-lock.json. Isso o torna muito mais rápido de gerar e ler, embora não seja legível por humanos.
  • Resolução otimizada. O algoritmo de resolução de dependências do Bun é implementado em Zig e é significativamente mais rápido que o do npm.

Prós e contras do Bun

Prós: Velocidade absurda (install, startup, runtime), drop-in replacement para Node.js, APIs nativas de alta performance, TypeScript/JSX nativos, startup rápido ideal para CLIs.

Contras: Ainda usa node_modules (mesmo otimizado), engine JavaScriptCore menos documentada e com comportamentos diferentes do V8, suporte a Windows que ainda está amadurecendo, comunidade menor que Node.js e Deno.

💡 Dica prática: O Bun é o caminho de menor resistência — troque npm por bun no seu projeto existente e ganhe velocidade imediata, sem reescrever nada.

Comparativo: Node.js vs Deno vs Bun

Agora que você conhece as motivações e arquiteturas de cada runtime, vou colocá-los lado a lado em um comparativo detalhado.

AspectoNode.jsDenoBun
CriadorRyan Dahl (2009)Ryan Dahl (2018)Jarred Sumner (2022)
EngineV8 (Chrome)V8JavaScriptCore (Safari)
Escrito emC++RustZig
TypeScriptPrecisa de configNativoNativo
node_modulesObrigatórioNão (cache global)Sim (otimizado/hardlinks)
SegurançaPermissivoSandbox por padrãoPermissivo
Velocidade installBaseRápidoMuito rápido (~25x npm)
Toolchain built-inNãoSim (fmt, lint, test)Sim (bundler, test)
Compat Node.jsN/AAlta (Deno 2.x, prefix npm:)Muito alta (drop-in)

Análise do comparativo

Engine e linguagem base. Node.js e Deno compartilham a V8, o que significa comportamento idêntico na execução de JavaScript. O Bun, ao usar JavaScriptCore, pode ter diferenças sutis em edge cases — mas em 99% dos cenários, o comportamento é compatível.

Node_modules. Esta é a diferença mais impactante no dia a dia. Node.js cria centenas de MB em cada projeto. Bun cria a mesma estrutura, mas com hardlinks que reduzem o uso real de disco. Deno não cria nada — tudo fica no cache global.

Segurança. Apenas o Deno implementa um modelo de segurança por sandbox. Node.js e Bun confiam que todo código importado é bem-intencionado — um risco real dado o histórico de supply chain attacks no npm.

Toolchain. Node.js exige ESLint, Prettier, Jest, webpack/Vite como dependências separadas. Deno e Bun incluem grande parte dessas ferramentas no próprio runtime.

Quando usar cada um

  • Node.js: Projetos legados, enterprise com ecossistema consolidado, quando a equipe já domina o tooling. Não há razão para migrar projetos estáveis em produção sem uma análise cuidadosa.
  • Deno: Projetos novos TypeScript que priorizam segurança (APIs, CLIs sensíveis), quando você quer evitar node_modules e suas complexidades, ou quando precisa de toolchain mínimo.
  • Bun: Quando velocidade é prioridade, em projetos Node.js existentes que querem ganho imediato trocando apenas o package manager, ou para CLIs e scripts que precisam de startup rápido.

Exemplo Prático: Angular Com Bun

Chega de teoria — vamos ver na prática. No repositório blog-zocateli-sample, criei o projeto frontend/search-ui-bun/ como uma versão do meu grid de clientes rodando com Bun.

O package.json com scripts Bun

O package.json é praticamente idêntico ao de um projeto Node.js, com scripts adaptados para usar bunx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "name": "search-ui-bun",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "bunx ng serve",
    "build": "bunx ng build",
    "test": "bunx ng test"
  },
  "dependencies": {
    "@angular/animations": "^19.2.0",
    "@angular/common": "^19.2.0",
    "@angular/compiler": "^19.2.0",
    "@angular/core": "^19.2.0",
    "@angular/forms": "^19.2.0",
    "@angular/platform-browser": "^19.2.0",
    "@angular/platform-browser-dynamic": "^19.2.0",
    "@angular/router": "^19.2.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.6.0",
    "zone.js": "~0.15.0"
  },
  "devDependencies": {
    "@angular/build": "^19.2.0",
    "@angular/cli": "^19.2.0",
    "@angular/compiler-cli": "^19.2.0",
    "typescript": "~5.7.0"
  }
}

Note que os scripts usam bunx em vez de npx. O bunx é o equivalente do Bun ao npx — executa binários de pacotes sem precisar instalar globalmente.

Configuração do Bun (bunfig.toml)

O arquivo bunfig.toml configura o comportamento do Bun:

1
2
3
4
5
6
7
[install]
# Gera lockfile binário (bun.lockb) — mais rápido que package-lock.json
saveTextLockfile = false

[install.cache]
# Usa cache global para evitar downloads repetidos
disable = false

Executando o projeto

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd frontend/search-ui-bun

# Instalar dependências — observe a velocidade
bun install

# Iniciar o servidor de desenvolvimento
bun run start

# Ou diretamente:
bunx ng serve

O bun install resolve todas as dependências Angular em segundos — onde npm install levaria minutos. O lockfile gerado é o bun.lockb, um arquivo binário compacto.

O componente Angular com standalone

O ClientesGridComponent foi convertido para standalone component (padrão do Angular 19+), importando CommonModule e ReactiveFormsModule diretamente no decorador:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Component({
  selector: 'app-clientes-grid',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  templateUrl: './clientes-grid.component.html'
})
export class ClientesGridComponent implements OnInit, OnDestroy {
  buscaControl = new FormControl('');
  clientes: ClienteDto[] = [];
  // ...
}

A configuração do app usa provideRouter e provideHttpClient, o padrão moderno do Angular:

1
2
3
4
5
6
7
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient()
  ]
};

O código Angular é idêntico — não importa se roda em Node.js ou Bun. A diferença está inteiramente no runtime e no package manager.

ℹ️ Sobre o node_modules: O Bun gera a pasta node_modules, mas com hardlinks. Na prática, a pasta ocupa menos espaço e a instalação é quase instantânea.

Exemplo Prático: Angular Com Deno

Agora vamos ao projeto mais revelador: frontend/search-ui-deno/, que roda Angular sem node_modules.

O deno.json: import maps e tasks

No lugar do package.json, o Deno usa o deno.json para mapear dependências via import maps e definir tarefas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "nodeModulesDir": "none",
  "imports": {
    "@angular/core": "npm:@angular/core@^19.2.0",
    "@angular/common": "npm:@angular/common@^19.2.0",
    "@angular/common/http": "npm:@angular/common@^19.2.0/http",
    "@angular/forms": "npm:@angular/forms@^19.2.0",
    "@angular/router": "npm:@angular/router@^19.2.0",
    "@angular/platform-browser": "npm:@angular/platform-browser@^19.2.0",
    "rxjs": "npm:rxjs@~7.8.0",
    "rxjs/operators": "npm:rxjs@~7.8.0/operators"
  },
  "tasks": {
    "dev": "deno run -A npm:@angular/cli@^19.2.0 serve",
    "build": "deno run -A npm:@angular/cli@^19.2.0 build"
  }
}

A chave é a configuração "nodeModulesDir": "none". Ela instrui o Deno a não criar a pasta node_modules — todas as dependências são resolvidas para o cache global do Deno. Os import maps no campo "imports" mapeiam os pacotes Angular para suas versões npm usando o prefixo npm:, uma feature do Deno 2.x.

Executando o projeto

1
2
3
4
5
6
7
cd frontend/search-ui-deno

# Resolver dependências no cache global — sem node_modules
deno install

# Iniciar o servidor de desenvolvimento
deno task dev

Após rodar deno install, olhe o diretório do projeto. Não há pasta node_modules. Não há package-lock.json. Apenas os arquivos de código-fonte e configuração. As dependências foram baixadas para o cache global em ~/.cache/deno/ e são compartilhadas com todos os outros projetos Deno na máquina.

O código Angular permanece o mesmo

O app.config.ts, o ClienteService, o ClientesGridComponent — todos são idênticos ao projeto Bun e ao original Node.js. O Angular não precisa saber qual runtime está executando. Os imports seguem os mesmos caminhos:

1
2
3
4
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { PagedResult, ClienteDto } from './models';

O Deno resolve @angular/core consultando o import map no deno.json, que aponta para npm:@angular/core@^19.2.0, que é buscado no cache global. Nenhuma pasta local é criada.

💡 O ponto-chave: Com Deno, o projeto inteiro ocupa apenas os seus arquivos de código — nada de 300MB parasitas. O cache global é compartilhado, então a segunda vez que você usa Angular em outro projeto, a resolução é instantânea.

Estrutura de Pastas: A Diferença Visual

Nada ilustra melhor a diferença entre os três runtimes do que uma comparação visual da estrutura de pastas de cada projeto.

Comparativo visual de estrutura de pastas Node.js vs Bun vs Deno mostrando diferença de tamanho do node_modules

search-ui/ (Node.js original)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
search-ui/
├── node_modules/          ← ~350 MB de dependências
│   ├── @angular/
│   ├── rxjs/
│   ├── typescript/
│   └── ... (800+ pastas)
├── package.json
├── package-lock.json
└── src/
    ├── models.ts
    ├── cliente.service.ts
    ├── clientes-grid.component.ts
    └── clientes-grid.component.html

search-ui-bun/ (Bun)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
search-ui-bun/
├── node_modules/          ← ~150 MB (hardlinks, menos espaço real)
│   ├── @angular/
│   ├── rxjs/
│   └── ...
├── bun.lockb              ← Lockfile binário (rápido)
├── bunfig.toml
├── package.json
├── angular.json
├── tsconfig.json
└── src/
    ├── main.ts
    ├── index.html
    ├── styles.css
    └── app/
        ├── app.component.ts
        ├── app.config.ts
        ├── app.routes.ts
        ├── models.ts
        ├── cliente.service.ts
        ├── clientes-grid.component.ts
        └── clientes-grid.component.html

search-ui-deno/ (Deno — SEM node_modules)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
search-ui-deno/
├── deno.json              ← Import maps + tasks
├── deno.lock              ← Lockfile (gerado após deno install)
├── angular.json
├── tsconfig.json
└── src/
    ├── main.ts
    ├── index.html
    ├── styles.css
    └── app/
        ├── app.component.ts
        ├── app.config.ts
        ├── app.routes.ts
        ├── models.ts
        ├── cliente.service.ts
        ├── clientes-grid.component.ts
        └── clientes-grid.component.html

Comparativo de tamanho em disco

ProjetoCódigo-fonteDependênciasTotal em disco
search-ui (Node.js)~5 KB~350 MB (node_modules)~350 MB
search-ui-bun (Bun)~20 KB~150 MB (hardlinks)~150 MB*
search-ui-deno (Deno)~20 KB0 (cache global)~20 KB**

* O tamanho real em disco do Bun depende de quantos projetos compartilham hardlinks. Se for o único projeto, o tamanho é similar ao npm; se houver múltiplos projetos, o espaço é reutilizado.

** As dependências do Deno existem no cache global (~/.cache/deno/), compartilhado entre todos os projetos. O tamanho total não desaparece — mas não é duplicado por projeto.

A diferença é visual e brutal. O projeto Deno é limpo — apenas código. O projeto Bun tem node_modules, mas otimizado. O projeto Node.js carrega o peso de uma década de decisões de design não revisadas.

Dicas e Boas Práticas

Com base no que mostrei, aqui estão seis recomendações práticas para incorporar Deno e Bun no seu workflow de desenvolvimento.

1. Comece testando Bun como package manager

A forma mais segura de experimentar é usar o Bun apenas como substituto do npm em projetos Node.js existentes. Troque npm install por bun install e npx por bunx. O risco é zero — se algo der errado, volte para o npm. Mas a chance de algo dar errado é mínima, e o ganho de velocidade é imediato.

2. Use Deno para projetos novos TypeScript que precisam de segurança

Se você está iniciando uma API, CLI ou serviço que lida com dados sensíveis, o modelo de sandbox do Deno é um diferencial real. A necessidade de flags explícitas para acessar rede, sistema de arquivos e variáveis de ambiente adiciona uma camada de proteção que inexiste no Node.js e no Bun.

3. Não abandone Node.js em produção sem testes extensivos

Migrar um projeto estável em produção para Deno ou Bun deve ser uma decisão técnica bem fundamentada, não uma decisão emocional. Teste extensivamente, verifique compatibilidade de todas as dependências, e tenha um plano de rollback.

4. Aproveite o toolchain built-in para reduzir devDependencies

Com Deno, você pode eliminar ESLint (deno lint), Prettier (deno fmt), Jest (deno test) e até benchmarking (deno bench) das suas devDependencies. Menos pacotes = menos superfície de ataque = node_modules mais leve (ou inexistente).

5. Verifique suporte oficial antes de migrar frameworks

Nem todo framework JavaScript suporta oficialmente Deno ou Bun. Antes de migrar, consulte a documentação oficial. Angular, por exemplo, funciona com ambos via CLI, mas o suporte oficial pode variar em edge cases. O ecossistema NestJS tem bom suporte a Bun, mas o suporte a Deno ainda está evoluindo.

6. Estratégia híbrida: bun install + node para execução

Uma abordagem pragmática é usar bun install pela velocidade (gerando o node_modules com hardlinks) e manter node como runtime de execução. Isso dá o melhor dos dois mundos: instalação rápida e compatibilidade máxima. É uma transição suave antes de adotar Bun como runtime completo.

Conclusão

O node_modules é mais do que uma pasta pesada — é o sintoma mais visível de decisões de design tomadas em 2009 que nunca foram revisadas. O Node.js revolucionou o desenvolvimento web ao trazer JavaScript para o servidor, mas deixou um legado estrutural que custa tempo, disco e segurança para toda a comunidade.

O Deno, criado pelo próprio Ryan Dahl como uma correção de seus arrependimentos, eliminou o node_modules por completo. As dependências vão para um cache global, o TypeScript funciona nativamente, e um modelo de sandbox protege contra código malicioso. O Bun, criado por Jarred Sumner com foco obsessivo em performance, manteve o node_modules por pragmatismo mas o otimizou com hardlinks e um lockfile binário, entregando velocidades de instalação até 25x superiores ao npm.

A competição entre Node.js, Deno e Bun é saudável e beneficia todos os desenvolvedores JavaScript. O Node.js continua sendo o runtime mais maduro e estável para produção. O Deno oferece a visão mais moderna de como um runtime deveria funcionar. O Bun entrega ganhos imediatos de performance com mínima fricção de adoção.

Se você usa Angular, React, Vue ou qualquer framework JavaScript, experimente os projetos práticos que criamos: o search-ui-bun/ mostra como rodar Angular com Bun em segundos, e o search-ui-deno/ demonstra um projeto Angular funcionando completamente sem node_modules. Clone o repositório blog-zocateli-sample, rode os comandos, e veja com seus próprios olhos a diferença que um runtime moderno faz. O futuro do node_modules pode não ser sua extinção total — mas certamente não será mais um buraco negro.

Leia Também

Referências