Skip to content

Compile hono end-to-end via perry.compilePackages #421

Closed

Description

@proggeramlug

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/res prototypes at boot; Hono's Context is a plain object built per request. AOT-natural.
  • No req.app circularity — 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:

  1. hono source compilation — pure TS, no native bindings. Designed for portable runtimes, so most should compile out of the box. Standard compilePackages shake-out for any TS-subset features individual modules trip over (same shape as Compile ink (React-based TUI framework) end-to-end via perry.compilePackages #348).
  2. 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's app.fetch. Most natural: build on top of Perry's existing http / fastify plumbing in perry-stdlib to provide a @hono/perry-server shape.
  3. 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.
  4. 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 under node --experimental-strip-types byte-for-byte for response bodies/headers.
  • At least one official Hono middleware (hono/cors or hono/logger) verified end-to-end.
  • Whatever shake-out fixes are needed in perry-codegen / perry-stdlib to make the dependency tree compile cleanly, land them under this umbrella.
  • A @hono/perry-server-shaped adapter exists, either inside perry-stdlib or 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/status only, 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

  1. proggeramlug commented on May 2, 2026

    @proggeramlug
    ContributorAuthor

    Smoke compile result

    A minimal Hono smoke (10 lines: new Hono() with two routes, JSON response) was attempted in a fresh project with compilePackages: ["hono"] and hono@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):

    Undefined symbols for architecture arm64:
      "_perry_fn_node_modules_hono_dist_router_js__MESSAGE_MATCHER_IS_ALREADY_BUILT"
      "_perry_fn_node_modules_hono_dist_router_js__METHOD_NAME_ALL"
      "_perry_fn_node_modules_hono_dist_router_reg_exp_router_node_js__PATH_ERROR"
      "_perry_fn_node_modules_hono_dist_utils_constants_js__COMPOSED_HANDLER"
      "_perry_fn_node_modules_hono_dist_utils_url_js__decodeURIComponent_"
      "_perry_fn_node_modules_hono_dist_utils_url_js__getQueryParam"
    

    The exact root cause for npmVersion in #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 uses as renames in its barrel files too. The fix in #420 will probably resolve a chunk of these without further work.

    Other observations

    • All Hono modules parsed and lowered cleanly — no U006s, no decorator rejection (Hono has none), no syntax gaps.
    • The Context/HonoRequest/HonoResponse plain-object pattern is structurally Perry-friendly. No Reflect.metadata, no Proxy-based magic.
    • A @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

    • ✅ Hono is structurally Perry-compatible (no decorators, no metadata reflection, plain Web-standard request/response).
    • ❌ Cross-module symbol mismatches block the link. Likely overlap with Compile drizzle-orm end-to-end via perry.compilePackages #420's root cause.
    • 🟡 Adapter (@hono/perry-server) is a follow-up; not on the critical path for the compile shake-out.
  2. added 7 commits that reference this issue on May 5, 2026
  3. proggeramlug commented on May 6, 2026

    @proggeramlug
    ContributorAuthor

    Status snapshot as of v0.5.585 + breakdown of what's left.

    ✅ Done in this release cycle

    • v0.5.574 — primitive-literal exports (var X = "literal"; export { X } import path)
    • v0.5.574 — new Set(string) runtime (was SIGSEGV-ing; hono's var regExpMetaChars = new Set('...') hit it)
    • v0.5.575 — new Response(body, optsVar) extracts status/headers from runtime opts object
    • v0.5.576 — duplicate-symbol __perry_wrap_perry_unknown_func (multi-module link blocker)
    • v0.5.577 — closes perry-codegen: classes from compilePackages have no instance methods/fields #482: class Foo {}; export { Foo }; flips class.is_exported so importer's stub gets methods/fields
    • v0.5.585 — closes Conditional rest parameter type cause args to be undefined #484: rest-parameter bundling for class methods (...args on a class method now correctly produces an empty array on no-arg call instead of TypeError)

    new Hono() now constructs without crashing; routes register via app.get(...).

    ❌ Blockers / acceptance gaps

    Filed as separate sub-issues for tracking:

    This issue stays open as the umbrella; closes when #485 + #486 + #487 all close.

  4. added 7 commits that reference this issue on May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions