feat(jsruntime): V8 ModuleLoader reads from embedded module map (#818)#989
Merged
Conversation
v0.5.993 closed half of #818: the bundle now contains every transitive ESM sibling. The other half — `NodeModuleLoader::load` still reading sources via `std::fs::read_to_string` and ignoring the bundle — meant binaries still required `node_modules/` to live next to them at runtime. This commit makes V8-fallback binaries truly self-contained. Three pieces: 1. `perry-jsruntime/src/modules.rs` grows two `RwLock<HashMap>`s — `EMBEDDED_MODULES` (build-time canonical path -> source) and `EMBEDDED_ALIASES` (bare specifier -> path) — plus `#[no_mangle]` FFIs `js_register_embedded_module` / `js_register_embedded_alias` and Rust-facing wrappers. 2. `NodeModuleLoader::resolve_module_path` consults the alias map for bare specs and the path map (with extension/index fallback) when on-disk probes fail. `NodeModuleLoader::load` checks the map before `std::fs::read_to_string`. Keys are build-time canonical paths used as opaque identifiers — `canonicalize()` in `resolve()` already gracefully falls back when the path doesn't exist on the runtime filesystem, and the `file://` URL handed to V8 works either way. 3. New `targets::generate_embedded_js_object` writes a generated `.c` file with one `static const char[]` per JS module source + length pair (and matching pairs for every TS import edge whose `resolved_path` is in `ctx.js_modules`), wrapped in a `__attribute__((constructor(101)))` that calls the registration FFIs at process start. `cc -c` produces an `.o`; compile.rs appends it to `obj_paths` whenever `needs_js_runtime && !js_modules.is_empty()`. Octal escapes for every non-printable byte keep the generated C ASCII-clean regardless of the JS file's encoding; `?` is escaped to defang trigraph hazards. Validation: `import { Hono } from 'hono'` compiles, the resulting binary moved to an isolated directory with no `node_modules/` and no source still prints `object function`. The on-disk `__perry_js_bundle.js` is retained as a build-time debugging artifact but is no longer needed at runtime. New fixture: `test-files/test_v8_self_contained.ts`.
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
ModuleLoaderreads from an in-memory map, not from disk)..cwith onestatic const char[]per JS module source + length, plus matching pairs for every TS import edge whose resolved path lives inctx.js_modules. A__attribute__((constructor(101)))calls the registration FFIs beforemaininvokesjs_runtime_init().NodeModuleLoader::resolve_module_pathconsults a bare-specifier alias map and a path map (with extension/index fallback) when on-disk probes fail.NodeModuleLoader::loadchecks the source map beforestd::fs::read_to_string.node_modules/and no source files.Test plan
node_modules/, run successfully:node_modules/-co-located directory.node_modules/(e.g.node_modules/junk/).test-files/test_v8_self_contained.tsdocuments the expectation.Files touched
crates/perry-jsruntime/src/modules.rs—EMBEDDED_MODULES/EMBEDDED_ALIASESmaps + FFIs + load/resolve consults + extension-fallback helper.crates/perry/src/commands/compile/targets.rs—generate_embedded_js_object+ ASCII-cleanc_string_literalhelper (octal-escapes non-printable bytes; defangs trigraphs).crates/perry/src/commands/compile.rs— append generated.otoobj_pathswheneverneeds_js_runtime && !js_modules.is_empty().test-files/test_v8_self_contained.ts— fixture.CHANGELOG.md,CLAUDE.md,Cargo.toml,Cargo.lock— v0.5.994.