fix(bundle): boot a bundled tegg app (cnpmcore) end to end#5987
Conversation
Make a complex multi-plugin egg/tegg app (cnpmcore) boot from an @utoo/pack bundle, where the module source files do not exist on disk and framework/plugin paths are rewritten to the bundle output dir. - core loader getPluginPath: resolve falsy-path plugins via bundle resolution; #resolveBundlePluginPath falls back to package.json for entry-less plugins - utils importResolve: feature-detect import.meta.resolve and forward paths to the require() fallback - egg customEggPaths: use the rebased framework dir when import.meta.dirname is bundler-rewritten - tegg EggModuleLoader.loadModule: reuse the manifest's precomputed decorated files in bundle mode so load-unit lifecycle hooks (EggQualifierProtoHook) still see the real decorated classes via ctx.loader.load(). Without this, auto Egg-qualifier injects of egg-compatible objects (e.g. httpClient) are ambiguous and DI fails with EggPrototypeNotFound. - test: bundle-mode regression for the auto Egg-qualifier inject and for manifest-served module discovery (no residual module-dir glob) Verified end to end: cnpmcore bundles, boots, and serves a full npm round-trip (publish/view/install) from the bundle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughBundle-mode resolution is hardened across four areas: ChangesBundle Mode Resolution and Manifest-Driven Loading
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## next #5987 +/- ##
==========================================
+ Coverage 85.91% 85.94% +0.02%
==========================================
Files 669 669
Lines 19907 19925 +18
Branches 3949 3961 +12
==========================================
+ Hits 17104 17124 +20
+ Misses 2425 2423 -2
Partials 378 378 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request improves bundle-mode compatibility and robustness across @eggjs packages. Key changes include updating plugin path resolution to handle missing or rewritten paths, falling back to package.json resolution for older plugins, feature-detecting import.meta.resolve capability, and reusing precomputed decoratedFiles from the manifest in EggModuleLoader to avoid re-globbing module directories. The review feedback highlights two potential runtime crashes: a TypeError when accessing import.meta.resolve if import.meta is undefined, and another TypeError when calling path.resolve with an undefined import.meta.dirname.
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.
| const supportImportMetaResolve = | ||
| nodeMajorVersion >= 18 && typeof (import.meta as { resolve?: unknown }).resolve === 'function'; |
There was a problem hiding this comment.
If import.meta is undefined (which is common in CommonJS environments or when bundled to CJS without a shim), attempting to access (import.meta as any).resolve will throw a TypeError: Cannot read properties of undefined (reading 'resolve') at runtime. We should safely guard the existence of import.meta before checking the type of its resolve property.
const supportImportMetaResolve =
nodeMajorVersion >= 18 &&
typeof import.meta !== 'undefined' &&
!!import.meta &&
typeof (import.meta as { resolve?: unknown }).resolve === 'function';There was a problem hiding this comment.
Good catch — fixed in 5997a06 (guarded the undefined case; no-op when defined).
Address review feedback: avoid TypeErrors when `import.meta` / its members are absent. - utils supportImportMetaResolve: guard `typeof import.meta !== 'undefined'` before reading `.resolve`, matching the file's existing CJS handling - egg customEggPaths: short-circuit `importMetaRewritten` when `import.meta.dirname` is undefined (bundler-rewritten to undefined or pre-20.11 Node) so `path.resolve(undefined)` cannot throw Both are no-ops when the values are defined. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Theme F assertion required *zero* non-module fallback globs, but a plugin whose `app/service`/`app/middleware` dir is empty produces an empty-result glob that the manifest does not cache, so it legitimately falls back to a real-fs glob. This only materializes in the pnpm/CI layout (where those dirs sit under `<app>/node_modules/@eggjs/*`), not in a local run, so CI failed while local passed. Assert the app's own (first-party) discovery is manifest-served and exclude third-party `node_modules` globs, which are environment-dependent and orthogonal to what this test covers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`app.getEggObject` is not available on the single-mode mock app in CI, and this case did not actually catch the bug anyway (the fixture's module source files exist on disk, so the pre-fix globbing loader still finds them). The Theme F assertion — that loadModule no longer globs module dirs in consume mode — is the real regression for this fix and fails when the fix is reverted. Revert the now-unused EggTypeService fixture inject. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…absent #resolveBundlePluginPath rebases a plugin's resolved dir under the output baseDir's node_modules (e.g. <baseDir>/node_modules/@eggjs/tegg-plugin/dist). In a real bundle the externals are installed there, so it exists and holds the files the manifest keyed. But when a bundle store is registered over a source checkout — an integration test consuming a normally-collected manifest, or a dev bundle whose externals still resolve from the workspace — that rebased dir does not exist, so the plugin's config/extend/app files (e.g. tegg-plugin's app/extend that provides app.getEggObject) fail to load. Guard the rebase on fs.existsSync; otherwise return the real resolved dir. No-op for real bundles (rebased dir exists); fixes source/dev consumption. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a **Bundle Deployment** guide to the Core docs (EN + zh-CN), documenting the `egg-bin bundle` / `@eggjs/egg-bundler` workflow: - **Build** — `egg-bin bundle`, options, auto-externals, `module.yml` config - **Output** — artifact layout and `bundle-manifest.json` - **Run** — install externals next to `worker.js` (`npm ci --omit=dev`), single-process boot - **Tegg applications** — module discovery is served from the manifest's `decoratedFiles` (no on-disk glob), keeping the tegg DI graph intact for auto-resolved egg-compatible injects (`httpClient`/`logger`/`config`) - **Limitations** — single-process only, native addons external Registered in the Core sidebar next to the Startup Manifest guide it builds on. Pairs with the bundle-boot fix in #5987. 🤖 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 “Bundle Deployment / 打包部署” documentation section, including the `egg-bin bundle` workflow, key CLI options, and how build outputs are structured in `dist-bundle/`. * Expanded runtime instructions (worker setup and single-process mode), plus `module.yml` configuration examples and documented limitations. * Updated both English and Chinese site navigation to include the new bundle page. * Refreshed the English bundle page to focus on Egg bundle deployment and remove tegg application coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Motivation
Booting a real, complex multi-plugin egg/tegg app from an
@utoo/packbundle (egg-bin bundle→node worker.js) did not fully work. Using cnpmcore (a large DDD tegg app) as the target, the bundle built fine and booted through the entire egg framework, all plugins, and the egg lifecycle — but then failed inside tegg DI:This PR collects the bundle-compat fixes that get cnpmcore to boot and serve a full npm round-trip from a bundle.
Root cause of the tegg-DI blocker
In a bundle the module source files do not exist on disk and only the manifest knows the decorated files.
EggModuleLoader.buildAppGraphalready loads modules through the manifest's precomputeddecoratedFiles, butEggModuleLoader.loadModulecreated its per-module load-unit loader via the globbingLoaderFactory.createLoader(MODULE), which returns 0 classes in a bundle.That loader's
load()is what the load-unit lifecycle hookEggQualifierProtoHook.preCreate(ctx)calls (ctx.loader.load()) to add theEggqualifier to every inject. With 0 classes, the hook never runs, so an inject like cnpmcore's@Inject() httpClient: HttpClient(no explicit@EggQualifier) never gets itsEgg=CONTEXTqualifier. SincehttpClientexists as both an app and a context egg object, the inject becomes ambiguous and DI fails.Verified by side-by-side bundle/non-bundle probes: non-bundle resolves the request
[Egg=CONTEXT]to exactly 1 proto; the bundle issued[](qualifier missing) and matched 0.Changes
@eggjs/tegg-pluginEggModuleLoader.loadModule: in bundle mode, reuse the manifest's precomputeddecoratedFiles(new ModuleLoader(path, { precomputedFiles, loaderFS })) instead of globbing, so lifecycle hooks see the real decorated classes. Gated on aloadedFromManifestflag — zero behavior change in non-bundle mode.@eggjs/coreloadergetPluginPath: resolve falsy-path plugins via bundle resolution;#resolveBundlePluginPathfalls back topackage.jsonfor entry-less plugins.@eggjs/utilsimportResolve: feature-detectimport.meta.resolveand forwardpathsto therequire()fallback.eggcustomEggPaths: use the rebased framework dir whenimport.meta.dirnameis bundler-rewritten.Tests
BundledAppBoot.test.ts: a fixture proto now carries an autoEgg-qualifier inject of an egg-compatible object (no explicit@EggQualifier), exercising theEggQualifierProtoHookpath in manifest-consume mode.loadModulechange.End-to-end validation (cnpmcore)
Against
cnpmcore@masterwith published@eggjs/egg-bundler@beta+ this branch's packages:egg-bin bundle→ 24-file artifact ✓node worker.js(MySQL + Redis) →server listening on port 7001✓npm publish @cnpm/hello-bundle@1.0.0✓npm view→ 1.0.0 ✓npm install+require()→ returns the package ✓🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
import.meta.resolvedetection and improved fallback resolution behavior when it’s unavailable.Tests