Repository navigation
Compile hono end-to-end via perry.compilePackages #421
Description
Tracking issue for landing hono (https://github.com/honojs/hono) as a perry.compilePackages target.
Surfaced from a Medusa-vs-Perry compatibility study. Takeaway: a faithful express shim is a tarpit — Express's value is the prototype-mutation contract that ~200 widely-used middlewares depend on, which is fundamentally AOT-hostile. Hono is the modern, actively-maintained, AOT-friendly Express-shaped framework — same design lineage as Drizzle, built for Workers/Bun/Deno and the same constraints Perry has.
Why Hono (vs. an Express shim)
- Express-shaped API —
app.get('/', c => c.json({...}))is instantly familiar to Express users. - No prototype mutation — Express extends
req/resprototypes at boot; Hono'sContextis a plain object built per request. AOT-natural. - No
req.appcircularity — clean dependency direction, no runtime metaprogramming required. - Trie-based router — fast, no runtime regex compilation.
- Middleware contract is a clean async function
(c, next) => {...}— composes statically, no side-effects-on-shared-prototypes. - Built for portable runtimes from day one — Workers, Bun, Deno, Node, edge. Same constraint set as Perry.
- Growing fast — 20K+ stars, increasingly the default for new TS backends.
Perry already ships fastify in perry-stdlib. Hono complements it: Fastify is the higher-throughput plugin-driven choice; Hono is the lighter Express-shaped choice with the strongest edge-runtime fit. The two together cover most of the modern TS backend space. A full Express shim is explicitly out of scope (see below).
Scope
End-to-end means: user does npm install hono, adds "compilePackages": ["hono"] to package.json, writes a standard Hono program with routing + a couple middleware + JSON request/response, runs perry compile app.ts -o app && ./app, and gets a single-file native binary serving HTTP.
Likely shake-out areas:
honosource compilation — pure TS, no native bindings. Designed for portable runtimes, so most should compile out of the box. StandardcompilePackagesshake-out for any TS-subset features individual modules trip over (same shape as Compileink(React-based TUI framework) end-to-end viaperry.compilePackages#348).- Adapter — Hono uses runtime adapters (
@hono/node-server, Bun adapter, Deno adapter, Workers adapter). Perry needs a server adapter that hands the listening socket / request stream to Hono'sapp.fetch. Most natural: build on top of Perry's existinghttp/fastifyplumbing inperry-stdlibto provide a@hono/perry-servershape. - Middleware ecosystem — Hono ships official middlewares (
hono/cors,hono/jwt,hono/logger, etc.) under its own namespace, all pure TS. These should fall out of (1) for free. - Web Standards APIs Hono leans on —
Request/Response/Headers/URL/URLSearchParams. Verify Perry's coverage matches what Hono uses; gaps land here as sub-issues.
Acceptance
- A 30-line Hono program (routing + JSON request/response + one middleware — e.g. a logger or a basic auth header check) compiles via
perry compile, produces a single-file native binary, serves real HTTP traffic, and matches the same program undernode --experimental-strip-typesbyte-for-byte for response bodies/headers. - At least one official Hono middleware (
hono/corsorhono/logger) verified end-to-end. - Whatever shake-out fixes are needed in
perry-codegen/perry-stdlibto make the dependency tree compile cleanly, land them under this umbrella. - A
@hono/perry-server-shaped adapter exists, either insideperry-stdlibor as a separate package, and is documented.
Out of scope (separate follow-ups)
- A full Express compatibility shim. Express's middleware ecosystem assumes prototype mutation; faithful support is ~20kloc of Rust to track upstream forever, and a partial shim is worse than no shim ("Express-but-not-really" is confusing). The express story becomes "use Hono or Fastify; here's a migration guide."
- A minimal Express subset module (
app.get/use/listen+req.params/query/body+res.json/send/statusonly, scoped to tutorial-grade apps) is possibly worth a separate small issue, only if real demand emerges. Default position: skip. - Elysia / itty-router / other small frameworks — separate tracking issues if/when demand materializes.
Strategic context
Companion to a Drizzle compilePackages tracking issue (filed alongside this one). Together they cover the modern AOT-friendly TS backend stack — the same selection Workers/Bun/Deno users have converged on for the same reasons Perry has. The thesis is: align with the modern-edge-TS lineage (Hono/Drizzle/Zod/Valibot) rather than chasing legacy-Node compat (Express/MikroORM/Awilix/NestJS).
Activity
proggeramlug commentedon May 2, 2026
Smoke compile result
A minimal Hono smoke (10 lines:
new Hono()with two routes, JSON response) was attempted in a fresh project withcompilePackages: ["hono"]andhono@4.6.x.HIR + per-module codegen succeed. Perry parses and lowers the entire Hono dep tree. The build fails at the link step with undefined cross-module symbols — the same shape as the Drizzle smoke (#420):
The exact root cause for
npmVersionin #420 (renamed exports —export { x as alias }emits the symbol under the internal binding name, not the alias) is the most likely cause for at least some of these. Hono usesasrenames in its barrel files too. The fix in #420 will probably resolve a chunk of these without further work.Other observations
Context/HonoRequest/HonoResponseplain-object pattern is structurally Perry-friendly. NoReflect.metadata, no Proxy-based magic.@hono/perry-server-shaped adapter is still needed (Hono ships runtime adapters per host runtime); that's a separate piece of work but doesn't block the compile/link surface.Status
drizzle-ormend-to-end viaperry.compilePackages#420's root cause.@hono/perry-server) is a follow-up; not on the critical path for the compile shake-out.fix(hir+runtime): refs #421 — primitive-literal exports + new Set(str…
fix(codegen): refs #421 — `new Response(body, optsVar)` extracts stat…
fix(hir): closes #482 — flip class.is_exported on `export { Class }` …
fix(perry-ext-net+ffi): refs #421/#420 — net regression: tokio featur…
fix(perry-stdlib+ext-net): refs #421/#420 — net pump registration + L…
fix(perry-ffi): refs #421/#420 — alloc_buffer through extern js_buffe…
fix(perry-stdlib+ext-net): refs #421/#420 — issue #91 HANDLE_METHOD_D…
proggeramlug commentedon May 6, 2026
Status snapshot as of v0.5.585 + breakdown of what's left.
✅ Done in this release cycle
var X = "literal"; export { X }import path)new Set(string)runtime (was SIGSEGV-ing; hono'svar regExpMetaChars = new Set('...')hit it)new Response(body, optsVar)extracts status/headers from runtime opts object__perry_wrap_perry_unknown_func(multi-module link blocker)class Foo {}; export { Foo };flipsclass.is_exportedso importer's stub gets methods/fields...argson a class method now correctly produces an empty array on no-arg call instead of TypeError)new Hono()now constructs without crashing; routes register viaapp.get(...).❌ Blockers / acceptance gaps
Filed as separate sub-issues for tracking:
Hono extends HonoBase; HonoBase declaresrequest,fetch,get,post, etc. as arrow class fields; the imported-class stub doesn't carry init expressions, so the Hono instance has onlyrouterand no way to handle requests. Until this fixes, no real HTTP traffic flows through hono.@hono/perry-serveradapter, 4th acceptance bullet (blocked on Class declarations in .js files inside compilePackages drop entirely from HIR #485).This issue stays open as the umbrella; closes when #485 + #486 + #487 all close.
fix(codegen+compile): closes #485 — cross-module subclass field initi…
fix(codegen+compile): closure-bound exported var calls fetch via gett…
fix(codegen+runtime): closes #514 — IndexGet on any-typed string retu…
fix(hir): refs #519/#421 — register Request/Response/Headers param ty…
fix(runtime+stdlib+codegen): refs #421 — Phase 1 of handle-NaN-boxing…
fix(codegen+runtime): refs #421 — `recv.method(...args)` on any-typed…
fix(runtime+codegen): refs #421 — int-tag string-method results + clo…
docs(stdlib): document hono compat in stdlib/http.md + porting.md