1 pontos por GN⁺ 2 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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 --dynamic ele executa o JavaScript de pacotes npm e código com tipo any usando quickjs-ng
  • Suporta desde classes, genéricos, async/await, exceções e expressões regulares até APIs de servidor do Node, fetch e 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.json e a biblioteca real es2025 do 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
  • scriptc coverage mostra 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
    1. Compilação estática: modo padrão, que converte para código nativo sem engine JavaScript
    2. 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 tipo any
      • 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 TypeError capturável sem corromper a memória
    3. 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 JSON passa por validação em runtime
    • Também fornece Math, typed arrays, Buffer e hierarquia de Error com suporte a catch tipado

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 sinal
    • fs oferece APIs síncronas e com Promise
    • child_process suporta streams com pipe
    • O event loop não tem dependências externas
  • A stack de servidor inclui net, http, https, tls, dgram, dns, fs.watch e readline, sendo capaz de compilar servidores proxy reais
    • Para TLS, usa mbedTLS embutido
  • Implementa parte das APIs web WHATWG, como fetch, streams, Headers e AbortSignal, sobre a mesma stack nativa de rede e TLS
    • Suporta redirecionamentos, gzip, AbortSignal.timeout e causas de erro no estilo Node
    • Não usa libcurl nem dependências HTTP do sistema

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.ts fornecido pelo pacote
  • O JavaScript dos pacotes npm é incluído no binário durante o build, então não lê node_modules em tempo de execução
  • scriptc coverage --dynamic mostra 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, stderr e 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 double com Node
    • Para servidores, conecta drivers de cliente reais a ambas as implementações durante os testes
  • 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 --dynamic e 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 f64 ajustada 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
  • --ffi conecta 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 Config inserem 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

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/compiler inclui 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/runtime implementa valores baseados em contagem de referências e coletor de ciclos, fibras stackful, event loop com kqueue, 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/cli fornece os comandos scriptc build, scriptc run e scriptc coverage

Instalação e desenvolvimento

  • Instala-se com npm install -g scriptc e 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 build
    • pnpm test executa o conjunto de testes diferenciais e snapshots de diagnóstico
    • SCRIPTC_SAN=1 pnpm test executa os mesmos testes sob ASan e auditoria de contagem de referências
    • pnpm scriptc build x.ts --emit-ir preserva o C gerado e x.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

 
GN⁺ 2 시간 전
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.

    • É uma contribuição realmente gigantesca ;-) commit
    • O mesmo líder de vibe coding também comandou o zerolang, a “linguagem de programação para agentes” que a Vercel anunciou em grande estilo em maio. Depois de 1.200 commits, o desenvolvimento parou por volta de meados de junho.
      Projeto / post relacionado no HN
    • Simon Willison parece ter alterado apenas o README, e não parece ter participado substancialmente do projeto.
    • Olhando o histórico de contribuições, parece que uma pessoa fez cerca de 99% por vibe coding, e ela também não parece ter experiência com compiladores.
    • Muitos produtos SaaS trabalham em parceria com a Vercel, e em ferramentas de desenvolvimento Next.js e React são tratados como SDKs de primeiro nível.
  • 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.

    • Foi graças a agentes de codificação. Em apenas uma semana, adicionaram 918 mil linhas.
  • É 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.

    • O scriptc parece resolver isso incluindo opcionalmente no bundle o motor quickjs-ng de 620 KB quando precisa executar essas dependências.
    • Eu gostaria de usá-lo não em código com muitas dependências, mas em ferramentas de linha de comando com finalidade bem definida que precisam compartilhar código com um projeto TypeScript maior.
    • Isso justifica distribuir bibliotecas sem tipos, mas é difícil entender a conclusão de que esse motivo supera o valor dos tipos.
  • 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.

    • Pelo que sei, a equipe do Graal também tentou uma abordagem de meta-interpretador parecida. O carregamento dinâmico de bytecode ou a reflexão que a execução nativa não conseguia lidar seriam interpretados pela implementação Java Espresso.
    • O GCJ sempre foi quase um protótipo. Um usuário sério provavelmente compraria um JDK comercial com ferramentas AOT, como Excelsior JET ou BEA JRockit.
      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.