O criador do Bun, Jarred Sumner, concluiu o merge do Pull Request #45631 na branch principal do repositório oven-sh/bun (acompanhado do patch correspondente no motor JavaScriptCore em oven-sh/WebKit#757), introduzindo o compilador Bun AOT (bun build --compile --aot ou o atalho bun compile ./app.ts).

Ao contrário do modo tradicional de empacotamento (bun build --compile), que anexa o código-fonte ou bytecode ao runtime completo com compiladores Just-In-Time (JIT), a flag --aot compila TypeScript e JavaScript diretamente para código de máquina nativo em tempo de build. O executável gerado inicia imediatamente em código nativo mapeado em páginas somente-leitura (.text), sem carregar nenhum compilador JIT na memória e sem exigir qualquer mudança de sintaxe ou anotação extra no código.


Por que preservar os tipos do TypeScript até a geração de código?

Historicamente, sempre que uma ferramenta de infraestrutura escrita em JavaScript ou TypeScript atingia um teto de desempenho ou consumo de memória, a resposta da indústria era reescrevê-la do zero em Rust ou Go — caminho seguido por projetos como esbuild, SWC, Biome, oxc, Rolldown, Turbopack e pelo próprio TypeScript 7 (o porte oficial do tsc para Go).

O diagnóstico da equipe do Bun parte de uma assimetria fundamental: motores JavaScript foram concebidos para navegadores, onde o código chega como texto não confiável pela rede, precisa iniciar instantaneamente e exige um pipeline de interpretação, coleta de perfil (profiling), especulação de tipos (inline caches e hidden classes) e recompilação JIT — repetindo esse trabalho toda vez que o processo é iniciado.

Em servidores ou ferramentas de linha de comando (CLIs) escritas em TypeScript, todo o código-fonte e as declarações de tipos já estão disponíveis antes da distribuição. No fluxo convencional, o transpilador apaga os tipos e força o motor JS a redescobri-los em tempo de execução. O Bun AOT preserva essas informações estáticas e realiza o trabalho pesado uma única vez durante o build.


Como o compilador usa tipos em uma linguagem intencionalmente unsound

Como o sistema de tipos do TypeScript permite escapatórias deliberadas (any, as, !, JSON.parse e arquivos .d.ts imprecisos), o compilador não pode simplesmente assumir que toda anotação é inviolável sob pena de corromper memória. Para preservar 100% da semântica do JavaScript sem lançar erros artificiais de tipo em produção, o Bun AOT divide o tratamento em duas regras:

  1. Para valores criados dentro do próprio programa, o tipo é um fato: Uma instância como new Vec(1, 2) recebe um layout fixo em memória (um cabeçalho de 16 bytes seguido de dois double brutos de 64 bits, sem boxing nem tabela de propriedades);
  2. Para valores recebidos de fronteiras externas, o tipo é uma previsão: Argumentos vindos de código que o compilador não pôde provar estaticamente ou de JSON.parse passam por uma verificação única (guard) na entrada da função. Se o teste confirmar o tipo esperado, o código segue o caminho rápido (fast path) de uma única instrução de leitura (load); se falhar (por exemplo, a string "3" onde se esperava number), a execução desvia para uma versão lenta (slow path) que aplica as regras dinâmicas completas do JavaScript.
Análise de fluxo interprocedural combinada aos tipos do TypeScript: cada acesso a propriedade é classificado como livre (1 instrução load), testado (shape test + load) ou por nome (cache dinâmico).

Ao compilar com --explain, o Bun informa exatamente a proporção de acessos otimizados e aponta os pontos lentos do código:

Classificação do AcessoCondição provada no buildCusto em código de máquina
free (Livre)O compilador provou quais objetos chegam ao ponto de uso (pelos tipos, pela análise de fluxo global ou por um teste anterior na função)1 instrução load ou store (sem verificação de hidden class e sem inline cache)
tested (Testado)Um valor não verificado pode alcançar a expressão1 teste de formato (shape test) + load (com desvio para slow path se falhar)
by name (Por nome)Nenhuma garantia estática disponívelBusca em tempo de execução via cache

Além disso, o pacote introduz JSON.parse<T>(text) (de "bun") e validate<T>(value) (de "bun:bundle"), que validam payloads contra o tipo T já declarado e instanciam os objetos diretamente com o layout estático de T em memória, eliminando a necessidade de bibliotecas que geram código em tempo de execução com new Function.


Benchmarks reais: TypeScript 6 (--aot) vs. TypeScript 7 (Go) e Claude Code CLI

Os testes divulgados no PR #45631 (executados em Linux x64 sobre um AMD EPYC 9R14 de 16 núcleos dedicados) demonstram o impacto prático da compilação AOT em duas cargas reais de grande porte.

Comparativo oficial no repositório oven-sh/bun: TypeScript 6 compilado com --aot checando 370 mil linhas do repositório Effect-TS e redução drástica de memória e instruções no Claude Code CLI.

1. O compilador do TypeScript 6 checando o repositório Effect-TS (370.839 linhas)

Sem alterar uma única linha do código-fonte da tag v6.0.3 do Microsoft TypeScript, o binário gerado por bun build --compile --aot foi comparado à execução normal sobre o Bun 1.4.3 e ao novo TypeScript 7.0.2 reescrito em Go:

Ambiente / CompiladorTempo Total (Wall)Pico de Memória (RSS)Tempo de CPU
TypeScript 7 (Go — multi-thread)2,68 s1.256 MB10,8 s
TypeScript 6 (--aot no Bun)4,25 s963 MB5,3 s
TypeScript 7 (Go — --singleThreaded)5,08 s933 MB6,2 s
TypeScript 6 (JIT padrão no Bun 1.4.3)14,00 s2.581 MB29,2 s

Mesmo sendo um programa single-threaded, o TypeScript 6 compilado com --aot é 3,3 vezes mais rápido que o mesmo código rodando no JIT do Bun 1.4.3, consumindo 2,7 vezes menos memória RAM (963 MB contra 2,58 GB) e 5,5 vezes menos tempo total de CPU (5,3 s contra 29,2 s). Quando o compilador em Go é restrito a uma única thread (--singleThreaded), o TypeScript 6 original compilado pelo Bun AOT termina mais rápido (4,25 s vs 5,08 s) e gasta menos ciclos de processador.

2. Claude Code CLI em sessão automatizada

Como o CLI do Claude Code é distribuído oficialmente como um executável bun build --compile, a equipe compilou a mesma aplicação com a flag --aot e mediu uma sessão completa de inicialização e cinco turnos:

  • Tempo até o primeiro prompt: cai de 318 ms para 141 ms (mais de 2,2x mais rápido);
  • Pico de memória privada: cai de 185,4 MB para 44,7 MB (redução de 75,9%);
  • Memória privada no primeiro prompt: cai de 112,0 MB para 28,7 MB (um quarto do consumo original);
  • Instruções executadas pela CPU: caem de 5,53 bilhões para 1,11 bilhão (redução de quase 5x no trabalho do processador).

Pipeline de compilação em 6 estágios, Full Types e --trim

Por trás da flag --aot, o Bun não faz bundling tradicional concatenando arquivos: cada módulo ES ou CommonJS permanece isolado, e os próprios módulos internos node: e bun: do runtime são compilados a partir de seus fontes TypeScript junto com a aplicação do usuário.

Arquitetura em 6 estágios do Bun AOT: resolução de módulos, checagem estática (src/sema), abaixamento para IR em formato SSA, análise de fluxo global no JavaScriptCore com backend B3 e empacotamento com corte de recursos (--trim).

O pipeline completo percorre seis etapas principais:

  1. src/aot/load & src/aot/modules: Resolve a árvore de importações, executa macros em tempo de build e realiza constant folding de expressões estáticas como process.platform, eliminando ramos de código mortos para outros sistemas operacionais;
  2. src/sema & src/aot/lower: O verificador de tipos do bun check valida cada arquivo e converte a AST tipada para uma representação intermediária (IR) em formato SSA (Static Single Assignment) em paralelo em todas as threads;
  3. src/aot/link & src/aot/emit: Funde todos os módulos em um único programa de mundo fechado (closed-world) e serializa a IR segundo o esquema compartilhado aot/ir/AOTSchema.def;
  4. JavaScriptCore + Backend B3: Executa análise de fluxo sobre o programa inteiro, otimiza a representação intermediária e emite código de máquina nativo e seções de dados somente-leitura. No Linux 6.7+, o binário aciona prctl(PR_SET_MDWE) (Memory-Deny-Write-Execute) logo após mapear o código, bloqueando no próprio kernel qualquer criação futura de páginas executáveis em memória;
  5. Poda de binário com --trim: Como o executável AOT não precisa do gerenciador de pacotes, do test runner, do transpiler nem dos compiladores JIT do Bun, eles são omitidos por padrão. A flag adicional --trim remove também subsistemas nativos não utilizados pela aplicação (bun:sqlite, HTMLRewriter, WebAssembly, Bun.Image, dados Intl), reduzindo o tamanho final dos binários pela metade (de ~144 MB para a faixa de 69 MB a 82 MB);
  6. Condição "full-types" para pacotes npm (index.full.ts): Para resolver a perda de performance em bibliotecas de node_modules que publicam apenas .js sem tipos e .d.ts resumidos nas bordas, o PR introduz a convenção "full-types": "./index.full.ts" no package.json (gerada via bun compile --library), permitindo que bibliotecas distribuam o código-fonte completo com anotações internas intactas para o compilador AOT.

Limitações atuais e próximos passos

Jarred Sumner destacou que o recurso ainda é experimental e passará por uma bateria intensiva de fuzzing e correções antes de ser liberado em uma versão estável para o público:

  • Desempenho em código dinâmico sem tipos e new Function: Em servidores HTTP como Fastify, Koa e Elysia, o binário --aot entrega até 1,93x mais requisições por segundo no primeiro segundo de vida e corta a latência de inicialização em até 3,7x, mas perde de 7% a 22% em taxa de transferência sustentada após o aquecimento do JIT quando a aplicação depende de muitos pacotes npm sem tipos ou de validadores que geram funções em tempo de execução (new Function, que no binário AOT rodam apenas no interpretador do JSC);

  • Tempo de build: Como o compilador faz checagem estática global e otimiza todas as funções alcançáveis sem cache incremental entre compilações, aplicações grandes levam dezenas de segundos para compilar;

  • Plataformas testadas: O suporte em Linux x64 (glibc) já está totalmente validado na suíte principal (passando em mais de 31 mil testes do test262), enquanto Linux arm64, musl, macOS (arm64/x64) e Windows ainda contam com falhas pontuais sendo corrigidas na branch main.

  • Pull Request oficial e documentação técnica: github.com/oven-sh/bun/pull/45631