Voltar ao Blog
compilerframeworksprogress

Compilando Hono, tRPC e Strapi para Binários Nativos

This dated article records the Perry version and measurements available when it was published. Use the current limitations, platform guide, and benchmark artifact for decisions today.

Perry agora compila tres grandes frameworks TypeScript — Hono, tRPC e Strapi — em executaveis nativos ARM64. Compilam em menos de um segundo, produzem binarios abaixo de 2 MB e rodam sem crashes.

Este post cobre o que funciona, o que ainda nao funciona e o que aprendemos ao testar o compilador contra codigo do mundo real.

Os Projetos

Escolhemos estes tres porque representam diferentes formas de TypeScript:

  • Hono — Um framework web leve (29 modulos). Uso intenso de genericos, heranca de classes, atribuicao dinamica de metodos e APIs Web Request/Response. Sua estrutura de exportacao usa re-exportacoes nomeadas atraves de barrel files.
  • tRPC — Um framework RPC type-safe (52 modulos). Cadeias profundas de re-exportacao em 4+ niveis, padrao builder com refinamento de tipos genericos, instanciacao de classes no escopo do modulo e streaming via Web Streams.
  • Strapi — Um CMS headless core (4 modulos compilados nativamente, o restante resolvido como externo). Monorepo com resolucao de pacotes workspace, re-exportacoes de namespace (export * as X), padrao de container de servico com Map e funcoes factory.

Resultados de Compilacao

Todos os tres compilam para binarios nativos com zero erros de compilacao:

ProjetoModulos CompiladosTamanho do BinarioTempo de Compilacao
Hono291.6 MB0.59s
tRPC521.8 MB0.97s
Strapi41.9 MB0.80s

Cada modulo fonte passa pelo pipeline completo: analise SWC, reducao HIR, geracao de codigo Cranelift, emissao de arquivo objeto e vinculacao nativa. Os tempos de compilacao incluem tudo — da analise ate o link final.

Para contexto, tsc --noEmit no tRPC sozinho leva varios segundos. Perry compila 52 modulos para um binario nativo vinculado em menos de um.

O Que Funciona em Tempo de Execucao

Instanciacao de Classes entre Modulos

Este foi o grande marco. A estrutura de exportacao do Hono parece assim:

hono export chain

// hono/src/hono.ts

export class Hono extends HonoBase { ... }

// hono/src/index.ts

import { Hono } from './hono'

export { Hono }

Esse export { Hono } e uma re-exportacao nomeada — nao export * from ou export { Hono } from './hono'. No HIR do Perry, isso se torna Export::Named, nao Export::ReExport ou Export::ExportAll. Anteriormente, a propagacao de classes do compilador so seguia cadeias ExportAll e ReExport, entao importar Hono de index.ts falhava silenciosamente — a busca de classe falhava e new Hono() retornava undefined.

Agora Perry rastrea Export::Named de volta atraves das importacoes do modulo para encontrar a definicao original da classe e propaga-la. O resultado:

$ ./perry compile test_hono.ts -o /tmp/test-hono && /tmp/test-hono

[1] Class instantiation through named re-export chain

PASS: new Hono() returned a real object

[2] Constructor-initialized fields

PASS: app.router initialized by constructor

PASS: app.router.name = SmartRouter

[5] Multiple instances

PASS: second instance created with router

[6] Constructor with options

PASS: new Hono({ strict: false }) accepted options

O construtor do Hono executa, inicializa um SmartRouter (que internamente cria tanto um RegExpRouter quanto um TrieRouter), e retorna um objeto real. Multiplas instancias independentes funcionam. Opcoes do construtor sao aceitas.

Resolucao de Re-exportacao Multi-nivel

O initTRPC do tRPC fica 4 niveis de profundidade:

initTRPC.ts (export const initTRPC = ...)

-> unstable-core-do-not-import.ts (export * from './initTRPC')

-> @trpc/server/index.ts (export { initTRPC } from '../../..')

-> index.ts (export * from './@trpc/server')

Isso e ExportAll NamedExportAll. Perry resolve a cadeia completa — initTRPC e acessivel no binario compilado. O mesmo para TRPCError, que segue o mesmo caminho.

Instanciacao de Classes entre Modulos com Argumentos

const err = new TRPCError({ code: 'NOT_FOUND', message: 'resource missing' })

// PASS: new TRPCError() returned object

// PASS: err.code = NOT_FOUND

TRPCError e definido em um modulo, re-exportado atraves de tres barrel files intermediarios, importado no teste e instanciado com um objeto de opcoes. O campo code da instancia e acessivel.

Resolucao de Pacotes em Monorepos

Strapi usa pacotes workspace — @strapi/core e um pacote irmao no monorepo, nao uma dependencia npm. Perry resolve o especificador bare atraves de campos package.json exports:

"exports": {

".": { "source": "./src/index.ts", "import": "./dist/index.mjs" }

}

A funcao createStrapi resolve corretamente como uma funcao chamavel atraves de export * from '@strapi/core'.

Filtragem de Exportacao Somente de Tipo

A sintaxe export type { Foo } do TypeScript nao tem significado em runtime — mas anteriormente Perry as transformava em entradas reais Export::ReExport que propagavam pelo linker e geravam simbolos stub. O index.ts do Hono sozinho tem quatro declaracoes export type cobrindo dezenas de tipos.

Perry agora verifica a flag type_only do SWC em declaracoes ExportNamed e is_type_only em especificadores individuais, pulando-os durante a reducao HIR. Isso eliminou a geracao de stubs mortos de re-exportacoes de tipo em todos os tres projetos.

Construtor RegExp

new RegExp(pattern, flags) agora compila para a funcao de runtime existente js_regexp_new do Perry. Isso foi direto — o runtime ja suportava RegExp — mas o handler de codegen Expr::New nao tinha um caso para ele, entao todo new RegExp(...) passava para um aviso "Unknown class". O RegExpRouter do Hono usa isso extensivamente.

O Que Ainda Nao Funciona

Estamos sendo especificos aqui porque as lacunas dizem tanto quanto as vitorias.

Atribuicao Dinamica de Propriedade em this

O construtor do Hono configura handlers de metodo HTTP dinamicamente:

const allMethods = ['get', 'post', 'put', 'delete', ...]

allMethods.forEach((method) => {

this[method] = (args1, ...args) => {

// register route

return this

}

})

Isso significa que app.get, app.post, etc. nao sao declarados estaticamente — sao atribuidos em runtime via nomes de propriedade computados. Perry ainda nao suporta this[variable] = value, entao esses metodos estao ausentes:

[4] Dynamic method assignment (this[method] = ...)

INFO: app.get not available

INFO: app.on not available

Esta e a maior lacuna para o Hono. A classe Hono existe, seu roteador e inicializado, mas voce nao pode registrar rotas.

Chamadas de Construtor no Nivel do Modulo

tRPC define seu ponto de entrada como:

export const initTRPC = new TRPCBuilder()

Em runtime, initTRPC aparece como typeof function em vez de typeof object — a expressao new TRPCBuilder() no nivel do modulo nao esta executando o construtor, entao o que voce recebe e uma referencia a classe em vez de uma instancia. Isso significa que initTRPC.create() e initTRPC.context() sao ambos undefined.

Propriedades Herdadas

TRPCError extends Error, e enquanto err.code (definido diretamente em TRPCError) funciona, err.message (herdado de Error) nao e acessivel. A cadeia de prototipos para busca de propriedades nao esta totalmente implementada.

Cadeias de Construtores Complexas

A funcao createStrapi() do Strapi internamente chama new Strapi(opts), que estende Container (apoiado por Map), chama loadConfiguration(), itera sobre provedores e registra servicos. Esta cadeia profunda de construtores produz um valor de retorno falsy — nao crasha, mas tambem nao produz uma instancia utilizavel.

Classes Built-in de Web API

Estes sao os avisos restantes de "Unknown class" nos tres projetos:

ClasseContagem
Response11
TransformStream7
ReadableStream5
Request4
Headers3
Proxy2
TextEncoderStream2
WritableStream1
DOMException1

Response, Requeste Headers sao os criticos para qualquer framework HTTP. Estes precisam de suporte de codegen built-in similar ao que ja temos para Map, Set, RegExp, Buffer, AbortController e outros.

O Que Isso Nos Diz

A boa noticia: o pipeline de compilacao do Perry lida com codigo real de frameworks. Projetos multi-arquivo com cadeias complexas de re-exportacao, assinaturas de tipo pesadas em genericos, hierarquias de classes e resolucao de pacotes monorepo passam todos para binarios vinculados.

As lacunas sao lacunas de runtime, nao lacunas de compilacao. O trabalho restante e:

  1. Atribuicao dinamica de propriedade — necessaria para frameworks que configuram metodos programaticamente
  2. Expressoes de inicializacao no nivel do moduloexport const x = new Foo() precisa realmente executar o construtor
  3. Cadeia de prototipos — propriedades e metodos herdados
  4. Built-ins de Web APIResponse, Request, Headers para frameworks HTTP

Estes sao problemas concretos e bem delimitados. Nenhum deles requer mudancas arquiteturais — sao extensoes de padroes que ja funcionam para casos mais simples.

Continuaremos trabalhando neles. O objetivo e new Hono().get('/', (c) => c.text('hello')) produzindo um servidor HTTP funcional em um binario nativo.

Gostou deste post? Receba o próximo.

Notas curtas sobre os releases do Perry e o que vem por aí.

Your email is sent to Resend for the Perry newsletter. You can request unsubscribe or deletion at any time. Privacy notice