stamatios
← Voltar ao feed
Scriptc: compilador que transforma TypeScript em executáveis nativos
Open Source · Dev & Engenharia

Scriptc: compilador que transforma TypeScript em executáveis nativos

resumo de ~3 min

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 Config insere 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.