Goal
Split #5863 (a 4812-line monolithic PR adding @eggjs/egg-bundler for turbopack-based application bundling) into 19 small, independently-reviewable stacked PRs.
#5863 stays open as the reference / smoke-test PR. The 19 small PRs land incrementally via a stacked-PR workflow.
Architecture (5 layers — see #5863 for detailed explanation)
- Manifest pre-computation —
ManifestLoader spawns generate-manifest.mjs (with tsx loader injected) to dump file discovery / resolveCache / tegg metadata; realpath → node_modules/<pkg> normalization.
- Externals resolution —
ExternalsResolver excludes native addons / ESM-only-without-require / peerDeps. Final commit removes @eggjs/* from always-external so the framework itself can be bundled.
- Entry generation —
EntryGenerator writes a synthetic worker.entry.ts with sorted static imports, inlined MANIFEST_DATA, dual-keyed BUNDLE_MAP, runtime baseDir via process.argv[1], and startEgg({mode:'single'}) + app.listen().
- Build orchestration —
Bundler wires the 4 layers; PackRunner wraps @utoo/pack/cjs/commands/build.js with pre-written tsconfig (decorators) + package.json {type:'commonjs'}.
- Runtime support APIs —
@eggjs/utils.setBundleModuleLoader, @eggjs/core.ManifestStore.fromBundle, ManifestStore.setBundleStore. State on globalThis to cross bundled/external module-instance boundaries.
Plus plugin compatibility patches (onerror/development/watcher) that inline templates and replace path-based references with class imports.
cnpmcore E2E result on #5863: externals 76 → 12, HTTP 200.
PR map (19 PRs, stacked)
Stack A — Runtime APIs (base: next)
| # |
PR |
Title |
Status |
| 01 |
#5867 |
feat(utils): add setBundleModuleLoader runtime hook |
Open |
| 02 |
#5876 |
feat(core): add ManifestStore.fromBundle for bundled artifacts |
Open (stacked on #5867) |
| 03 |
#5877 |
feat(core): add ManifestStore.setBundleStore hook |
Open (stacked on #5876) |
Stack B — Plugin patches (independent, base: next)
| # |
PR |
Title |
Status |
| 04 |
#5868 |
refactor(onerror): inline error page template as string constant |
Open |
| 05 |
#5869 |
refactor(development): inline loader trace template as string constant |
Open |
| 06 |
#5870 |
refactor(watcher): use direct class imports for event sources |
Open |
Stack C — Bundler package (stacked on Stack A)
| # |
PR |
Title |
Status |
| 07 |
#5878 |
feat(bundler): scaffold @eggjs/egg-bundler package |
Open (stacked on #5877) |
| 08 |
#5879 |
feat(bundler): add ExternalsResolver |
Open (stacked on #5878) |
| 09 |
#5880 |
feat(bundler): add ManifestLoader with realpath normalization |
Open (stacked on #5879) |
| 10 |
#5881 |
feat(bundler): add generate-manifest subprocess with tsx injection |
Open (stacked on #5880) |
| 11 |
#5882 |
feat(bundler): define BundlerConfig public API |
Open (stacked on #5881) |
| 12 |
#5883 |
feat(bundler): add EntryGenerator with sorted static imports |
Open (stacked on #5882) |
| 13 |
#5884 |
feat(bundler): add PackRunner wrapper over @utoo/pack |
Open (stacked on #5883) |
| 14 |
#5885 |
feat(bundler): add Bundler orchestrator |
Open (stacked on #5884) |
| 15 |
#5886 |
test(bundler): verify no-fs-scan contract and bundle determinism |
Open (stacked on #5885) |
| 16 |
#5887 |
test(bundler): add tegg-app fixture with HTTPController + service |
Open (stacked on #5886) |
Stack D+E — CLI + final integration (stacked on Stack C)
| # |
PR |
Title |
Status |
| 17 |
#5888 |
feat(egg-bin): add bundle subcommand |
Open (stacked on #5887) |
| 18 |
#5889 |
feat(bundler): remove @eggjs/* from auto-externals |
Open (stacked on #5888) |
| 19 |
#5890 |
feat(bundler): post-process turbopack output to fix import.meta.url |
Open (stacked on #5889) |
Merge order
Stack A (#5867 → #5876 → #5877) must merge first — other stacks depend on the runtime APIs.
Stack B (#5868, #5869, #5870) can merge in any order, at any time — fully independent.
Stack C (#5878 → ... → #5887) merges after Stack A. Each PR in sequence.
Stack D+E (#5888 → #5889 → #5890) merges after Stack C.
When merging a stacked PR: after the bottom PR lands into next, GitHub will auto-update the next PR's base to next. Review and merge in order.
Improvements over #5863
These split PRs fix issues present in #5863:
Handoff context for agents
Branch structure
All 19 split/* branches are pushed to origin (eggjs/egg). They form a linear stack:
origin/next
└─ split/01-utils-bundle-module-loader (A1)
└─ split/05-core-from-bundle (A2)
└─ split/06-core-set-bundle-store (A3)
└─ split/07-bundler-scaffold (C1)
└─ ... (linear chain) ...
└─ split/19-bundler-patch-import-meta (E2)
Stack B branches (split/02-*, split/03-*, split/04-*) are independently based on origin/next.
After a PR merges
When the bottom PR of a stack merges into next:
- GitHub auto-retargets the next PR to
next
- If there are merge conflicts (unlikely since each PR touches different files), rebase the branch onto
origin/next and force-push
- No action needed for higher PRs in the stack — they still point at their intermediate base branches
If next advances with unrelated commits
The split branches may need rebasing. Procedure:
git fetch origin next
- In the
split/c-orchestration branch: git rebase origin/next
- Recreate per-PR branches:
git branch -f split/XX-name <commit-oid>
- Force-push all branches:
git push origin --force split/01-* split/05-* ... split/19-*
Key files per PR
| PR |
Primary files |
| #5867 (A1) |
packages/utils/src/import.ts, packages/utils/test/bundle-import.test.ts |
| #5876 (A2) |
packages/core/src/loader/manifest.ts |
| #5877 (A3) |
packages/core/src/loader/manifest.ts |
| #5868 (B1) |
plugins/onerror/src/lib/onerror_page.ts, plugins/onerror/src/app.ts, plugins/onerror/src/config/config.default.ts |
| #5869 (B2) |
plugins/development/src/app/middleware/loader_trace_template.ts, plugins/development/src/app/middleware/egg_loader_trace.ts |
| #5870 (B3) |
plugins/watcher/src/config/config.default.ts |
| #5878 (C1) |
tools/egg-bundler/{package.json,tsconfig.json,tsdown.config.ts,vitest.config.ts}, pnpm-workspace.yaml, root tsconfig.json, root tsdown.config.ts |
| #5879 (C2) |
tools/egg-bundler/src/lib/ExternalsResolver.ts, tools/egg-bundler/test/ExternalsResolver.test.ts |
| #5880 (C3) |
tools/egg-bundler/src/lib/ManifestLoader.ts |
| #5881 (C4) |
tools/egg-bundler/src/scripts/generate-manifest.mjs, tools/egg-bundler/src/lib/ManifestLoader.ts |
| #5882 (C5) |
tools/egg-bundler/src/index.ts, tools/egg-bundler/test/index.test.ts |
| #5883 (C6) |
tools/egg-bundler/src/lib/EntryGenerator.ts, tools/egg-bundler/test/EntryGenerator.test.ts |
| #5884 (C7) |
tools/egg-bundler/src/lib/PackRunner.ts, tools/egg-bundler/test/PackRunner*.test.ts |
| #5885 (C8) |
tools/egg-bundler/src/lib/Bundler.ts, tools/egg-bundler/test/integration.test.ts, tools/egg-bundler/docs/output-structure.md |
| #5886 (C9) |
tools/egg-bundler/test/deterministic.test.ts, tools/egg-bundler/test/no-filesystem-scan.test.ts |
| #5887 (C10) |
tools/egg-bundler/test/fixtures/apps/tegg-app/** |
| #5888 (D1) |
tools/egg-bin/src/commands/bundle.ts, tools/egg-bin/src/index.ts, tools/egg-bin/package.json |
| #5889 (E1) |
tools/egg-bundler/src/lib/ExternalsResolver.ts, tools/egg-bundler/test/ExternalsResolver.test.ts |
| #5890 (E2) |
tools/egg-bundler/src/lib/Bundler.ts |
Test commands
# Stack A
pnpm --filter=@eggjs/utils test # A1: 60 pass + 13 skip
pnpm --filter=@eggjs/core test # A2, A3: 420 pass + 25 skip
# Stack B
pnpm --filter=@eggjs/onerror test # B1: 36/36
pnpm --filter=@eggjs/development test # B2: 12 pass + 5 skip
pnpm --filter=@eggjs/watcher test # B3: 4/4 + 4 skip
# Stack C, D, E
pnpm --filter=@eggjs/egg-bundler test # 60 pass + 1 skip
pnpm --filter=@eggjs/bin typecheck # D1: typecheck clean
🤖 Generated with Claude Code
Agent operational notes
Local master branch for rebasing
All 19 per-PR branches are derived from a single linear branch split/c-orchestration (current tip: 9911d5363). When rebasing is needed:
# 1. Rebase the master branch
cd /path/to/egg
git checkout split/c-orchestration
git fetch origin next
git rebase origin/next
# 2. Recreate per-PR branches from new OIDs
git log --oneline split/c-orchestration -16 # read new OIDs
git branch -f split/01-utils-bundle-module-loader <A1-oid>
git branch -f split/05-core-from-bundle <A2-oid>
# ... etc for all 16 stack branches
# 3. Cherry-pick B branches independently (not in c-orchestration stack)
# B1, B2, B3 touch different files; cherry-pick onto origin/next
# 4. Force-push BOTH remotes
git push origin --force split/01-* split/02-* split/03-* split/04-* split/05-* split/06-* split/07-* split/08-* split/09-* split/10-* split/11-* split/12-* split/13-* split/14-* split/15-* split/16-* split/17-* split/18-* split/19-*
git push fork --force split/01-* split/02-* split/03-* split/04-* split/05-* split/06-* split/07-* split/08-* split/09-* split/10-* split/11-* split/12-* split/13-* split/14-* split/15-* split/16-* split/17-* split/18-* split/19-*
Branches exist on both remotes
All split/* branches are pushed to:
- origin (eggjs/egg) — needed as stacked PR base branches
- fork (killagu/egg) — the original source
Both must be force-pushed when rebasing.
pnpm install constraint
ut install (the utoo skill) does NOT support pnpm workspace catalog: specifiers. For dependency install, use pnpm install directly:
pnpm install --no-frozen-lockfile # catalog deps require this
For running tests, use pnpm --filter=<pkg> run test or npx vitest run --project=<pkg> directly.
lint-staged quirk with fixture-only commits
The pre-commit hook runs oxlint via lint-staged. Commits touching only test/fixtures/** files will be rejected with "No files found to lint" because oxlint ignores fixture dirs. Workaround: include at least one non-fixture source change in the commit (e.g., a structural assertion test in integration.test.ts).
Agent worktrees (may have been pruned)
Previous session created worktrees under .claude/worktrees/agent-*. Check git worktree list — they may still exist. The key one is agent-aa51f131 which had split/c-orchestration checked out.
Goal
Split #5863 (a 4812-line monolithic PR adding
@eggjs/egg-bundlerfor turbopack-based application bundling) into 19 small, independently-reviewable stacked PRs.#5863 stays open as the reference / smoke-test PR. The 19 small PRs land incrementally via a stacked-PR workflow.
Architecture (5 layers — see #5863 for detailed explanation)
ManifestLoaderspawnsgenerate-manifest.mjs(with tsx loader injected) to dump file discovery / resolveCache / tegg metadata; realpath →node_modules/<pkg>normalization.ExternalsResolverexcludes native addons / ESM-only-without-require / peerDeps. Final commit removes@eggjs/*from always-external so the framework itself can be bundled.EntryGeneratorwrites a syntheticworker.entry.tswith sorted static imports, inlinedMANIFEST_DATA, dual-keyedBUNDLE_MAP, runtime baseDir viaprocess.argv[1], andstartEgg({mode:'single'})+app.listen().Bundlerwires the 4 layers;PackRunnerwraps@utoo/pack/cjs/commands/build.jswith pre-written tsconfig (decorators) +package.json {type:'commonjs'}.@eggjs/utils.setBundleModuleLoader,@eggjs/core.ManifestStore.fromBundle,ManifestStore.setBundleStore. State onglobalThisto cross bundled/external module-instance boundaries.Plus plugin compatibility patches (onerror/development/watcher) that inline templates and replace path-based references with class imports.
cnpmcore E2E result on #5863: externals 76 → 12, HTTP 200.
PR map (19 PRs, stacked)
Stack A — Runtime APIs (base:
next)feat(utils): add setBundleModuleLoader runtime hookfeat(core): add ManifestStore.fromBundle for bundled artifactsfeat(core): add ManifestStore.setBundleStore hookStack B — Plugin patches (independent, base:
next)refactor(onerror): inline error page template as string constantrefactor(development): inline loader trace template as string constantrefactor(watcher): use direct class imports for event sourcesStack C — Bundler package (stacked on Stack A)
feat(bundler): scaffold @eggjs/egg-bundler packagefeat(bundler): add ExternalsResolverfeat(bundler): add ManifestLoader with realpath normalizationfeat(bundler): add generate-manifest subprocess with tsx injectionfeat(bundler): define BundlerConfig public APIfeat(bundler): add EntryGenerator with sorted static importsfeat(bundler): add PackRunner wrapper over @utoo/packfeat(bundler): add Bundler orchestratortest(bundler): verify no-fs-scan contract and bundle determinismtest(bundler): add tegg-app fixture with HTTPController + serviceStack D+E — CLI + final integration (stacked on Stack C)
feat(egg-bin): add bundle subcommandfeat(bundler): remove @eggjs/* from auto-externalsfeat(bundler): post-process turbopack output to fix import.meta.urlMerge order
Stack A (#5867 → #5876 → #5877) must merge first — other stacks depend on the runtime APIs.
Stack B (#5868, #5869, #5870) can merge in any order, at any time — fully independent.
Stack C (#5878 → ... → #5887) merges after Stack A. Each PR in sequence.
Stack D+E (#5888 → #5889 → #5890) merges after Stack C.
When merging a stacked PR: after the bottom PR lands into
next, GitHub will auto-update the next PR's base tonext. Review and merge in order.Improvements over #5863
These split PRs fix issues present in #5863:
#isEsmOnlyand#hasRequireConditionmethods that causetsgo --noEmitfailure..egg/atfs.cptime, eliminating a ~60% race condition.Handoff context for agents
Branch structure
All 19
split/*branches are pushed toorigin(eggjs/egg). They form a linear stack:Stack B branches (
split/02-*,split/03-*,split/04-*) are independently based onorigin/next.After a PR merges
When the bottom PR of a stack merges into
next:nextorigin/nextand force-pushIf
nextadvances with unrelated commitsThe split branches may need rebasing. Procedure:
git fetch origin nextsplit/c-orchestrationbranch:git rebase origin/nextgit branch -f split/XX-name <commit-oid>git push origin --force split/01-* split/05-* ... split/19-*Key files per PR
packages/utils/src/import.ts,packages/utils/test/bundle-import.test.tspackages/core/src/loader/manifest.tspackages/core/src/loader/manifest.tsplugins/onerror/src/lib/onerror_page.ts,plugins/onerror/src/app.ts,plugins/onerror/src/config/config.default.tsplugins/development/src/app/middleware/loader_trace_template.ts,plugins/development/src/app/middleware/egg_loader_trace.tsplugins/watcher/src/config/config.default.tstools/egg-bundler/{package.json,tsconfig.json,tsdown.config.ts,vitest.config.ts},pnpm-workspace.yaml, roottsconfig.json, roottsdown.config.tstools/egg-bundler/src/lib/ExternalsResolver.ts,tools/egg-bundler/test/ExternalsResolver.test.tstools/egg-bundler/src/lib/ManifestLoader.tstools/egg-bundler/src/scripts/generate-manifest.mjs,tools/egg-bundler/src/lib/ManifestLoader.tstools/egg-bundler/src/index.ts,tools/egg-bundler/test/index.test.tstools/egg-bundler/src/lib/EntryGenerator.ts,tools/egg-bundler/test/EntryGenerator.test.tstools/egg-bundler/src/lib/PackRunner.ts,tools/egg-bundler/test/PackRunner*.test.tstools/egg-bundler/src/lib/Bundler.ts,tools/egg-bundler/test/integration.test.ts,tools/egg-bundler/docs/output-structure.mdtools/egg-bundler/test/deterministic.test.ts,tools/egg-bundler/test/no-filesystem-scan.test.tstools/egg-bundler/test/fixtures/apps/tegg-app/**tools/egg-bin/src/commands/bundle.ts,tools/egg-bin/src/index.ts,tools/egg-bin/package.jsontools/egg-bundler/src/lib/ExternalsResolver.ts,tools/egg-bundler/test/ExternalsResolver.test.tstools/egg-bundler/src/lib/Bundler.tsTest commands
🤖 Generated with Claude Code
Agent operational notes
Local master branch for rebasing
All 19 per-PR branches are derived from a single linear branch
split/c-orchestration(current tip:9911d5363). When rebasing is needed:Branches exist on both remotes
All
split/*branches are pushed to:Both must be force-pushed when rebasing.
pnpm install constraint
ut install(the utoo skill) does NOT support pnpm workspacecatalog:specifiers. For dependency install, usepnpm installdirectly:pnpm install --no-frozen-lockfile # catalog deps require thisFor running tests, use
pnpm --filter=<pkg> run testornpx vitest run --project=<pkg>directly.lint-staged quirk with fixture-only commits
The pre-commit hook runs oxlint via lint-staged. Commits touching only
test/fixtures/**files will be rejected with "No files found to lint" because oxlint ignores fixture dirs. Workaround: include at least one non-fixture source change in the commit (e.g., a structural assertion test inintegration.test.ts).Agent worktrees (may have been pruned)
Previous session created worktrees under
.claude/worktrees/agent-*. Checkgit worktree list— they may still exist. The key one isagent-aa51f131which hadsplit/c-orchestrationchecked out.