Skip to content

feat(egg-bundler): generate snapshot entry/prelude + egg-bin snapshot command#5998

Merged
killagu merged 1 commit into
nextfrom
feat/egg-bundler-snapshot-entry
Jun 26, 2026
Merged

feat(egg-bundler): generate snapshot entry/prelude + egg-bin snapshot command#5998
killagu merged 1 commit into
nextfrom
feat/egg-bundler-snapshot-entry

Conversation

@killagu

@killagu killagu commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Motivation

@eggjs/egg-bundler now defaults to single-file (snapshot-eligible) output (#5997). This PR adds the mechanism that turns that bundle into a V8 startup snapshot: a 3-mode generated entry, an auto-prepended runtime prelude, an egg-bin snapshot build command, and egg-scripts start --snapshot-blob to boot from the blob.

egg core already has the snapshot lifecycle (buildSnapshot/restoreSnapshot, snapshotWillSerialize/snapshotDidDeserialize, snapshot:true stopping at configWillLoad). This wires egg-bundler + egg-bin + egg-scripts to drive it.

Scope

  • 3-mode worker entry (EntryGenerator), selected at runtime by EGG_BUNDLE_SNAPSHOT:
    • normalstartEgg + listen (unchanged behavior).
    • snapshot-build (EGG_BUNDLE_SNAPSHOT=build) — startEgg({ snapshot: true }), run snapshotWillSerialize hooks, then v8.startupSnapshot.setDeserializeMainFunction(...).
    • restore main (inside the deserialize callback) — deferred via setImmediate (the Node ESM loader isn't ready when the callback runs), installs require-based __EGG_MODULE_IMPORTER__ / __RUNTIME_REQUIRE hooks (via process.getBuiltinModule('node:module'), with an eval-require fallback for Node < 22.3) so the egg loader avoids the missing dynamic import() callback, resumes snapshotDidDeserialize, listens, and emits an egg-ready IPC message for daemon readiness.
  • Snapshot prelude (prelude.ts) — Bundler prepends it before the bundle IIFE in snapshot mode so it runs before any module loads. Skeleton/placeholder; PR3 fills the lazy / native-binding-stub mechanism.
  • BundlerConfig.snapshot — forces single-file output (even if pack.singleFile: false) and triggers the prelude prepend.
  • egg-bin snapshot build — bundle in snapshot mode, then node --snapshot-blob X --build-snapshot worker.js (clean env so the ts-node loader isn't applied to the bundle; verifies the blob exists). Uses spawn (not fork) so no IPC handle breaks --build-snapshot.
  • egg-scripts start --snapshot-blob <blob> — boots the single self-contained snapshot process (no egg-cluster), reusing the existing daemon / stdout-stderr / signal lifecycle. egg-scripts stop recognizes snapshot processes by --snapshot-blob + --title.
  • __RUNTIME_REQUIRE declared in @eggjs/typings global; shared bundle-option helpers extracted to bundleOptions.ts (deduped from bundle.ts).

snapshot start lives in @eggjs/scripts (the production launcher), not egg-bin (dev/build tooling): a deployed app that ships a blob has egg-scripts available but usually not egg-bin.

Boundary

This PR is the entry/prelude skeleton + commands. The lazy/stub mechanism that converges non-serializable native bindings lands in PR3, so a live egg-scripts start --snapshot-blob may still hit native-binding errors after setDeserializeMainFunction until then. The egg-scripts daemon/stop path is unit-tested at the spawn-arg / process-match level; end-to-end daemon boot is verifiable once restore works (PR3).

Test evidence

  • tools/egg-bundler: prelude.test.ts; Bundler.test.ts snapshot-mode cases (forces single-file even with pack.singleFile:false, prepends prelude, fails fast on missing worker.js); EntryGenerator.test.ts 3-mode + egg-ready assertions + regenerated canonical snapshot.
  • tools/egg-bin: snapshot.test.ts (build bundles + spawns --build-snapshot with clean env; --skip-bundle; custom --blob; --dry-run; non-zero exit / missing-blob rejections); bundleOptions.test.ts; existing bundle.test.ts green after the helper extraction.
  • tools/scripts: snapshot-start.test.ts (boots via node --snapshot-blob, no cluster bin; --port → PORT env); snapshot-stop.test.ts (stop matches snapshot processes by title).
  • typecheck + oxlint clean on all three packages.

Pre-existing, unrelated to this PR: a few EntryGenerator/ManifestLoader tests fail locally on macOS due to /var/private/var realpath (identical on the base branch).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added V8 startup-snapshot workflow: snapshot build, and snapshot boot support for the start/stop flow.
    • Implemented snapshot prelude injection for single-file worker bundles and added a runtime require helper for snapshot restores.
    • Exposed additional command/option entry points and shared bundle options (mode/framework selection, --pack-alias).
  • Bug Fixes

    • Fixed port handling so PORT=0 is treated as an explicit value.
    • Improved validation and error reporting when expected snapshot artifacts are missing.
  • Tests

    • Added and expanded unit/integration tests for snapshot commands, bundler/prelude/entry generation, and bundle options.

Copilot AI review requested due to automatic review settings June 26, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds snapshot build, restore, and snapshot-boot process handling across egg-bin, egg-bundler, runtime typings, and script commands.

Changes

Snapshot build, restore, and process control

Layer / File(s) Summary
Shared bundle helpers and exports
tools/egg-bin/package.json, tools/egg-bin/src/bundleOptions.ts, tools/egg-bin/src/commands/bundle.ts, tools/egg-bin/test/bundleOptions.test.ts
Adds shared bundle-mode validation, alias parsing, framework resolution, and new package export subpaths.
Snapshot build command
tools/egg-bin/src/commands/snapshot.ts, tools/egg-bin/test/commands/snapshot.test.ts
Defines snapshot build, resolves output and blob paths, bundles in snapshot mode, spawns Node with snapshot flags, and verifies the blob is written.
Snapshot bundler prelude
tools/egg-bundler/src/index.ts, tools/egg-bundler/src/lib/Bundler.ts, tools/egg-bundler/src/lib/prelude.ts, tools/egg-bundler/test/Bundler.test.ts, tools/egg-bundler/test/prelude.test.ts
Exports snapshot prelude helpers, forces single-file output in snapshot mode, prepends the prelude to bundled worker output, and updates bundler tests for the new output shape.
Worker snapshot restore bootstrap
packages/typings/src/global.ts, tools/egg-bundler/src/lib/EntryGenerator.ts, tools/egg-bundler/test/EntryGenerator.test.ts
Updates the generated worker entry to branch on snapshot build mode, install restore hooks and runtime globals, resolve ports explicitly, and adds the matching global type plus tests.
Snapshot start and stop commands
tools/scripts/src/commands/start.ts, tools/scripts/src/commands/stop.ts, tools/scripts/test/snapshot-start.test.ts, tools/scripts/test/snapshot-stop.test.ts
Adds snapshot boot support to the start command, extends stop command matching for snapshot processes, and covers both flows with tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • eggjs/egg#5856: Adds the snapshot lifecycle hooks and boot integration used by the generated worker restore path.
  • eggjs/egg#5997: Changes the single-file bundling behavior that snapshot mode now relies on for worker output.
  • eggjs/egg#5888: Touches the same egg-bin bundle command area that now imports the shared bundle option helpers.

Suggested reviewers

  • jerryliang64
  • fengmk2

Poem

A rabbit packed a snapshot crate,
With ports and blobs and hops in state.
I tipped the prelude into place,
Then woke the worker face to face. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: snapshot entry/prelude generation in egg-bundler plus the new egg-bin snapshot command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/egg-bundler-snapshot-entry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7ec16b
Status: ✅  Deploy successful!
Preview URL: https://a8828be2.egg-cci.pages.dev
Branch Preview URL: https://feat-egg-bundler-snapshot-en.egg-cci.pages.dev

View logs

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.88%. Comparing base (003c456) to head (a7ec16b).
⚠️ Report is 2 commits behind head on next.

Files with missing lines Patch % Lines
tools/egg-bin/src/commands/snapshot.ts 92.30% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #5998      +/-   ##
==========================================
- Coverage   84.89%   84.88%   -0.01%     
==========================================
  Files         669      674       +5     
  Lines       19942    20261     +319     
  Branches     3964     4037      +73     
==========================================
+ Hits        16929    17199     +270     
- Misses       2588     2631      +43     
- Partials      425      431       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7ec16b
Status: ✅  Deploy successful!
Preview URL: https://05435721.egg-v3.pages.dev
Branch Preview URL: https://feat-egg-bundler-snapshot-en.egg-v3.pages.dev

View logs

@socket-security

socket-security Bot commented Jun 26, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for V8 startup snapshots in both @eggjs/egg-bundler and egg-bin. It adds a new snapshot command to egg-bin for building and starting snapshots, extracts shared bundle options, and updates the bundler to force single-file output and prepend a snapshot prelude. Additionally, the entry generator is updated to support snapshot build and restore modes. Feedback on these changes suggests improving termination signal forwarding in the spawned process to correctly report exit status, and adding a fallback for process.getBuiltinModule to maintain compatibility with Node.js versions prior to 22.3.0.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tools/egg-bin/src/commands/snapshot.ts Outdated
Comment thread tools/egg-bundler/src/lib/EntryGenerator.ts Outdated
@killagu

killagu commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

PR2 符合性评审 ✅ 符合,可合入

对照 PR2 要求(snapshot entry/prelude 生成机制 + egg-bin 命令,依赖 PR1 单文件输出)逐项核对:

PR2 要求 实现 状态
EntryGenerator 三模式(normal / snapshot-build / restore),运行时由 EGG_BUNDLE_SNAPSHOT EntryGenerator.tsEGG_BUNDLE_SNAPSHOT==='build' → snapshot-build,否则 normal startEgg+listen
snapshot-build:startEgg({snapshot:true})triggerSnapshotWillSerialize(app+agent) → setDeserializeMainFunction 完全一致
restore main:setImmediate 包裹 + 设 __RUNTIME_REQUIRE/__EGG_MODULE_IMPORTER__=createRequire(outputDir) → triggerSnapshotDidDeserialize → listen 一致,且用 process.getBuiltinModule('node:module') 取 createRequire(比裸 require/冻结 import 更稳,避开 bundler 追踪 + snapshot 冻结) ✅ 超出预期
prelude 自动生成 + prepend 到 worker.js IIFE 之前,本 PR 只建空壳 + 占位 prelude.ts:no-op IIFE + marker 幂等 + prepend(保留 shebang/use-strict),body 注明留给 PR3
Bundler 在 snapshot 模式 prepend prelude(并强制单文件) Bundler.tssnapshot 强制 singleFile=true + prependSnapshotPrelude
egg-bin snapshot build / snapshot start 命令 commands/snapshot.ts:build = bundle(snapshot 模式)+node --snapshot-blob X --build-snapshot worker.js;start = node --snapshot-blob X

边界遵守正确:lazy/native-binding stub 机制确实未泄漏到本 PR —— prelude body 为空占位,EntryGenerator 只装了 restore-importer(属 PR2 restore-main 范畴),delete web 全局 / __LAZY_EXT / __makeLazyExt 均未出现,正确留给 PR3。PR 描述也如实说明 snapshot start 在 PR3 前仍会撞 native-binding 错误。

额外硬化(均在 PR2 scope 内,加分项)

  • spawn(非 fork)跑 --build-snapshot:避免 IPC handle 这种不可序列化 libuv 资源破坏 snapshot build。
  • #spawnNodeprocess.env 干净环境(非 this.env):避免 BaseCommand 给 TS 应用注入的 ts-node NODE_OPTIONS loader 被带进 --build-snapshot
  • build 后校验 blob 实际写出(node --build-snapshot 可能 exit 0 却没产物);exit code===null(signal) 也判失败;转发 SIGINT/TERM/QUIT。
  • __resolvePort 把配置端口 0 当真实值。

测试prelude.test.ts + Bundler.test.ts(snapshot 强制单文件/prepend/否则跳过) + EntryGenerator.test.ts(三模式断言 + canonical snapshot) + egg-bin snapshot.test.ts(build 真 spawn --build-snapshot + clean env / start / --skip-bundle / --blob / --dry-run),覆盖到位。

无需改动。结论:符合 PR2,可合入;PR3(lazy-external + 默认网络栈列表 + delete-globals prelude body)可在此基础上接续。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tools/egg-bundler/src/lib/EntryGenerator.ts (1)

388-391: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Restore-time synchronous setup is outside the error boundary.

process.getBuiltinModule('node:module') and createRequire(...) run before the async IIFE that has the .catch(...). If either throws during deserialize, it surfaces as an unhandled exception with no diagnostic, unlike the deferred work below which logs and exits cleanly. Consider moving these two lines inside the try/catch-guarded async block (or wrapping them) so restore failures get the same '[egg-bundler] failed to restore snapshot:' treatment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bundler/src/lib/EntryGenerator.ts` around lines 388 - 391, The
restore-time setup in EntryGenerator is happening outside the async error
boundary, so failures from process.getBuiltinModule('node:module') or
createRequire(...) bypass the existing snapshot restore handling. Move the
require initialization into the same try/catch-guarded async flow that already
wraps the restore work, or explicitly wrap these statements so any
deserialize-time failure is reported through the existing '[egg-bundler] failed
to restore snapshot:' path. Keep the assignments to globalThis.__RUNTIME_REQUIRE
and globalThis.__EGG_MODULE_IMPORTER__ in that guarded section.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/egg-bin/src/commands/snapshot.ts`:
- Around line 141-148: The `SnapshotCommand` flow logs a successful blob write
even when `flags['dry-run']` skips spawning and the `fs.access` existence check,
so update the logic around `#spawnNode` and the final `this.log` call to only
report “snapshot blob written” when a blob was actually produced. In dry-run
mode, either suppress that success message or replace it with a dry-run-specific
message, while keeping the existing error path for missing blobs when not in
dry-run.

---

Nitpick comments:
In `@tools/egg-bundler/src/lib/EntryGenerator.ts`:
- Around line 388-391: The restore-time setup in EntryGenerator is happening
outside the async error boundary, so failures from
process.getBuiltinModule('node:module') or createRequire(...) bypass the
existing snapshot restore handling. Move the require initialization into the
same try/catch-guarded async flow that already wraps the restore work, or
explicitly wrap these statements so any deserialize-time failure is reported
through the existing '[egg-bundler] failed to restore snapshot:' path. Keep the
assignments to globalThis.__RUNTIME_REQUIRE and
globalThis.__EGG_MODULE_IMPORTER__ in that guarded section.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2138bb0-d971-426f-9fe0-e88f489b8117

📥 Commits

Reviewing files that changed from the base of the PR and between 003c456 and 357ea82.

⛔ Files ignored due to path filters (1)
  • tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • packages/typings/src/global.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/Bundler.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/test/prelude.test.ts

Comment thread tools/egg-bin/src/commands/snapshot.ts Outdated
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 357ea82 to 5c11204 Compare June 26, 2026 14:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tools/egg-bin/test/bundleOptions.test.ts (2)

39-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the default framework fallback.

getBundleFrameworkSpecifier() also has a fallback branch that returns 'egg' when no usable framework is found, but this suite only locks down the explicit and happy-path fixture cases. Covering that branch would make the new helper contract much harder to regress.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/test/bundleOptions.test.ts` around lines 39 - 47, Add a test
case in getBundleFrameworkSpecifier coverage for the fallback path where no
explicit framework and no usable egg.framework are found, and assert that it
resolves to 'egg'. Place it alongside the existing getBundleFrameworkSpecifier
tests in bundleOptions.test.ts, using a fixture or temporary baseDir that lacks
a valid framework value so the default branch is exercised.

1-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename this file to lowercase hyphen form.

bundleOptions.test.ts breaks the repo filename convention. A name like bundle-options.test.ts would match the rule and keep imports consistent. As per coding guidelines, "Keep file names lowercase with hyphens".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/test/bundleOptions.test.ts` around lines 1 - 49, Rename the
test file to follow the lowercase hyphenated filename convention by changing the
bundleOptions.test.ts test module to bundle-options.test.ts, and update any
imports or references that point to this test file so they continue to resolve
correctly; keep the existing test contents and symbols like getBundleMode,
parsePackAliases, and getBundleFrameworkSpecifier unchanged.

Source: Coding guidelines

tools/egg-bin/test/commands/snapshot.test.ts (1)

106-138: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cover the signal-exit branches too.

These tests validate the non-zero exit path, but tools/egg-bin/src/commands/snapshot.ts now also distinguishes between signal-killed children and user-initiated termination. A focused test for code === null would lock down the new failure/cleanup behavior that this PR added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/test/commands/snapshot.test.ts` around lines 106 - 138, Add a
focused snapshot test for the signal-exit path in Snapshot.run, alongside the
existing non-zero exit case in snapshot.test.ts. Mock the spawned child so the
exit handler receives code as null and a signal value, then assert the command
follows the new cleanup/failure behavior introduced in
tools/egg-bin/src/commands/snapshot.ts for signal-killed children rather than
the normal exit-code path. Use the existing Snapshot.run, spawnMock, and
spawnArgs helpers to keep the test aligned with the current build/start
coverage.
tools/egg-bin/src/bundleOptions.ts (1)

1-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename this new file to match the repo naming convention.

bundleOptions.ts introduces a second filename style into the CLI code. Renaming it to a lowercase hyphenated path now is much cheaper than carrying the exception forward through more imports.

As per coding guidelines, **/*: Keep file names lowercase with hyphens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/src/bundleOptions.ts` around lines 1 - 45, The new bundle
options module uses a camelCase filename that conflicts with the repository’s
lowercase-hyphen naming convention. Rename the file containing getBundleMode,
parsePackAliases, and getBundleFrameworkSpecifier to a lowercase hyphenated
name, then update every import/re-export that references bundleOptions so the
CLI code continues to resolve it correctly.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/egg-bundler/src/lib/Bundler.ts`:
- Around line 456-460: The snapshot entry lookup in Bundler should fail fast for
the required worker entry instead of skipping missing files. Update the
read/continue logic around the fs.readFile try/catch so that ENOENT is no longer
ignored in this path, and let the missing worker entry surface as an immediate
error from the snapshot build flow. Use the Bundler entry-loading code and the
worker entry handling in this loop to locate the change.

---

Nitpick comments:
In `@tools/egg-bin/src/bundleOptions.ts`:
- Around line 1-45: The new bundle options module uses a camelCase filename that
conflicts with the repository’s lowercase-hyphen naming convention. Rename the
file containing getBundleMode, parsePackAliases, and getBundleFrameworkSpecifier
to a lowercase hyphenated name, then update every import/re-export that
references bundleOptions so the CLI code continues to resolve it correctly.

In `@tools/egg-bin/test/bundleOptions.test.ts`:
- Around line 39-47: Add a test case in getBundleFrameworkSpecifier coverage for
the fallback path where no explicit framework and no usable egg.framework are
found, and assert that it resolves to 'egg'. Place it alongside the existing
getBundleFrameworkSpecifier tests in bundleOptions.test.ts, using a fixture or
temporary baseDir that lacks a valid framework value so the default branch is
exercised.
- Around line 1-49: Rename the test file to follow the lowercase hyphenated
filename convention by changing the bundleOptions.test.ts test module to
bundle-options.test.ts, and update any imports or references that point to this
test file so they continue to resolve correctly; keep the existing test contents
and symbols like getBundleMode, parsePackAliases, and
getBundleFrameworkSpecifier unchanged.

In `@tools/egg-bin/test/commands/snapshot.test.ts`:
- Around line 106-138: Add a focused snapshot test for the signal-exit path in
Snapshot.run, alongside the existing non-zero exit case in snapshot.test.ts.
Mock the spawned child so the exit handler receives code as null and a signal
value, then assert the command follows the new cleanup/failure behavior
introduced in tools/egg-bin/src/commands/snapshot.ts for signal-killed children
rather than the normal exit-code path. Use the existing Snapshot.run, spawnMock,
and spawnArgs helpers to keep the test aligned with the current build/start
coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc684d41-f36e-497d-aac7-6f514cc46e17

📥 Commits

Reviewing files that changed from the base of the PR and between 357ea82 and 5c11204.

⛔ Files ignored due to path filters (1)
  • tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • packages/typings/src/global.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bin/test/bundleOptions.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/Bundler.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/test/prelude.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bundler/test/prelude.test.ts
  • packages/typings/src/global.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bin/package.json
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/src/lib/prelude.ts

Comment thread tools/egg-bundler/src/lib/Bundler.ts Outdated
Copilot AI review requested due to automatic review settings June 26, 2026 14:44
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 5c11204 to 52b0ae5 Compare June 26, 2026 14:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/egg-bundler/src/lib/Bundler.ts`:
- Around line 459-462: The fs.readFile error handling in Bundler’s snapshot
prelude path is over-catching and rewrites every failure as a missing entry;
update the catch so only ENOENT is converted into the custom “was not found”
error, using the existing rel/filename context, and rethrow any other
NodeJS.ErrnoException unchanged so permission and I/O issues preserve their
original details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14f7d31f-5be0-41cf-bd3f-5cdaf97d854e

📥 Commits

Reviewing files that changed from the base of the PR and between 5c11204 and 52b0ae5.

⛔ Files ignored due to path filters (1)
  • tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • packages/typings/src/global.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bin/test/bundleOptions.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/Bundler.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/test/prelude.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/typings/src/global.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • tools/egg-bin/test/bundleOptions.test.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/test/prelude.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts

Comment thread tools/egg-bundler/src/lib/Bundler.ts Outdated
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 52b0ae5 to 60e2067 Compare June 26, 2026 14:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tools/egg-bin/src/bundleOptions.ts (1)

1-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename this helper file to match the repo filename convention.

This new module introduces a camelCase path (bundleOptions.ts), so the new snapshot command imports already depend on a non-conforming filename. Renaming it to something like bundle-options.ts now keeps the new surface aligned before it spreads further. As per coding guidelines, **/*: Keep file names lowercase with hyphens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/src/bundleOptions.ts` around lines 1 - 45, The new helper
module name does not follow the repo’s lowercase-with-hyphens convention, so
rename the bundle-options helper file to a hyphenated lowercase filename and
update any imports that reference it. Keep the exported helpers unchanged, but
ensure the new snapshot command and any other consumers import from the renamed
file so the shared bundle parsing symbols remain reachable.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tools/egg-bin/src/bundleOptions.ts`:
- Around line 1-45: The new helper module name does not follow the repo’s
lowercase-with-hyphens convention, so rename the bundle-options helper file to a
hyphenated lowercase filename and update any imports that reference it. Keep the
exported helpers unchanged, but ensure the new snapshot command and any other
consumers import from the renamed file so the shared bundle parsing symbols
remain reachable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3c474d3-d1f7-4c2d-8db1-ca77e91f0800

📥 Commits

Reviewing files that changed from the base of the PR and between 52b0ae5 and 60e2067.

⛔ Files ignored due to path filters (1)
  • tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • packages/typings/src/global.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bin/test/bundleOptions.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/Bundler.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/test/prelude.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • tools/egg-bundler/test/prelude.test.ts
  • tools/egg-bin/test/bundleOptions.test.ts
  • packages/typings/src/global.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bin/package.json
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts

Copilot AI review requested due to automatic review settings June 26, 2026 15:30
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 60e2067 to 2da1f5d Compare June 26, 2026 15:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tools/egg-bin/src/bundleOptions.ts (1)

1-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename this file to match the repo filename convention.

bundleOptions.ts breaks the lowercase-hyphen rule for new files. Renaming it to bundle-options.ts keeps the new shared helper aligned with the rest of the repo. As per coding guidelines, **/*: Keep file names lowercase with hyphens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/egg-bin/src/bundleOptions.ts` around lines 1 - 46, The shared helper
file name does not follow the repo’s lowercase-hyphen convention, so rename
bundleOptions.ts to bundle-options.ts and update any imports/usages that
reference the BundleMode helpers, parsePackAliases, or
getBundleFrameworkSpecifier so the new path is used consistently across egg-bin
bundle and snapshot build.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/scripts/src/commands/start.ts`:
- Around line 134-141: The snapshot early return in start() skips the shared
startup option and environment normalization, so move the common env/argv
preparation before the snapshot-blob branch and keep only the final command
selection different. Ensure startFromSnapshot() and the regular start-cluster
path both receive the same processed eggScriptsConfig values, including require,
node-options flags, sourcemap handling, PATH adjustments, and the
EGG_TS_ENABLE=false hardening.

In `@tools/scripts/src/commands/stop.ts`:
- Around line 49-58: The snapshot process matching in stop.ts is too broad and
can match unrelated snapshot builds or partial title substrings. Update the
matching logic in the stop command’s command-checking path so the snapshot
branch only targets snapshot-started servers by parsing argv boundaries instead
of using raw substring checks, and require a real --title= argument when
flags.title is absent. Keep the existing clusterMatched/snapshotMatched
structure, but make the snapshot criteria precise enough to avoid matching
egg-bin snapshot build or titles like egg-server-foo-bar when stopping
egg-server-foo.

---

Nitpick comments:
In `@tools/egg-bin/src/bundleOptions.ts`:
- Around line 1-46: The shared helper file name does not follow the repo’s
lowercase-hyphen convention, so rename bundleOptions.ts to bundle-options.ts and
update any imports/usages that reference the BundleMode helpers,
parsePackAliases, or getBundleFrameworkSpecifier so the new path is used
consistently across egg-bin bundle and snapshot build.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 968e581f-f0de-4dd5-8e56-0dd18b614793

📥 Commits

Reviewing files that changed from the base of the PR and between 60e2067 and 2da1f5d.

⛔ Files ignored due to path filters (1)
  • tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap is excluded by !**/*.snap
📒 Files selected for processing (18)
  • packages/typings/src/global.ts
  • tools/egg-bin/package.json
  • tools/egg-bin/src/bundleOptions.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bin/src/commands/snapshot.ts
  • tools/egg-bin/test/bundleOptions.test.ts
  • tools/egg-bin/test/commands/snapshot.test.ts
  • tools/egg-bundler/src/index.ts
  • tools/egg-bundler/src/lib/Bundler.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bundler/test/Bundler.test.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/test/prelude.test.ts
  • tools/scripts/src/commands/start.ts
  • tools/scripts/src/commands/stop.ts
  • tools/scripts/test/snapshot-start.test.ts
  • tools/scripts/test/snapshot-stop.test.ts
✅ Files skipped from review due to trivial changes (1)
  • tools/egg-bin/test/bundleOptions.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • tools/egg-bin/package.json
  • packages/typings/src/global.ts
  • tools/egg-bundler/test/prelude.test.ts
  • tools/egg-bundler/src/lib/prelude.ts
  • tools/egg-bin/src/commands/bundle.ts
  • tools/egg-bundler/test/EntryGenerator.test.ts
  • tools/egg-bundler/src/lib/EntryGenerator.ts
  • tools/egg-bundler/src/index.ts

Comment thread tools/scripts/src/commands/start.ts Outdated
Comment thread tools/scripts/src/commands/stop.ts
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 2da1f5d to 0488de0 Compare June 26, 2026 15:52
… command

Add the V8 startup snapshot entry-generation mechanism to @eggjs/egg-bundler
(building on the single-file output default from #5997) plus egg-bin commands.

- EntryGenerator emits a 3-mode worker entry driven by EGG_BUNDLE_SNAPSHOT:
  - normal: startEgg + listen (unchanged)
  - snapshot-build (EGG_BUNDLE_SNAPSHOT=build): startEgg({snapshot:true}),
    run snapshotWillSerialize hooks, register v8 setDeserializeMainFunction
  - restore main: setImmediate-deferred (ESM loader not ready at deserialize),
    installs require-based __EGG_MODULE_IMPORTER__/__RUNTIME_REQUIRE hooks so
    the egg loader avoids the missing dynamic import() callback, then resumes
    snapshotDidDeserialize and listens
- Add a snapshot prelude generator; Bundler prepends it before the bundle IIFE
  in snapshot mode (skeleton placeholder; PR3 fills the lazy/stub mechanism)
- BundlerConfig.snapshot forces single-file output and prelude prepend
- egg-bin: add `snapshot build` (bundle + node --build-snapshot) and
  `snapshot start` (node --snapshot-blob); spawn (not fork) to avoid an IPC
  handle that would break --build-snapshot
- Declare __RUNTIME_REQUIRE in @eggjs/typings global

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 26, 2026 15:57
@killagu killagu force-pushed the feat/egg-bundler-snapshot-entry branch from 0488de0 to a7ec16b Compare June 26, 2026 15:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@killagu killagu merged commit 0ac03ac into next Jun 26, 2026
24 of 25 checks passed
@killagu killagu deleted the feat/egg-bundler-snapshot-entry branch June 26, 2026 16:14
killagu added a commit to killagu/egg that referenced this pull request Jun 26, 2026
Fill the snapshot prelude skeleton from eggjs#5998 with the lazy-external /
native-binding stub mechanism, so a snapshot bundle stays serializable.

The Node network stack (http/https/http2/tls/dns) produces native bindings
(HTTPParser, nghttp2 settingsBuffer, tls SecureContext, dns ChannelWrap)
that V8 cannot serialize, and WebAssembly is disabled under
--build-snapshot. They must not load while the snapshot is built, then load
for real at restore.

prelude.ts (renderSnapshotPrelude now carries the lazy id set):
- deletes Node's lazy web globals with `delete` (redefining the accessor
  would trigger the undici load we avoid);
- installs `__LAZY_EXT` + `__makeLazyExt`. The factory returns a Proxy that
  is a stub at build time (hardcoded http METHODS/STATUS_CODES/maxHeaderSize
  so a library's top-level `[...http.METHODS]` does not force a load) and
  forwards to the real module via `globalThis.__RUNTIME_REQUIRE` once the
  generated restore entry installs it. Structural traps delegate to the
  target to keep Proxy invariants.
- new `injectExternalRequireLazyHook` and `resolveSnapshotLazyModules`.

Bundler (snapshot mode): resolves the lazy id set (network-stack default +
app `egg.snapshot.lazyModules` from package.json), keeps those ids external
so @utoo/pack emits externalRequire, injects the lazy dispatch into
externalRequire, then prepends the prelude carrying that id set.

Third-party business deps (leoric/@elastic/...) are out of scope — apps can
add them via egg.snapshot.lazyModules but stub-completeness is a separate RFC.

Tests: lazy-module resolution, filled prelude, externalRequire injection,
vm-based runtime proxy behavior (build stub vs restore), a Bundler wiring
test, and a real @utoo/pack build proving http is a stub at build and the
real module after __RUNTIME_REQUIRE. eggjs#5998's prelude/Bundler tests stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
killagu added a commit to killagu/egg that referenced this pull request Jun 26, 2026
Fill the snapshot prelude skeleton from eggjs#5998 with the lazy-external /
native-binding stub mechanism, so a snapshot bundle stays serializable.

The Node network stack (http/https/http2/tls/dns) produces native bindings
(HTTPParser, nghttp2 settingsBuffer, tls SecureContext, dns ChannelWrap)
that V8 cannot serialize, and WebAssembly is disabled under
--build-snapshot. They must not load while the snapshot is built, then load
for real at restore.

prelude.ts (renderSnapshotPrelude now carries the lazy id set):
- deletes Node's lazy web globals with `delete` (redefining the accessor
  would trigger the undici load we avoid);
- installs `__LAZY_EXT` + `__makeLazyExt`. The factory returns a Proxy that
  is a stub at build time (hardcoded http METHODS/STATUS_CODES/maxHeaderSize
  so a library's top-level `[...http.METHODS]` does not force a load) and
  forwards to the real module via `globalThis.__RUNTIME_REQUIRE` once the
  generated restore entry installs it. Structural traps delegate to the
  target to keep Proxy invariants.
- new `injectExternalRequireLazyHook` and `resolveSnapshotLazyModules`.

Bundler (snapshot mode): resolves the lazy id set (network-stack default +
app `egg.snapshot.lazyModules` from package.json), keeps those ids external
so @utoo/pack emits externalRequire, injects the lazy dispatch into
externalRequire, then prepends the prelude carrying that id set.

Third-party business deps (leoric/@elastic/...) are out of scope — apps can
add them via egg.snapshot.lazyModules but stub-completeness is a separate RFC.

Tests: lazy-module resolution, filled prelude, externalRequire injection,
vm-based runtime proxy behavior (build stub vs restore), a Bundler wiring
test, and a real @utoo/pack build proving http is a stub at build and the
real module after __RUNTIME_REQUIRE. eggjs#5998's prelude/Bundler tests stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
killagu added a commit to killagu/egg that referenced this pull request Jun 26, 2026
Fill the snapshot prelude skeleton from eggjs#5998 with the lazy-external /
native-binding stub mechanism, so a snapshot bundle stays serializable.

The Node network stack (http/https/http2/tls/dns) produces native bindings
(HTTPParser, nghttp2 settingsBuffer, tls SecureContext, dns ChannelWrap)
that V8 cannot serialize, and WebAssembly is disabled under
--build-snapshot. They must not load while the snapshot is built, then load
for real at restore.

prelude.ts (renderSnapshotPrelude now carries the lazy id set):
- deletes Node's lazy web globals with `delete` (redefining the accessor
  would trigger the undici load we avoid);
- installs `__LAZY_EXT` + `__makeLazyExt`. The factory returns a Proxy that
  is a stub at build time (hardcoded http METHODS/STATUS_CODES/maxHeaderSize
  so a library's top-level `[...http.METHODS]` does not force a load) and
  forwards to the real module via `globalThis.__RUNTIME_REQUIRE` once the
  generated restore entry installs it. Structural traps delegate to the
  target to keep Proxy invariants.
- new `injectExternalRequireLazyHook` and `resolveSnapshotLazyModules`.

Bundler (snapshot mode): resolves the lazy id set (network-stack default +
app `egg.snapshot.lazyModules` from package.json), keeps those ids external
so @utoo/pack emits externalRequire, injects the lazy dispatch into
externalRequire, then prepends the prelude carrying that id set.

Third-party business deps (leoric/@elastic/...) are out of scope — apps can
add them via egg.snapshot.lazyModules but stub-completeness is a separate RFC.

Tests: lazy-module resolution, filled prelude, externalRequire injection,
vm-based runtime proxy behavior (build stub vs restore), a Bundler wiring
test, and a real @utoo/pack build proving http is a stub at build and the
real module after __RUNTIME_REQUIRE. eggjs#5998's prelude/Bundler tests stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
killagu added a commit that referenced this pull request Jun 27, 2026
## Motivation

#5998 wired the V8 snapshot mechanism for `@eggjs/egg-bundler` (3-mode
entry, prelude prepend, `egg-bin snapshot` / `egg-scripts start
--snapshot-blob`) and left the snapshot prelude as an **intentional
no-op skeleton** with the note *"PR3 fills the lazy / native-binding
stub mechanism."* This PR fills it.

A V8 startup snapshot cannot serialize the native bindings the Node
network stack allocates — `node:http`/`node:https` `HTTPParser`
(llhttp), `node:http2` `nghttp2` settingsBuffer, `node:tls`
`SecureContext`, `node:dns` `ChannelWrap` — and `WebAssembly` is
disabled under `--build-snapshot`. Egg's loader phase touches that stack
(HttpClient, agents…), so it must **not** load while the snapshot is
built, then load for real at restore.

## What this does

**`prelude.ts` — `renderSnapshotPrelude(lazyModules)` now emits a real
body:**
- deletes Node's lazy web globals (`fetch`/`Headers`/`Request`/…) with
`delete` — redefining the lazy accessor would itself trigger the undici
load we avoid;
- installs `globalThis.__LAZY_EXT` (the lazy id set) and
`globalThis.__makeLazyExt`. The factory returns a `Proxy` that is a
**stub at build time** (hardcoded `http`
`METHODS`/`STATUS_CODES`/`maxHeaderSize` so a library's top-level
`[...http.METHODS]` doesn't force a load) and **forwards to the real
module via `globalThis.__RUNTIME_REQUIRE`** once #5998's restore entry
installs it — so `http.createServer` is the genuine builtin and the app
truly listens. Structural traps (`ownKeys`/`getOwnPropertyDescriptor`)
delegate to the target to keep `Proxy` invariants.
- adds `injectExternalRequireLazyHook` (idempotent) and
`resolveSnapshotLazyModules`.

**`Bundler.ts` (snapshot mode):** resolves the lazy id set
(network-stack default + the app's `egg.snapshot.lazyModules` from
`package.json`), keeps those ids **external** so `@utoo/pack` emits an
`externalRequire` call, injects the lazy dispatch into
`externalRequire`, and prepends the prelude carrying that id set — all
in a single pass over the worker output.

### Default lazy list
`http`, `https`, `http2` (+ `node:` variants), `tls`/`node:tls`,
`dns`/`node:dns`.

### Application extension point
```jsonc
// package.json
{ "egg": { "snapshot": { "lazyModules": ["leoric", "@elastic/elasticsearch"] } } }
```
appended (deduped) to the default list.

## Boundary

This PR covers the **Node built-in network stack** (fully functional at
runtime). Third-party business deps (leoric/@elastic/…) can be added via
`egg.snapshot.lazyModules`, but completing their stubs is a separate
RFC. Note the lazy mechanism relies on **CommonJS `require`** (how
urllib/undici/node internals load the stack); ESM `import *` can't defer
through a static namespace, which is an inherent ESM limitation, not
specific to this PR.

## Test evidence

- `snapshot-lazy-external.test.ts` — lazy-module resolution, filled
prelude content, `externalRequire` injection (incl. idempotency), and
**vm-evaluated runtime proxy behavior** (build-time stub vs restore-time
forward, Proxy invariants).
- `snapshot-lazy-bundler.test.ts` — Bundler wiring: hook injected, lazy
ids kept external, `egg.snapshot.lazyModules` merged, no-op when
snapshot off.
- `snapshot-lazy.realbuild.test.ts` — **real `@utoo/pack` build**
proving `node:http` is a stub at build (hardcoded METHODS,
`createServer()` no-ops) and the real module after `__RUNTIME_REQUIRE`
is installed.
- #5998's `prelude.test.ts` and `Bundler.test.ts` snapshot cases stay
green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Snapshot builds now support lazy handling for selected external
modules, including app-provided overrides from configuration.
* Snapshot output embeds runtime helpers and automatically patches
external module loading to route through the lazy dispatch path.
* Expanded public exports for snapshot-lazy utilities to support custom
integrations.

* **Bug Fixes**
* Improved reliability and idempotency of snapshot patching, with
stronger validation and clearer failure modes.
* Updated manifest externals behavior to preserve default network
externals while adding configured lazy modules.

* **Tests**
* Added unit and real-build regression coverage for lazy-external
wiring, fallback behavior, and error handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
killagu added a commit that referenced this pull request Jun 28, 2026
…nd cnpmcore e2e (#6003)

## Motivation

Restoring a V8 startup snapshot requires **Node.js >= 24**: Node.js 22
aborts during deserialization of a non-trivial Egg heap with the native
fatal `Check failed: current == end_slot_index` (a V8 bug). Building a
snapshot still works on Node.js >= 22. Today nothing enforces or
documents this, and there is no regression coverage. This PR adds a
runtime gate, documentation, and an e2e regression — without changing
the snapshot mechanism itself.

Builds on the snapshot work already on `next` (#5998 entry/prelude +
`egg-bin snapshot` command, #5999 lazy-external network stack, #6001
logger reopen, #6002 module-loader hooks).

## Scope

**Runtime gate (restore ≥ 24; build stays ≥ 22)**
- `@eggjs/scripts`: `egg-scripts start --snapshot-blob` refuses to
launch on Node.js < 24 with a clear error *before* spawning, checking
the major version of the resolved `--node` target binary (not just the
egg-scripts runtime). Also adds `allowNo: true` to the `sourcemap` flag
so `--no-sourcemap` is accepted.
- `@eggjs/egg-bundler`: a defense-in-depth guard in the generated
deserialize-main for direct `node --snapshot-blob` launches that manage
to deserialize on an unsupported runtime.
- `@eggjs/bin`: `snapshot build` prints a note that restoring needs
Node.js >= 24.

**Docs**
- Enrich `site/docs/advanced/snapshot.md` (EN + ZH): Node version
requirements, the CLI workflow (`egg-bin snapshot build` → `egg-scripts
start --snapshot-blob`), how it works (load module graph → run to
`configWillLoad` → freeze; restore = `didReady` + listen), performance
(~233ms vs ~942ms, ~4× on cnpmcore), and known limitations.
- Wire the page into the VitePress sidebar (EN + ZH) — it existed but
was unreachable.

**CI**
- Add a blocking `cnpmcore-snapshot` ecosystem-ci e2e (Node 24):
snapshot build → restore via `egg-scripts start --snapshot-blob` → `curl
/-/ping` == 200 → stop. Wires `repo.json`, `patch-project.ts`,
`.gitignore`.
- Extract the shared health-check poll into
`ecosystem-ci/wait-health.sh`, used by both the `cnpmcore` and
`cnpmcore-snapshot` jobs.

## Test evidence

- `pnpm --filter=@eggjs/scripts --filter=@eggjs/egg-bundler
--filter=@eggjs/bin run typecheck` — clean.
- `@eggjs/scripts` `snapshot-start.test.ts` (4 tests, incl. a Node<24
gate test and a `--no-sourcemap` parse regression test) +
`start-unit.test.ts` — pass.
- `@eggjs/bin` `snapshot.test.ts` — pass.
- `@eggjs/egg-bundler` `EntryGenerator` canonical snapshot regenerated
for the new guard; the rest of the suite matches the pre-change baseline
(a few pre-existing macOS/Node-22 path-resolution failures are
unrelated).
- A multi-agent diff review was run and all confirmed findings addressed
(most notably: `--no-sourcemap` is now parseable via `allowNo: true` —
without it the e2e job would have failed 100%).

## Notes

- The `cnpmcore-snapshot` job is correct-by-construction but **could not
be validated on the author's machine** (Node 22, no MySQL+cnpmcore
build); it relies on the supported path (lazy-external from #5999, no
manual stubs) and is validated by this PR's CI run.
- The job is intentionally a **separate matrix project** (not folded
into the existing cnpmcore job) for failure isolation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added “V8 Startup Snapshot” documentation navigation.
* Added support for snapshot project configuration (shared project-root
patching).

* **Bug Fixes**
* Enforced Node.js version gating for V8 snapshot restore (requires
Node.js ≥ 24) and improved snapshot restore safety.
* Improved snapshot lazy-external behavior, including correct external
named-export handling.
  * Updated the start command to allow `--no-sourcemap`.

* **Documentation**
* Expanded snapshot docs with requirements, workflow details,
performance, and limitations.

* **Tests**
* Updated snapshot start/version-gating and lazy-external readiness
assertions.

* **Chores**
* Improved E2E readiness checks with consistent polling, timeouts, and
error log output.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
killagu added a commit that referenced this pull request Jun 28, 2026
## Motivation

Building a V8 startup snapshot serializes the whole heap, so any
dependency that
opens a socket, starts a timer, or initializes a native binding at
module-evaluation time can make the blob fail to build — or build and
then crash
on restore. There was no guide for finding which module is responsible
or how to
fix it.

## What

New dedicated page `advanced/snapshot-troubleshooting.md` (EN + zh-CN):

- **The serializability rule** — what cannot survive the round-trip
(native
bindings / libuv handles / lazy web-global getters) and when a
dependency
  trips it.
- **Failure surfaces** — build-time vs restore-time, with the exact
error
  strings each emits (`killed by signal SIGSEGV`, `no blob was written`,
  `Check failed: current == end_slot_index`, `Aop Advice not found`,
  `Cannot find module`, …).
- **Find the offending module** — `NODE_DEBUG` namespaces, a clean
`NODE_OPTIONS`, `--dry-run`, `--skip-bundle` bisecting,
`--force-external`
  confirmation.
- **Fixes** — `--force-external`, `egg.snapshot.lazyModules`, the
snapshot
lifecycle hooks, deferring work out of module scope, avoiding the web
globals.
- **Failure modes in detail** (tegg `@Advice` filePath, the
lazy-external member
  proxy, runtime-asset `ENOENT`) and a configuration reference table.

Also documents the previously-undocumented `egg.snapshot.lazyModules`
config in
`advanced/snapshot.md`, cross-links the new page from it, and wires the
page into
the English and Chinese advanced sidebars.

Docs-only; no runtime code change. Builds on the snapshot feature
already on
`next` (#5998 / #5999 / #6001 / #6003).

## Test evidence

- `vitepress build site` passes clean — VitePress's dead-link/anchor
check is
green, so both new pages render and every cross-file and in-page anchor
resolves (the two detail headings use explicit ASCII `{#…}` ids because
the
  VitePress slugifier retains CJK + fullwidth `「」`).
- Every technical claim was adversarially verified against the
`egg-bundler` / `egg-bin` / `egg-scripts` source — exact error strings,
flag
  names, debug namespaces, and the default lazy-module set.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added a new Snapshot Troubleshooting guide in English and Chinese,
covering common build and restore failure symptoms, how to diagnose
them, and recommended fixes.
* Expanded the Snapshot guide with guidance on configuring additional
lazy-loaded modules, plus clearer troubleshooting links.
* Updated the sidebar navigation to include the new troubleshooting page
in both languages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants