Repository navigation
feat(crust): embed a V8 code cache in host Node.js binaries - #555
Conversation
[Medium impact] The PR appears safe to merge; no blocking defects were found. SummaryNode.js binary builds now embed a V8 code cache when the selected Node matches the official host binary and
|
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
📦 Package sizeBundle cost (per entrypoint, minified + gzip)
Consumer bundles (minified + gzip)Fixture apps from
Install size (
|
| Package | Tarball | Unpacked | Δ unpacked | Runtime footprint | Δ footprint |
|---|---|---|---|---|---|
@crustjs/core |
67.61 KB | 253.21 KB | ±0 | 294.62 KB | ±0 |
@crustjs/create |
7.20 KB | 22.11 KB | ±0 | 63.53 KB | ±0 |
@crustjs/effect |
4.99 KB | 13.92 KB | ±0 | 13.92 KB | ±0 |
@crustjs/env |
4.25 KB | 10.27 KB | ±0 | 10.27 KB | ±0 |
@crustjs/extensions |
25.16 KB | 86.33 KB | ±0 | 240.17 KB | ±0 |
@crustjs/man |
3.60 KB | 8.63 KB | ±0 | 8.63 KB | ±0 |
@crustjs/mcp |
8.57 KB | 25.10 KB | ±0 | 66.52 KB | ±0 |
@crustjs/progress |
5.60 KB | 14.76 KB | ±0 | 119.90 KB | ±0 |
@crustjs/prompts |
24.00 KB | 91.80 KB | ±0 | 196.94 KB | ±0 |
@crustjs/skills |
18.01 KB | 64.28 KB | ±0 | 275.99 KB | ±0 |
@crustjs/store |
13.18 KB | 48.71 KB | ±0 | 90.13 KB | ±0 |
@crustjs/style |
18.54 KB | 63.72 KB | ±0 | 63.72 KB | ±0 |
@crustjs/testing |
3.19 KB | 7.33 KB | ±0 | 7.33 KB | ±0 |
@crustjs/tui |
2.71 KB | 6.02 KB | ±0 | 6.02 KB | ±0 |
@crustjs/utils |
13.70 KB | 41.42 KB | ±0 | 41.42 KB | ±0 |
🦋 Changeset detectedLatest commit: d755ed3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the cache eligibility check, tsdown SEA integration, regression tests, documentation, and changeset.
- Cache eligibility: Enables V8 code caching only when the selected Node executable matches the embedded target byte-for-byte and
NODE_OPTIONSis absent or empty. - Runtime coverage: Adds dynamic built-in import coverage and verifies cache acceptance without rejection warnings.
- Documentation: Explains eligible builds, expected startup improvements, and warnings caused by runtime V8 flag changes.
- Validation: All 11 focused unit and integration tests passed on Linux x64 with Node 26.13.0, including the cache-enabled path. All four integration tests also passed with
NODE_OPTIONSset, exercising cache opt-out. Cross-platform execution was not tested.
gpt-6-astra | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the incremental changes in 182fc44 since the previous approval at 124c616.
- Moved cache eligibility checks: Compared the builder and embedded Node binaries once per target during provisioning instead of once per command.
- Reused target decisions: Returned the eligibility map to compiler selection and passed each target's explicit boolean into the generated tsdown build script, retaining the same selected Node and captured environment.
- Validated behavior: Passed all 11 focused unit and integration tests on Linux x64 with Node 26.13.0, including cache acceptance with a byte-identical builder. Passed all four integration tests again with
NODE_OPTIONSset, plus the package type-check. Cross-platform execution was not tested.
gpt-6-astra | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the test-only delta in 3ce3c31 since the previous approval at 182fc44.
- Corrected builder identification: Queried the selected Node's
process.execPathwith a bounded subprocess instead of comparing the version-manager shim itself with the embedded binary. - Validated executable resolution: Passed a focused probe using both direct Node execution and a forwarding shim. Full SEA integration tests were not rerun in this environment, which has Node 24 rather than the suite's required Node 26 and reported missing Bun during setup.
gpt-6-astra | 𝕏
96c5d32 to
62160c2
Compare
62160c2 to
b3f80cf
Compare
6f023d1 to
dc22548
Compare
dc22548 to
bb15fa7
Compare
c29e465 to
75a4e08
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the incremental cache-decision reporting changes since the previously reviewed version.
- Added cache diagnostics: Reported whether caching is enabled, grouped differing decisions by target, and explained disabled caching due to
NODE_OPTIONS, cross-building, or a different Node binary. - Short-circuited cross-target comparisons: Skipped binary comparison when the target platform or architecture differs from the builder, while retaining explicit
=== truecache enablement. - Updated coverage and documentation: Added formatter tests and a build-output integration assertion, and documented the new diagnostics in the guide and changeset.
- Validated the delta: Passed all eight focused unit tests and the package type-check after building workspace prerequisites. The four SEA integration tests skipped because this environment has Node 24 and the suite requires Node 26 or later; native SEA and cross-platform execution were not revalidated.
gpt-6-astra | 𝕏
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
b6ef05d to
a26c8ca
Compare
Node.js binaries compiled their whole bundle on every start. tsdown passes seaConfig through to `node --build-sea`, so the build now sets useCodeCache, which makes the SEA load the main module from a V8 code cache instead: a 30-command CLI runs ~15 M fewer instructions per start (~5.5%, 1 to 2 ms), for ~60 KB. The cache is written by the Node running --build-sea (the node on PATH) and V8 rejects it, printing "Code cache data rejected." on every start, unless the embedded binary is that exact build with the same V8 flags. Matching platform and version is not enough: a Nix build of Node 26.10 produced a cache the official 26.10 binary rejected, although both report the same v8.cachedDataVersionTag(). So the build enables it only when the node on PATH is byte-identical to the official binary tsdown embeds for the target (which implies the host target) and NODE_OPTIONS is unset (--jitless there also gets the cache rejected); every other build stays as before. import() of built-ins works with the cache for ESM main scripts, which is what Crust builds (Node 26 docs dropped the old import() caveat in nodejs/node#62678); the integration fixture now exercises it.
Comparing the builder's Node with the binary a target embeds reads both (~100 MB each), which added ~0.2 s to every command of a multi-command build. The provisioning script already runs once per build with the same Node and environment, so it now makes the decision per target and the build passes it to each command.
a26c8ca to
e417cb1
Compare
The build compares against process.execPath; resolving the node on PATH can land on a version-manager shim instead of the binary.
e417cb1 to
d755ed3
Compare

Summary
tsdown exe: { targets: [target], + seaConfig: { useCodeCache } } // true only when the node on PATH is the exact binary being embeddedA Node.js binary compiled its whole bundle on every start.
crust buildnow asksnode --build-seato embed a V8 code cache. V8 accepts the cache only in the exact binary that wrote it, with the same V8 flags. Crust therefore enables it only when thenoderunning the build is byte-identical to the official binary tsdown embeds for the target, andNODE_OPTIONSis unset. In practice that means the host target with a nodejs.org Node (mise, nvm, setup-node). Every other build is unchanged.crust buildprints the decision next toCompiler:, so a developer can see why a binary has no cache:Evidence
-N, 120 runs: 255.2 M vs 270.1 M instructions (−5.5%), 46.3 vs 48.1 ms.--help: −15.1 M, −0.8 ms. The binary is 61 KB larger.NODE_OPTIONSproduceuseCodeCache: false(verified withDEBUG=tsdown:exe), so no binary ever carries a cache V8 would reject.NODE_OPTIONS. Through the feat(crust): replace the npm launcher with the binary through execve #542 launcher the cache is still accepted;NODE_OPTIONS=--max-old-space-size=4096and--enable-source-mapsprint no warning at run time,--jitlessprints the rejection warning.tests/node-exe.integration.test.ts(4/4) checks that the binary printsSEA module code cache acceptedexactly when the builder is the embedded binary, neverrejected, and thatimport()of a built-in still works.Merge Danger
Door: two-way
Blast Radius: Node.js binaries built on the host target with an official Node
A user who runs the binary with V8 flags like
--jitlessor--expose-gcinNODE_OPTIONSseesWarning: Code cache data rejected.on every start (documented). Common flags such as--max-old-space-sizeare fine. Host builds read two ~150 MB files (the builder'snodeand the downloaded one) once per target per build, ~0.4 s, and only when their sizes match. Reverting restores the previous output exactly.Stack
Stack
tooling(10 PRs), merge in order. Each PR targets the branch of the one before it.