fix(compile): #818 — recursively bundle transitive ESM imports for V8 fallback#987
Merged
Merged
Conversation
… fallback
The V8-fallback bundler (`__perry_js_bundle.js`) only included the first
JS module it landed on — pure-ESM packages whose entry re-exports
siblings (hono's `dist/index.js` → `./hono.js` → `./hono-base.js` → …
twenty-some submodules) ended up with one entry, dropping the rest.
Compiled binaries still worked when their `node_modules/` tree happened
to sit next to them (V8 reads files off disk), but the realistic hono
call path (`app.fetch(req)` running cross-thread) cascaded into rc=139
segfaults when those resolved paths weren't co-located, and shipping the
binary on its own surfaced `Cannot resolve module` for every missing
sibling.
Root cause was the JS branch of `collect_modules` bailing after one
insert with the comment "V8 will handle that at runtime". Native
TypeScript imports already recursed via `cached_resolve_import`; JS did
not. Added a lightweight regex-based scanner
(`collect_js_module_imports`) that pulls out static `import` /
`export ... from` / string-literal `import("...")` specifiers, resolves
each relative / absolute path via the existing `resolve_with_extensions`
helper, and re-enters `collect_modules` for each — re-running the
JS/native classification so cross-package landings into a
`compilePackages`-overridden TS file are still handled correctly. Bare
specifiers (`react`, `@foo/bar`) stay the entry walker's job since the
top-level TS path already pulls package deps; the relative walk only
needs to cover the inside-a-package case, which is always relative-path.
Validation: hono@4.12.19 repro under `/tmp/perry-hono/` — pre-fix
bundle had 1 module entry, post-fix has 24 (every `dist/**/*.js`
reachable from `index.js` through the full re-export graph). New
fixture `test-files/test_hono_bundle.ts` documents the smoke shape;
the bundle-walks-recursively assertion lives in the PR description
because the parity suite doesn't install npm packages.
Next blocker (out of scope): V8's `ModuleLoader::load` in
`crates/perry-jsruntime/src/modules.rs` still reads from disk via
`std::fs::read_to_string` and never consults `__COMPILETS_MODULES`, so
the binary isn't yet self-contained even with the correct bundle.
36bb20f to
d0e210d
Compare
3 tasks
proggeramlug
added a commit
that referenced
this pull request
May 18, 2026
…object_to_v8 (#1015) PR #987 unblocked hono compilation by bundling transitive ESM imports, but the binary then SIGSEGV'd at `app.fetch(req)` because `native_object_to_v8` treated bridge "small handles" (low-value integers stored in JS_HANDLE_TAG) as raw pointers to ObjectHeader, dereferencing invalid memory. Added a small-handle guard in bridge.rs that returns the handle as a V8 number instead of attempting pointer indirection. Hono no longer crashes; next blocker is V8 fallback's missing `Response` global (separate concern).
This was referenced May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
__perry_js_bundle.jsonly contained the first JS module Perry landed on. Pure-ESM packages whose entry re-exports siblings (hono'sdist/index.js→./hono.js→./hono-base.js→ … ~20 submodules) shipped with one entry, dropping the rest. Compiled binaries still worked when theirnode_modules/tree happened to sit next to them (V8 reads files off disk), but the realistic hono call path (app.fetch(req)running cross-thread) cascaded into rc=139 segfaults when those paths weren't co-located.collect_moduleswas a leaf: it pushed one entry toctx.js_modulesand bailed with the comment "V8 will handle that at runtime". The native TypeScript branch already recursed throughcached_resolve_import; JS did not.collect_js_module_imports— a lightweight regex-based scanner that extracts staticimport/export ... from/ string-literalimport(...)specifiers, resolves each relative / absolute path via the existingresolve_with_extensions, and re-enterscollect_modulesfor every result. Bare specifiers (react,@foo/bar) stay the entry walker's job since the top-level TS path already pulls package deps in viacached_resolve_import.Validation
hono@4.12.19repro under/tmp/perry-hono/(import { Hono } from 'hono'; const app = new Hono(); …):__perry_js_bundle.jscontained 1globalThis.__COMPILETS_MODULES[...]entry (dist/index.js).dist/**/*.jsreachable fromindex.jsthrough the full re-export graph (compose.js,context.js,hono.js,hono-base.js,http-exception.js,request.js,request/constants.js,router.js, plus therouter/{reg-exp,smart,trie}-router/*.jsandutils/*.jssubtrees).object/function/function.console.log("hello")) still works.cargo build --release -p perry-runtime -p perry-stdlib -p perry-jsruntime -p perrygreen.Known next blocker (out of scope)
V8's
ModuleLoader::loadincrates/perry-jsruntime/src/modules.rs:464-538still reads sources viastd::fs::read_to_string(&path)and never consults the embedded__COMPILETS_MODULESmap. So the binary still requires thenode_modules/tree at runtime — removing it after compile producesCannot resolve module. Wiring the loader to prefer the embedded source (a small short-circuit at the top ofload()) is a separate fix toward the wider "single self-contained binary for V8-fallback packages" goal also captured under #818.Test plan
cd /tmp/perry-hono && perry test.ts -o out && ./outprintsobject/function/functiongrep -c '^globalThis.__COMPILETS_MODULES\[' __perry_js_bundle.js== 24)