- scriptc compila TypeScript comum em pequenos binários nativos que rodam sem Node, V8 ou engine JavaScript, mantendo a verificação de tipos do compilador real de TypeScript e a compatibilidade de comportamento com Node
- Determina se a compilação estática é possível com base na estrutura do código e, por padrão, gera código nativo; só ao escolher
--dynamicele executa o JavaScript de pacotes npm e código com tipoanyusando quickjs-ng - Suporta desde classes, genéricos,
async/await, exceções e expressões regulares até APIs de servidor do Node,fetche dependências npm; sintaxes não suportadas são rejeitadas com código de erro, frame de código e dicas de correção - Executa mais de 800 programas em Node e em binários nativos, comparando saída e código de término, e verifica erros de memória com AddressSanitizer e auditoria de contagem de referências
- Em medições na série Apple M, o tempo de inicialização é de cerca de 2,4 ms, os binários estáticos têm 170~200 KB, o RSS típico é de 1~4 MB e, com modo dinâmico e dependências embutidas, o binário fica em cerca de 3 MB
Modelo de compilação estática
- Usa TypeScript existente, sem dialeto separado nem anotações, e aplica o rigor de verificação do
tsconfig.jsone a biblioteca reales2025do TypeScript- Se o projeto tiver
@types/node, isso também entra na verificação de tipos - Código alcançável sem lowering recebe diagnósticos precisos e interrompe a compilação
- Se o projeto tiver
scriptc coveragemostra o número de instruções analisadas, a proporção compilada estaticamente, fatores de bloqueio e códigos de erro- No exemplo, 99% de 4.451 entre 4.481 instruções são compiladas estaticamente
- O modo de execução é explicitamente dividido em três etapas
- Compilação estática: modo padrão, que converte para código nativo sem engine JavaScript
- Execução dinâmica: ao especificar
--dynamic, inclui cerca de 620 KB de quickjs-ng para executar o JavaScript de pacotes npm e código com tipoany- Todos os valores que passam para o código estático são validados em runtime
- Se o tipo declarado e o valor forem diferentes, lança um
TypeErrorcapturável sem corromper a memória
- Rejeição: código que não pode ser tratado recebe código de erro, frame de código e, em geral, dicas de correção, sem ser compilado incorretamente em silêncio
TypeScript e biblioteca padrão suportados
- Os recursos da linguagem incluem classes com herança única e despacho dinâmico, closures, monomorfização de genéricos, unions discriminadas, destructuring, spread, template literals, getter/setter e iteradores
- O despacho dinâmico é desvirtualizado quando a segurança pode ser comprovada
- Unions discriminadas são tratadas por valores de tag usando o narrowing do TypeScript
async/awaité implementado com fibras stackful e agendamento ajustado ao JavaScript- Suporta exceções e
finally, além de parâmetros opcionais, padrão e rest
- Para expressões regulares, usa o mesmo interpretador de bytecode compatível com ECMAScript usado pelo QuickJS, vinculado apenas a binários que usam regex
- A biblioteca padrão inclui strings com semântica UTF-16, além de arrays, Map e Set com as mesmas regras de ordem e identidade do JavaScript
- A conversão de tipos de
JSONpassa por validação em runtime - Também fornece
Math, typed arrays,Buffere hierarquia deErrorcom suporte acatchtipado
- A conversão de tipos de
APIs de Node e da web
- As APIs de Node suportam
fs,path,process,child_process,os,crypto,url/URL,zlib, timers e tratadores de sinalfsoferece APIs síncronas e com Promisechild_processsuporta streams com pipe- O event loop não tem dependências externas
- A stack de servidor inclui
net,http,https,tls,dgram,dns,fs.watchereadline, sendo capaz de compilar servidores proxy reais- Para TLS, usa mbedTLS embutido
- Implementa parte das APIs web WHATWG, como
fetch, streams,HeaderseAbortSignal, sobre a mesma stack nativa de rede e TLS- Suporta redirecionamentos, gzip,
AbortSignal.timeoute causas de erro no estilo Node - Não usa libcurl nem dependências HTTP do sistema
- Suporta redirecionamentos, gzip,
Dependências npm e execução dinâmica
- Em
--dynamic, usa o método de resolução de módulos do Node e faz verificação de tipos com base no.d.tsfornecido pelo pacote - O JavaScript dos pacotes npm é incluído no binário durante o build, então não lê
node_modulesem tempo de execução scriptc coverage --dynamicmostra em qual área, estática ou dinâmica, cada instrução será executada, além dos bloqueios restantes- O engine JavaScript só é incluído quando o modo dinâmico é selecionado explicitamente, então o tamanho do binário não aumenta silenciosamente
Exatidão e segurança de memória
- O teste diferencial executa mais de 800 programas em Node e em binários nativos, comparando
stdout,stderre código de término byte a byte- A saída numérica segue a menor representação de ida e volta, e faz validação por fuzzing comparando 1 milhão de valores
doublecom Node - Para servidores, conecta drivers de cliente reais a ambas as implementações durante os testes
- A saída numérica segue a menor representação de ida e volta, e faz validação por fuzzing comparando 1 milhão de valores
- O conjunto completo de testes é executado novamente sob AddressSanitizer e auditoria de contagem de referências; se houver leak ou use-after-free, o build falha
- Existem algumas dezenas de comportamentos intencionalmente diferentes do Node, em geral ligados a detalhes internos de temporização e propriedades de objetos de erro
- Cada diferença é documentada e numerada; diferenças ocultas não são permitidas
Características de desempenho
- Na série Apple M, as medições usam as mesmas tarefas e a mesma saída byte a byte para Node, Go, Rust e Zig
- O tempo de inicialização é de cerca de 2,4 ms, menor que os cerca de 47 ms do Node, semelhante ao Zig e à frente de Go e Rust
- O tamanho dos binários estáticos é de 170~200 KB e, com
--dynamice dependências embutidas, fica em cerca de 3 MB- Como comparação, o binário em Go mostrado tem cerca de 2 MB, e o Node SEA fica em 60~100 MB
- O uso típico de memória é RSS de 1~4 MB, enquanto no Node fica em 67~116 MB
- O runtime compete com linguagens de sistema na maioria das tarefas, mantendo a semântica
f64ajustada ao JavaScript- Inferência de inteiros e análise de ownership estão no roadmap
Saídas de escape explícitas
comptime(() => ...)executa TypeScript em tempo de build numa VM isolada dentro do compilador e insere o resultado como literal no binário--fficonecta diretamente declarações TypeScript só com assinatura a chamadas de ABI C e faz o link de arquivos, objetos e bibliotecas de sistema declarados no manifesto- O limite é explícito e inclui informações de comprimento
- Mais detalhes podem ser vistos no guia de Native FFI
- Asserções de tipo verificadas como
JSON.parse(...) as Configinserem código de validação em runtime- Se a validação falhar, lança uma exceção com o caminho incorreto e os tipos esperado e real, como
expected number at $.port, got string
- Se a validação falhar, lança uma exceção com o caminho incorreto e os tipos esperado e real, como
Estrutura do compilador
- O fluxo de processamento é
TypeScript → parsing e verificação de tipos com tsc → lowering → IR tipada → C → clang → executável nativo packages/compilerinclui o frontend baseado na API do tsc, verificação e serialização de IR, além de backends LLVM e C- Apenas a IR é usada como interface entre frontend e backend
- LLVM é o gerador de código padrão, e programas fora da cobertura suportada usam uma rota alternativa transparente
- C é mantido como backend de referência permanente e, com
--backend c, gera uma saída legível com informações de linhas do código-fonte
packages/runtimeimplementa valores baseados em contagem de referências e coletor de ciclos, fibras stackful, event loop comkqueue, stack de servidor e saída numérica compatível com JavaScript- Usa linkagem por recurso para incluir no binário apenas o que é realmente usado
packages/clifornece os comandosscriptc build,scriptc runescriptc coverage
Instalação e desenvolvimento
- Instala-se com
npm install -g scriptce requer clang - A principal plataforma é macOS arm64, e os binários para Linux e Windows são cross-compiled
- Cada plataforma é validada por um caminho separado de testes diferenciais
- O desenvolvimento começa com
pnpm install && pnpm buildpnpm testexecuta o conjunto de testes diferenciais e snapshots de diagnósticoSCRIPTC_SAN=1 pnpm testexecuta os mesmos testes sob ASan e auditoria de contagem de referênciaspnpm scriptc build x.ts --emit-irpreserva o C gerado ex.ir.json
- Todos os recursos são adicionados junto com testes diferenciais, e só podem ser mesclados quando os testes normais e os de segurança de memória passam
1 comentários
Opiniões no Hacker News
A Vercel parece lançar um projeto chamativo mais ou menos uma vez por mês para manter sua credibilidade e presença. Não acho que uma empresa ou projeto sério vá usar o scriptc.
Respeito os contribuidores, mas o código tem um forte aspecto de ter sido gerado pelo Claude, e o fato de o Claude não aparecer como contribuidor torna tudo ainda mais suspeito.
Projeto / post relacionado no HN
O Porffor vem perseguindo o mesmo objetivo há algum tempo. O desenvolvedor, CanadaHonk, é extremamente talentoso, mas o projeto ainda passa em apenas cerca de 68% do Test262.
A menos que eu esteja entendendo mal o escopo do projeto, é bastante suspeita a forma como a Vercel avançou tão rápido.
É um projeto típico da Vercel. Foi publicado há 5 dias, é todo vibe coding, recebeu 1.500 estrelas sem motivo, não resolve o problema de ninguém e provavelmente deixará de ser mantido em alguns meses, no máximo.
Em vez de apenas criticar, testei em vários projetos locais, mas todos geraram centenas de erros na análise de alcance do código, tornando-o praticamente inutilizável.
Se você escrever tudo do zero sem bibliotecas externas, talvez consiga compilar para um binário, mas nesse caso não há motivo para não usar linguagens que já foram projetadas desde o início para compilação de verdade, como Rust, Go, Zig, D, C, V, Ada, C++, Nim, Swift, Kotlin Native ou Haskell.
A vantagem do TypeScript não é só a expressividade, mas também a compatibilidade com o enorme ecossistema npm. A maioria dos pacotes define apenas a interface por meio de declarações de tipo e distribui o código real em JavaScript, então, para usar pacotes, na prática é necessário um motor JavaScript.
Se você pretende começar do zero e não usar nenhum pacote npm, é melhor usar AssemblyScript. O Node recomenda explicitamente não distribuir pacotes em TypeScript, porque o TypeScript não mantém compatibilidade retroativa nem mesmo entre versões menores, e as configurações do compilador também não são portáveis entre pacotes.
Um projeto desses basta para continuar aparecendo na primeira página de serviços como o HN. É uma estratégia de crescimento: investir tokens para criar um projeto plausível que ninguém quer, publicar para ampliar o alcance e repetir.
Daqui a 12 meses, 90% dos projetos open source talvez sejam resultados de vibe coding que parecem interessantes, mas não têm usuários reais. Agora é fácil gerar até compiladores completos, mas o ponto central é manutenção de longo prazo e comunidade; um título chamativo por si só não retém usuários.
Se a Vercel estiver falando sério, deveria assumir custos e riscos reais adotando-o como seu runtime experimental.
É uma área de problema excelente. Apliquei ao Zod um trabalho parecido de criar um compilador que otimiza código de runtime com IA: zod-compiler.
Ele compila schemas Zod em tempo de build para cadeias simples de operações booleanas, tornando-os 2 a 74 vezes mais rápidos sem alteração de código, e o plugin substitui chamadas ao Zod pelo parsing compilado. A maior parte das otimizações foi escrita pelo Claude em mais de 100 iterações.
Assim como no scriptc, é possível comparar os resultados com o Zod real, então não é preciso julgar a correção subjetivamente. Isso pode ser aplicado a compiladores, ferramentas de serialização, formatadores, planejadores de consulta etc. que tenham uma implementação de referência e benchmarks.
Executei com Claude um benchmark de scriptc e Node. Mesmo no resultado mais favorável com arrays de bytes, depois de otimizações específicas, o scriptc ainda é cerca de 7,5 vezes mais lento que o Node 24.
Em compensação, o executável inicia 12 vezes mais rápido (1,5 ms contra 18,6 ms), usa 72 vezes menos memória (2,5 MiB contra 181 MiB) e vira um executável único de 370 KB sem dependências de runtime.
É bom reconhecer a necessidade de executáveis nativos pequenos e rápidos, mas, olhando o que Java passou por décadas, sou cético quanto à praticidade. O GCJ dos anos 1990 era tecnicamente razoável, mas não tinha suporte do ecossistema.
Depois, o GraalVM Native tratou o problema de forma mais abrangente, e as principais bibliotecas e frameworks passaram a trabalhar para garantir compatibilidade, mas até hoje é muito difícil executar nativamente de forma perfeita até mesmo aplicações existentes simples. Tentativas como o scriptc são bem-vindas, mas o caminho até a viabilização prática parece longo e acidentado.
Um dos motivos para a Excelsior ter desaparecido provavelmente é que GraalVM e OpenJ9 passaram a ser oferecidos gratuitamente. A PTC e a Aicas continuam indo bem graças a uma base de clientes embarcados e de tempo real que recebe pouca atenção.
Se o desenvolvimento continuar, há potencial para ser um grande avanço no nível do .NET AOT. Como foi publicado há poucos dias, por enquanto é algo para testar sem grandes expectativas, mas se não for abandonado e continuar evoluindo, pode ajudar bastante o ecossistema.
Código feito por IA também tem uma faixa ampla de qualidade, assim como código feito por humanos. Em software importante, ele deve ser gerado com os mesmos critérios de quando se escreve manualmente, e todo o código deve ser revisado; usado assim, é uma ótima abordagem. Projetos com pouca revisão provavelmente têm menor qualidade e menor senso de responsabilidade dos desenvolvedores, o que torna sua adoção mais difícil.
Discussões online tendem a ir para os extremos de “gerado totalmente por IA” e “nunca usar IA”, mas, na realidade, o razoável é um meio-termo que acelera o julgamento cuidadoso. Sem isso, fico relutante em usar o software por risco de baixa qualidade ou abandono.