Bun é um conjunto de ferramentas completo para desenvolvimento com JavaScript e TypeScript, reunindo em um único executável um runtime rápido, um gerenciador de pacotes, um bundler e um executor de testes. Essa abordagem reduz a necessidade de múltiplos programas e bibliotecas externas, simplificando a configuração e o fluxo de trabalho em projetos modernos.
Esse runtime foi projetado para ser um substituto direto do Node.js em muitos cenários, mantendo compatibilidade com grande parte do ecossistema já existente. Ao mesmo tempo, oferece melhorias importantes em desempenho, consumo de memória e praticidade, graças ao uso da linguagem Zig na implementação e do motor JavaScriptCore como base para executar o código.
Conceito de runtime e posição do Bun nesse contexto
Um runtime JavaScript é o ambiente que interpreta e executa código JavaScript fora do navegador, oferecendo APIs para acessar arquivos, rede, processos e outros recursos do sistema operacional. Node.js, Deno e Bun são exemplos de runtimes que permitem essa integração entre JavaScript e o sistema. Nesse cenário, o Bun surge como um runtime moderno cujo foco está no alto desempenho e na redução de complexidade.
Em vez de funcionar somente como um interpretador de código, o Bun incorpora ferramentas comuns do dia a dia de desenvolvimento, como gerenciador de pacotes e executor de testes. Essa integração reduz a necessidade de instalar diversos programas separados, como npm, yarn, Jest e Webpack. Dessa forma, o runtime deixa de ser apenas uma camada de execução e se torna um kit completo de desenvolvimento.
O Bun é distribuído como um único binário chamado bun, o que facilita instalação, atualização e distribuição em diferentes sistemas. Esse binário inclui o runtime, o gerenciador de pacotes, o bundler e a CLI (interface de linha de comando) unificada. Essa abordagem contrasta com ecossistemas que exigem múltiplas ferramentas, bibliotecas e scripts adicionais para formar uma pilha mínima de desenvolvimento.
Outro ponto importante é a compatibilidade com o ecossistema Node.js, que permite reaproveitar projetos existentes com poucas ou nenhuma modificação. Isso inclui suporte ao modelo de módulos, variáveis globais comuns (como process e Buffer) e o algoritmo de resolução de módulos do Node.js. Essa compatibilidade reduz o atrito ao adotar o Bun em projetos já em andamento.
Arquitetura interna: Zig, JavaScriptCore e foco em desempenho
A base de desempenho do Bun está em duas escolhas principais: o uso da linguagem Zig e do motor JavaScriptCore. Zig é uma linguagem de baixo nível que oferece controle refinado de memória e recursos, permitindo que o runtime seja otimizado para velocidade e consumo reduzido. Esse controle gera executáveis mais enxutos e previsíveis, o que favorece cenários com alta carga e muitos processos.
O JavaScriptCore é o motor JavaScript desenvolvido pela Apple para o navegador Safari. Ele é responsável por interpretar, compilar e executar o código JavaScript, bem como realizar otimizações dinâmicas em tempo de execução. No contexto do Bun, esse motor é integrado de forma a reduzir o tempo de inicialização e o uso de memória em comparação com o motor V8, utilizado pelo Node.js. Em muitos testes, scripts simples iniciam várias vezes mais rápido no Bun do que no Node.js.
Além disso, o Bun inclui um transpilador nativo escrito em Zig para converter TypeScript e JSX (sintaxe de componentes de interface) em JavaScript. O termo transpilador designa uma ferramenta que converte código-fonte de uma linguagem ou sintaxe para outra, mantendo o mesmo nível de abstração. A integração desse transpilador torna desnecessário o uso de ferramentas adicionais para compilar TypeScript antes da execução.
O gerenciamento de memória também é otimizado, com opções específicas para ambientes com recursos limitados. Um exemplo é a flag --smol, que ajusta o comportamento do coletor de lixo para reduzir o consumo de memória, mesmo com algum impacto no desempenho. Essa combinação de escolhas arquiteturais torna o Bun especialmente atrativo para serviços que exigem grande quantidade de instâncias leves.
Instalação do Bun em diferentes sistemas operacionais
O Bun oferece suporte oficial a Linux (arquiteturas x64 e arm64), macOS (Intel e Apple Silicon) e Windows (x64). Em Linux, recomenda-se um kernel a partir da versão 5.6 para alcançar estabilidade e recursos modernos, embora versões um pouco mais antigas também funcionem. Em Windows, o runtime pode ser usado de forma nativa ou por meio de ambientes como WSL, dependendo da configuração de desenvolvimento.
Existem vários métodos para instalar o Bun, variando conforme o gerenciador de pacotes ou ferramenta disponível no sistema. De forma geral, todos os métodos resultam em um único binário chamado bun, tornado acessível pelo PATH (lista de pastas onde o sistema procura executáveis). A seguir, estão alguns comandos comuns para instalação, que ilustram como o processo ocorre na prática.
# Com script de instalação (método recomendado em muitos casos)
curl -fsSL https://bun.sh/install | bash
# Com npm, usando o ecossistema Node.js já instalado
npm install -g bun
# Com Homebrew, em sistemas macOS e algumas distribuições Linux
brew tap oven-sh/bun
brew install bun
# Com Docker, executando o bun em um contêiner
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
# Após a instalação, é possível verificar a versão instalada
bun --version
Independente do método de instalação, a verificação da versão com bun --version confirma se o binário está corretamente configurado. Em ambientes de desenvolvimento contínuo, é comum manter o Bun atualizado, e para isso existe o comando bun upgrade, que baixa a versão estável mais recente. Também é possível instalar versões experimentais conhecidas como “canary”, usando a flag --canary.
Comando bun run e execução de arquivos de código-fonte
O comando bun run é a base do uso diário do runtime, pois permite executar arquivos JavaScript e TypeScript diretamente. Além disso, o Bun possui suporte nativo a JSX e TSX, tornando desnecessário configurar ferramentas extras para interpretar essas sintaxes. Cada arquivo é transpilado em tempo real pelo transpilador interno e em seguida executado pelo runtime baseado em JavaScriptCore.
Para ilustrar o uso do bun run, é possível construir um exemplo simples que leia um parâmetro de linha de comando e imprima uma mensagem personalizada. Esse tipo de programa demonstra a rapidez de inicialização e o suporte imediato a TypeScript em um único passo. A seguir, há um arquivo de exemplo em TypeScript e os comandos correspondentes para sua execução.
// arquivo: saudacao.ts
// Lê o nome a partir dos argumentos da linha de comando
const nome = process.argv[2] ?? "Mundo";
// Monta uma mensagem simples
const mensagem = `Olá, ${nome}! Este código está rodando no Bun.`;
// Exibe a mensagem no console
console.log(mensagem);
Com o arquivo criado, o comando a seguir executa o programa usando o Bun como runtime TypeScript sem nenhuma configuração adicional de compilador. Note que o arquivo é transpilado internamente antes da execução, sem gerar arquivos intermediários visíveis.
# Execução direta de um arquivo TypeScript com o Bun
bun run saudacao.ts Ana
# Saída esperada:
# Olá, Ana! Este código está rodando no Bun.
Execução sem a palavra-chave run e modo de observação
Além de bun run, o Bun permite executar arquivos de forma mais curta, omitindo a palavra-chave run. Nesse caso, o comando bun arquivo.ts tem o mesmo efeito de bun run arquivo.ts, facilitando o uso diário. Esse comportamento é chamado de comando “naked”, pois utiliza apenas o nome do binário seguido do arquivo ou script.
Para fluxos de desenvolvimento que exigem recarga automática após alterações em arquivo, o Bun oferece o modo de observação por meio da flag --watch. Nesse modo, o runtime monitora mudanças nos arquivos e reexecuta o programa sempre que uma modificação é detectada. É importante ressaltar que as flags do Bun devem ser posicionadas logo após o comando bun, antes de run ou do nome do script.
# Execução "naked" de um arquivo TypeScript
bun saudacao.ts Ana
# Execução em modo de observação, recompilando e reexecutando a cada alteração
bun --watch run saudacao.ts Ana
# Exemplo incorreto (a flag seria repassada ao script em vez do Bun):
# bun run saudacao.ts --watch
O posicionamento correto de flags evita confusão entre parâmetros destinados ao runtime e argumentos que pertencem ao programa em si. Esse detalhe torna-se especialmente importante em scripts mais complexos que esperam receber seus próprios parâmetros de linha de comando. Em ambientes de desenvolvimento com recarga frequente, o modo watch ajuda a reduzir o tempo entre mudanças no código e a verificação do resultado no console.
Em conjunto com o modo de observação, é comum utilizar bibliotecas de servidor HTTP ou frameworks, criando fluxos de desenvolvimento com resposta quase imediata à edição de arquivos. Embora o próprio Bun ofereça funcionalidades de servidor e APIs web, o exemplo anterior ilustra apenas o comportamento básico de recarga automática através da CLI.
Execução de scripts definidos no package.json
O Bun também executa scripts definidos no arquivo package.json, seguindo um padrão similar a ferramentas como npm e yarn. Um script é um comando de shell associado a um nome, permitindo agrupar tarefas comuns como limpar diretórios, iniciar servidores ou rodar ferramentas de build. Essa integração facilita o uso do Bun em projetos já configurados para Node.js.
Quando há scripts definidos no package.json, o comando bun run <nome-do-script> executa o comando de shell correspondente em um subshell apropriado. Em sistemas Linux e macOS, o Bun procura shells como bash, sh e zsh, usando o primeiro que encontrar. Em Windows, o runtime usa um shell interno com sintaxe semelhante à do bash, permitindo reuso de scripts cross-platform.
{
"name": "exemplo-bun-scripts",
"version": "1.0.0",
"scripts": {
"clean": "rm -rf dist && echo 'Diretório dist removido.'",
"dev": "bun run servidor.ts"
},
"dependencies": {}
}
Com esse package.json em um projeto, o Bun executa os scripts de forma muito rápida, com tempos de inicialização menores que os observados em npm run. No exemplo a seguir, há uma sequência com a execução do script de limpeza e depois do script de desenvolvimento, ilustrando o fluxo de trabalho básico com scripts em projetos Bun.
# Listando scripts disponíveis
bun run
# Executando o script "clean"
bun run clean
# Saída esperada:
# rm -rf dist && echo 'Diretório dist removido.'
# Diretório dist removido.
# Executando o script "dev"
bun run dev
Gerenciador de pacotes integrado e comandos básicos
O Bun inclui um gerenciador de pacotes compatível com o ecossistema do Node.js, desempenhando papel semelhante ao npm, yarn ou pnpm. Essa ferramenta é responsável por instalar dependências listadas em package.json, gerenciar o cache de pacotes e criar links simbólicos para reaproveitar módulos compartilhados. Essa abordagem reduz o espaço em disco e acelera instalações subsequentes.
Ao instalar um pacote, o Bun conecta-o a um repositório central, evitando que múltiplos projetos baixem cópias duplicadas dos mesmos módulos. O uso de links simbólicos é uma técnica que já aparece em outras ferramentas, mas a implementação do Bun é fortemente focada em desempenho. Benchmarks divulgados pela equipe indicam tempos de instalação bem menores em comparação a gerenciadores mais antigos.
# Inicializando um novo projeto com package.json
bun init
# Instalando uma dependência de produção
bun install react
# Instalando múltiplas dependências de uma vez
bun install react react-dom
# Instalando uma dependência de desenvolvimento
bun install --dev typescript
# Executando um binário de pacote sem instalação global
bunx cowsay "Olá com Bun!"
O comando bun install lê o arquivo package.json, bem como o arquivo de lock (se existir), e baixa todas as dependências de forma rápida. Já o comando bunx executa binários fornecidos por pacotes, sem exigir instalação global, comportamento semelhante ao npx do npm. Esse conjunto de funcionalidades reduz a quantidade de ferramentas necessárias para o fluxo de desenvolvimento.
Com essa integração, o Bun passa a substituir não apenas o Node.js como runtime, mas também o npm, yarn ou pnpm como gerenciadores de pacotes. A simplicidade de um único binário que cuida de tudo evita configurações duplicadas e processos em paralelo para tarefas comuns, como instalação, execução de scripts e testes.
Bundler integrado e empacotamento de código
O termo bundler descreve uma ferramenta que agrupa vários arquivos de código e suas dependências em um ou poucos arquivos otimizados para distribuição. Em projetos web, esse processo é essencial para reduzir requisições ao servidor, aplicar minificação (remoção de espaços e comentários) e realizar otimizações específicas para produção. No ecossistema Node.js, é comum depender de bundlers externos como Webpack, Rollup, Parcel ou esbuild.
O Bun oferece um bundler integrado com suporte a diferentes formatos de módulo e otimizações internas de desempenho. Comparativos de desempenho divulgados indicam que esse bundler pode ser significativamente mais rápido que alternativas tradicionais, chegando a superar em várias vezes ferramentas como Webpack e Rollup em tarefas de build. Essa velocidade é especialmente útil em grandes bases de código com muitos arquivos e dependências.
# Exemplo de empacotamento de um ponto de entrada para produção
bun bundle src/index.ts --out-dir dist
# O comando gera um arquivo empacotado em "dist",
# pronto para ser servido em produção ou consumido por outro processo.
O bundler respeita as importações definidas no código-fonte e aplica transformações necessárias para que o resultado seja executável em ambientes específicos, como navegadores. Em muitos casos, essa capacidade elimina a necessidade de um Webpack ou Rollup separados, simplificando o pipeline de build. A integração com o runtime também facilita o uso do mesmo conjunto de ferramentas para desenvolvimento e produção.
Com o bundler interno, o Bun se posiciona como uma solução completa tanto para back-end quanto para front-end, especialmente em projetos que utilizam TypeScript e frameworks modernos. A ausência de múltiplos arquivos de configuração de bundlers externos reduz a carga cognitiva e o tempo gasto apenas em ajustes de build.
Executor de testes integrado (bun test)
Testes automatizados são fundamentais para garantir a qualidade e a estabilidade de aplicações, permitindo detectar erros antes que cheguem à produção. O Bun incorpora um executor de testes próprio, com sintaxe simples e focado em velocidade, reduzindo a necessidade de configurar estruturas externas como Jest ou Mocha em muitos casos. Esse executor é chamado por meio do comando bun test.
O executor de testes trabalha em harmonia com o restante do runtime, aproveitando o suporte nativo a TypeScript e JSX. Isso significa que arquivos de teste escritos diretamente em TypeScript podem ser executados sem configuração extra de transpilações ou loaders especiais. Além disso, o desempenho rápido de inicialização reduz o tempo total de execução em grandes suítes de testes.
// arquivo: calculadora.test.ts
import { test, expect } from "bun:test";
// Função simples a ser testada
function somar(a: number, b: number): number {
return a + b;
}
// Caso de teste básico
test("somar dois números inteiros", () => {
const resultado = somar(2, 2);
expect(resultado).toBe(4);
});
// Outro caso de teste
test("somar número negativo e positivo", () => {
const resultado = somar(-3, 5);
expect(resultado).toBe(2);
});
Com o arquivo de teste criado, a execução dos testes ocorre com um único comando no terminal. O Bun procura arquivos de teste de acordo com convenções de nomes, como sufixos .test.ts ou .spec.ts, permitindo organização clara da base de testes.
# Execução da suíte de testes integrada do Bun
bun test
# Saída típica inclui cada teste executado e um resumo:
# PASS calculadora.test.ts
# 2 tests passed
Leitura de código a partir de stdin com bun run -
Em algumas situações, é útil executar pequenos trechos de código JavaScript ou TypeScript sem a necessidade de salvar tudo em um arquivo. Para isso, o Bun oferece o modo stdin (entrada padrão), em que o código é lido diretamente do terminal ou de um pipe. Esse modo é ativado com o comando bun run -, em que o hífen indica a leitura a partir da entrada padrão.
Nesse modo, o Bun trata todo o código recebido como TypeScript com suporte a JSX, o que permite testar rapidamente recursos da linguagem, declarações de tipos ou trechos que normalmente exigiriam configuração de build. Esse recurso é particularmente útil para experimentos, scripts temporários ou demonstrações em linha de comando.
# Execução de um trecho simples em JavaScript via stdin
echo "console.log('Olá a partir do stdin com Bun');" | bun run -
# Saída esperada:
# Olá a partir do stdin com Bun
# Exemplo redirecionando um arquivo .js tratado como TypeScript
echo "console.log('Isto é tratado como TypeScript!' as any)" > exemplo.js
bun run - < exemplo.js
# Saída esperada:
# Isto é tratado como TypeScript!
Essa abordagem elimina a necessidade de criar arquivos temporários apenas para testar uma ideia rápida. Em conjunto com o suporte a TypeScript, permite experimentar recursos de tipagem estática e inferência de tipos em um ambiente interativo, semelhante a um REPL mais flexível. Em pipelines automatizados, bun run - também pode receber código gerado dinamicamente por outros programas.
Essa capacidade de ler código diretamente do stdin aproxima o Bun de ambientes interativos de script encontrados em outras linguagens, oferecendo flexibilidade para uso em automações, testes diretos e demonstrações em tempo real durante o desenvolvimento.
Parâmetros avançados: profundidade de console e modo smol
O Bun inclui alguns parâmetros avançados que ajustam o comportamento da inspeção de objetos e da gestão de memória. Um deles é o --console-depth, que controla quantos níveis de propriedades são exibidos quando um objeto profundo é impresso com console.log. Em estruturas de dados muito aninhadas, essa configuração evita saídas excessivamente longas ou pouco legíveis.
Outro parâmetro relevante é o --smol, voltado para ambientes com memória limitada, como contêineres com recursos restritos ou dispositivos embarcados. Essa opção faz com que o coletor de lixo do runtime seja acionado com maior frequência, reduzindo o crescimento do heap (área de memória usada para alocação dinâmica). O impacto é um consumo menor de memória às custas de alguma perda de desempenho.
// arquivo: console-profundo.ts
const objetoAninhado = { a: { b: { c: { d: { e: "nível profundo" } } } } };
// Console padrão (profundidade 2)
console.log("Padrão:", objetoAninhado);
// Com profundidade maior, mais níveis são exibidos
console.log("Detalhado:", objetoAninhado);
# Execução com profundidade padrão (2)
bun run console-profundo.ts
# Saída típica (exemplo):
# Padrão: { a: { b: [Object] } }
# Detalhado: { a: { b: [Object] } }
# Execução com profundidade maior
bun --console-depth 5 run console-profundo.ts
# Saída típica (exemplo):
# Padrão: { a: { b: { c: { d: { e: 'nível profundo' } } } } }
# Detalhado: { a: { b: { c: { d: { e: 'nível profundo' } } } } }
Em cenários limitados por memória, o parâmetro --smol passa a ser útil para evitar que o heap cresça rapidamente. Embora o Bun já considere limitações de memória do sistema e de cgroups, essa flag direciona o coletor de lixo a agir de forma ainda mais conservadora quanto ao crescimento da memória.
# Execução em modo de memória reduzida
bun --smol run saudacao.ts
# O comportamento funcional do programa é o mesmo,
# porém o coletor de lixo trabalha com maior frequência
# para manter o uso de memória mais baixo.
Exemplo completo: pequeno servidor HTTP com Bun
Para ilustrar o uso integrado de várias funcionalidades do Bun, é útil montar um exemplo que combine TypeScript, servidor HTTP, scripts em package.json e execução de testes. O exemplo a seguir descreve um pequeno servidor de API que responde com dados JSON e um teste automatizado que verifica a resposta. Isso demonstra o uso de bun run, bun test e do gerenciador de pacotes em um mini projeto.
Primeiro, é preciso criar um arquivo server.ts que exponha um endpoint HTTP simples usando as APIs do Bun. Esse servidor responderá na porta 3000 com uma mensagem em formato JSON que contém uma saudação e um carimbo de data e hora em ISO.
// arquivo: server.ts
// Criação de um servidor HTTP simples usando Bun
const servidor = Bun.serve({
port: 3000,
// Função que trata cada requisição recebida
fetch(request) {
const url = new URL(request.url);
// Rota principal
if (url.pathname === "/") {
return new Response(
JSON.stringify({
mensagem: "Olá do servidor Bun!",
horario: new Date().toISOString()
}),
{
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8"
}
}
);
}
// Rota não encontrada
return new Response(
JSON.stringify({ erro: "Rota não encontrada" }),
{
status: 404,
headers: {
"Content-Type": "application/json; charset=utf-8"
}
}
);
}
});
// Exibe no console a porta usada pelo servidor
console.log(`Servidor Bun rodando em http://localhost:${servidor.port}`);
Em seguida, é possível adicionar scripts no arquivo package.json para facilitar a execução do servidor e dos testes. Esse arquivo também centraliza metadados do projeto e dependências. No exemplo abaixo, há um script dev para rodar o servidor e um script test para executar a suíte de testes integrada.
{
"name": "api-exemplo-bun",
"version": "1.0.0",
"scripts": {
"dev": "bun run server.ts",
"test": "bun test"
},
"dependencies": {}
}
Por fim, um arquivo de teste chamado server.test.ts pode verificar se o servidor responde corretamente na rota principal. O Bun permite usar a API fetch nativa para fazer chamadas HTTP ao próprio servidor, o que simplifica bastante a escrita de testes de integração básicos.
// arquivo: server.test.ts
import { test, expect } from "bun:test";
// URL base do servidor
const BASE_URL = "http://localhost:3000";
// Teste que verifica o conteúdo da rota principal
test("rota principal responde com mensagem de boas-vindas", async () => {
// Faz uma requisição HTTP para a raiz
const resposta = await fetch(BASE_URL);
// Verifica se o status é 200 (OK)
expect(resposta.status).toBe(200);
// Garante que o cabeçalho de conteúdo é JSON
expect(resposta.headers.get("Content-Type")).toContain("application/json");
// Lê e verifica o corpo da resposta
const corpo = await resposta.json();
expect(corpo.mensagem).toBe("Olá do servidor Bun!");
expect(typeof corpo.horario).toBe("string");
});
# Exemplo de sequência de execução desse mini projeto
# 1. Inicializa o projeto e cria package.json, se necessário
bun init
# 2. Executa o servidor em modo de desenvolvimento
bun run dev
# Saída típica:
# Servidor Bun rodando em http://localhost:3000
# 3. Em outro terminal, executa a suíte de testes
bun test
# Saída típica:
# PASS server.test.ts
# 1 test passed
Esse exemplo reúne as ideias centrais do Bun como runtime completo: execução de TypeScript sem configuração, criação de servidor HTTP com APIs nativas, uso de scripts no package.json, executor de testes integrado e suporte a fetch tanto no servidor quanto nos testes. Com esse conjunto de recursos, o Bun se posiciona como uma alternativa moderna que unifica etapas que, em outras pilhas, exigiriam múltiplas ferramentas separadas.