Carregando publicação…
Carregando publicação…
O Pull Request #45631 adiciona bun build --compile --aot, usando os tipos do TypeScript em tempo de compilação para acelerar o tsc em 3,3x e cortar 76% da memória do Claude Code CLI.
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.
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 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:
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);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.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 Acesso | Condição provada no build | Custo 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ão | 1 teste de formato (shape test) + load (com desvio para slow path se falhar) |
by name (Por nome) | Nenhuma garantia estática disponível | Busca 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.
--aot) vs. TypeScript 7 (Go) e Claude Code CLIOs 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.
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 / Compilador | Tempo Total (Wall) | Pico de Memória (RSS) | Tempo de CPU |
|---|---|---|---|
| TypeScript 7 (Go — multi-thread) | 2,68 s | 1.256 MB | 10,8 s |
TypeScript 6 (--aot no Bun) | 4,25 s | 963 MB | 5,3 s |
TypeScript 7 (Go — --singleThreaded) | 5,08 s | 933 MB | 6,2 s |
| TypeScript 6 (JIT padrão no Bun 1.4.3) | 14,00 s | 2.581 MB | 29,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.
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:
318 ms para 141 ms (mais de 2,2x mais rápido);185,4 MB para 44,7 MB (redução de 75,9%);112,0 MB para 28,7 MB (um quarto do consumo original);5,53 bilhões para 1,11 bilhão (redução de quase 5x no trabalho do processador).Full Types e --trimPor 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.
O pipeline completo percorre seis etapas principais:
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;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;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;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;--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);"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.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