
Scriptc: compilador que transforma TypeScript em executáveis nativos
Scriptc: compilador TypeScript para nativo
O scriptc, projeto do vercel-labs, compila TypeScript comum em executáveis nativos pequenos e rápidos - sem Node, sem V8, sem engine JavaScript no binário. O código não precisa de anotações nem de dialeto especial: é o mesmo TypeScript que roda em Node, type-checked pelo compilador TypeScript real, e o comportamento é byte-for-byte idêntico ao Node.
Como funciona
O compilador decide, construto por construto, o que pode ser compilado estaticamente e informa o resultado com o comando scriptc coverage. Existem três tiers explícitos:
- Compilado estaticamente (padrão): código nativo, sem engine.
- Executado dinamicamente (
--dynamic): um engine JavaScript embutido (quickjs-ng, ~620KB) executa o que não pode ser estático, como dependências npm em JS. Valores que cruzam de volta para código estático são validados em runtime. - Rejeitado: tudo que não se encaixa falha com código de erro específico e sugestão de reescrita. Nada é silenciosamente mal compilado.
Superfície suportada
A cobertura estática inclui classes com herança e dispatch dinâmico, closures, genéricos (monomorfizados), discriminated unions, async/await com fibers, exceções com finally, destructuring, spread, iterators, template literals e regex. Da biblioteca padrão: strings UTF-16, arrays/Maps/Sets, JSON, Math, typed arrays, Buffer e hierarquias de Error. Da API do Node: fs, path, process, child_process, os, crypto, url, zlib, timers, net, http, https, tls, dgram, dns, fs.watch e readline. Também suporta fetch e o subset WHATWG web (streams, Headers, AbortSignal) sobre a mesma stack nativa, sem libcurl.
Dependências npm funcionam com --dynamic: resolvem com o algoritmo do Node, fazem typecheck contra os .d.ts e têm seu JS embutido no binário em build time. Binários nunca leem node_modules em runtime.
Correção e testes
Dois mecanismos rodam a cada mudança: testes diferenciais (800+ programas rodam em Node e como binário nativo, com stdout/stderr/exit codes idênticos byte-for-byte) e uma lane de memory-safety com AddressSanitizer e auditoria de reference counting. Divergências deliberadas do Node são documentadas e numeradas.
Performance (Apple M-series)
| Dimensão | scriptc | Contexto |
|---|---|---|
| Startup | ~2,4ms | Node: ~47ms; comparável a Zig, à frente de Go/Rust |
| Tamanho do binário | 170–200KB estático, ~3MB com --dynamic |
Go: ~2MB; Node SEA: 60–100MB |
| Memória (RSS) | 1–4MB típico | Node: 67–116MB |
Escape hatches
comptime(() => ...)executa TypeScript em build time e embute o resultado como literal.- FFI nativo (
--ffi) liga declarações TypeScript a chamadas diretas de C ABI. - Casts verificados:
JSON.parse(...) as Configinsere validação runtime que lança erro nomeando o caminho problemático.
Arquitetura
O pipeline é: TypeScript → tsc (parse + typecheck) → lowering → IR tipado → C → clang → executável nativo. O backend LLVM é o gerador padrão, com C como backend de referência legível. O runtime em C usa valores com refcount e cycle collector, fibers com stack e event loop baseado em kqueue.