Três coisas que as pessoas chamam de “compilar TypeScript”
Quando desenvolvedores pesquisam como compilar TypeScript para um binário, geralmente encontram três técnicas bem diferentes que compartilham uma palavra:
- Transpilação.
tsc, SWC e esbuild transformam TypeScript em JavaScript. A saída ainda precisa de Node.js, Bun ou um navegador para rodar. Nenhum binário está envolvido. - Runtime embutido.
bun build --compile,deno compilee as Single Executable Applications (SEA) do Node.js concatenam seu JavaScript empacotado com uma cópia completa do runtime. Você obtém um único arquivo, mas o motor viaja junto dentro dele e seu código ainda é analisado e compilado via JIT toda vez que o processo inicia. - Compilação nativa ahead-of-time. É isso que o Perry faz. O TypeScript é analisado com SWC, os tipos são resolvidos, os generics são monomorfizados, e o LLVM emite código de máquina. O linker produz um executável normal — a mesma classe de artefato que um toolchain de Rust, Go ou C++ produz. Não há motor JavaScript algum no binário.
Como não há motor para inicializar nem nada para analisar na inicialização, um binário do Perry inicia em cerca de um milissegundo. O próprio pipeline é descrito com mais profundidade na página do compilador nativo de TypeScript e em detalhes internos do compilador.
Qual é o tamanho do binário?
O tamanho depende do que você importa, porque apenas o código que você realmente usa é compilado e vinculado:
- Um hello world tem cerca de 330 KB.
- Ferramentas de CLI típicas ficam em 2–5 MB.
- Aplicações completas que vinculam frameworks grandes (Fastify, mysql2 e afins) ficam em cerca de 48 MB.
Para efeito de comparação: um executável Node SEA é uma cópia do próprio binário node, então ele começa em cerca de 88–118 MB dependendo da plataforma antes mesmo de adicionar seu código, e um hello world compilado com o Bun mede cerca de 60 MB no macOS arm64 e cerca de 100 MB no Linux x64, porque todo o runtime do Bun é embutido.
Perry vs bun build --compile vs Node SEA
Os três te dão um único arquivo que você pode entregar a alguém. Fora isso, são ferramentas muito diferentes, e cada uma é a resposta certa para alguém:
| Perry | bun build --compile | Node SEA | |
|---|---|---|---|
| O que produz | Código de máquina compilado AOT (LLVM) | JS empacotado + runtime do Bun embutido | Cópia do binário node com seu script empacotado injetado |
| Modelo de execução | Código nativo, sem motor JS | JIT (JavaScriptCore) em tempo de execução | JIT (V8) em tempo de execução |
| Tamanho do hello world | ~330 KB | ~60 MB (macOS arm64) a mais de ~100 MB (Linux/Windows) | ~88–118 MB (tamanho do binário node) |
| Inicialização | ~1 ms | ~10 ms | ~30 ms |
| Compilação cruzada | 10 alvos, incluindo Windows/macOS/iOS a partir do Linux | Sim — Linux, Windows, macOS via --target | Não — em vez disso, copie um binário node específico da plataforma |
| Compatibilidade JS/npm | Crescente: axios, zod v4, express, fastify, hono compilam nativamente; runtime V8 opcional para o restante | Completa — é o próprio runtime do Bun | Semântica completa do Node; requer pré-empacotamento, apenas CommonJS no Node 24 LTS |
| Status | Pré-1.0 | Estável | Estabilidade “em desenvolvimento ativo” no Node 24 LTS |
Colocando as coisas com honestidade: se sua aplicação depende do ecossistema npm completo e você quer risco zero de compatibilidade, Bun e Node SEA rodam exatamente a semântica de motor contra a qual você já desenvolve — essa é a força deles, e o custo de tamanho pode não importar para o seu deployment. O Perry é uma troca diferente. Você ganha compilação ahead-of-time de verdade, binários pequenos e inicialização em milissegundos; em troca, você adota um compilador pré-1.0 cuja conformidade com JavaScript é medida e publicada (test262: String 79%, Array 72% na v0.5.1146) em vez de herdada do V8.
Comparações detalhadas: Perry vs Bun e Perry vs Deno. Para como os pacotes npm compilam, veja Pacotes npm reais e uma varredura de conformidade.