feat(tegg-loader): support async module importer override (__EGG_MODULE_IMPORTER__)#5989
Conversation
Add an optional `__EGG_MODULE_IMPORTER__` global hook to `LoaderUtil.loadFile`, mirroring the existing `__EGG_BUNDLE_MODULE_LOADER__` bundle hook. When set, the loader delegates module loading to it instead of the built-in `await import()`. This is an extension point for bundler-based test runners (e.g. Vitest). When an app is tested as published packages (the loader externalized to native `import()`) while the test file imports the same fixture source through the runner's module graph, the two resolve to different module instances — so a class registered as an egg proto by the loader is not the same class the test references, and `ctx.getEggObject(ClassRef)` throws "can not get proto for clazz". Routing the loader's import through the runner (e.g. `filePath => import(filePath)` evaluated in the runner context) keeps a single module instance. eggjs's own tests don't hit this because tegg packages resolve to workspace source (inlined by Vite), so the loader already imports through Vite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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 due to trivial changes (1)
📝 WalkthroughWalkthroughAdds a new ChangesModuleImporter global fallback
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an async module import override hook (globalThis.__EGG_MODULE_IMPORTER__) to LoaderUtil.loadFile so bundler-based runners (e.g. Vitest) can force loader imports through the runner’s module graph and avoid duplicate module instances.
Changes:
- Add
__EGG_MODULE_IMPORTER__hook handling (with error wrapping) toLoaderUtil.loadFile. - Add
ModuleImportertype + global declaration in@eggjs/typings. - Add loader tests covering importer usage, fallback behavior, and error wrapping.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tegg/core/loader/src/LoaderUtil.ts | Adds the async importer override hook before native dynamic import. |
| tegg/core/loader/test/Loader.test.ts | Adds tests for importer override, null fallback, and error wrapping. |
| packages/typings/src/index.ts | Introduces ModuleImporter type and docs for the new hook. |
| packages/typings/src/global.ts | Declares globalThis.__EGG_MODULE_IMPORTER__ for consumers. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## next #5989 +/- ##
=======================================
Coverage 85.94% 85.94%
=======================================
Files 669 669
Lines 19925 19929 +4
Branches 3961 3962 +1
=======================================
+ Hits 17124 17128 +4
Misses 2423 2423
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 introduces an asynchronous module importer override (__EGG_MODULE_IMPORTER__) to delegate module loading to custom runners (like Vitest), preventing duplicate module instances during testing. It updates global typings, implements the loader fallback in LoaderUtil.ts, and adds corresponding unit tests. The feedback suggests normalizing the file path passed to the importer to use forward slashes, ensuring cross-platform consistency on Windows.
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.
| // same instance the test file imports. See `ModuleImporter` in @eggjs/typings. | ||
| if (exports == null && typeof globalThis.__EGG_MODULE_IMPORTER__ === 'function') { | ||
| try { | ||
| exports = await globalThis.__EGG_MODULE_IMPORTER__(originalFilePath); |
There was a problem hiding this comment.
To ensure cross-platform consistency and prevent backslash escaping or path matching issues on Windows, the file path passed to __EGG_MODULE_IMPORTER__ should be normalized to use forward slashes (/). This matches the normalization pattern already used for __EGG_BUNDLE_MODULE_LOADER__ on line 93, maintaining symmetry between path resolution and module loading.
| exports = await globalThis.__EGG_MODULE_IMPORTER__(originalFilePath); | |
| exports = await globalThis.__EGG_MODULE_IMPORTER__(originalFilePath.split('\\').join('/')); |
References
- Maintain symmetry between path resolution and module loading for bundle-only modules by using the same raw, unresolved path as the canonical key.
Address review: pass the importer a forward-slash path, mirroring __EGG_BUNDLE_MODULE_LOADER__, for cross-platform consistency on Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review!
|
Drive-by: `oxfmt --check .` (the repo-wide fmtcheck) was failing on site/docs/zh-CN/core/bundle.md (table alignment) on `next`, turning the check red for every PR. Reformat so fmtcheck passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Also pushed a drive-by commit reformatting |
| if (exports == null && typeof globalThis.__EGG_MODULE_IMPORTER__ === 'function') { | ||
| try { | ||
| // Pass a POSIX-normalized path, mirroring __EGG_BUNDLE_MODULE_LOADER__, | ||
| // so importers behave consistently across platforms (e.g. on Windows). | ||
| exports = await globalThis.__EGG_MODULE_IMPORTER__(originalFilePath.split('\\').join('/')); |
| * When set, the loader delegates module loading to this importer instead of the | ||
| * built-in `await import(filePath)`. Its main use is testing with a bundler-based | ||
| * test runner (e.g. Vitest): when an app's egg modules are loaded by the loader | ||
| * via the native `import()` while the test file imports the same source through | ||
| * the runner's module graph, the two resolve to *different* module instances — | ||
| * so a class decorated as an egg proto by the loader is not the same class the | ||
| * test references, and `ctx.getEggObject(ClassRef)` fails with "can not get proto". | ||
| * | ||
| * A test runner can inject an importer that routes loading through its own module | ||
| * graph (e.g. `filePath => import(filePath)` evaluated inside the runner context), | ||
| * keeping a single module instance. Return value mirrors `await import()`. | ||
| */ |
What
Add an optional
__EGG_MODULE_IMPORTER__global hook toLoaderUtil.loadFile, mirroring the existing__EGG_BUNDLE_MODULE_LOADER__bundle hook. When set, the loader delegates module loading to it instead of the built-inawait import().Why
This is an extension point for bundler-based test runners (e.g. Vitest).
When an egg app is tested with the tegg packages installed as published packages (so the loader is externalized and uses native
import()), while the test file imports the same fixture source through the runner's module graph (Vite), the same file resolves to two different module instances. The proto registry keys by the class object (Reflect.defineMetadata(CLAZZ_PROTO, proto, clazz)), so the class the loader registered is not the class the test holds, and:eggjs's own tests don't hit this because the tegg packages resolve to workspace source (inlined by Vite), so the loader already imports through Vite and there is a single instance.
Downstream consumers (e.g. tegg distributed as published
@scope/*packages) can route the loader's import through the runner by setting the hook in a Vitest setupFile (which is Vite-processed, so itsimportis the runner's):This keeps one module instance → the loader-registered class === the test's class →
getEggObject(ClassRef)works. (A follow-up can wire this provision into@eggjs/tegg-vitest/@eggjs/mock'ssetup_vitestso it is out-of-box.)Notes
ModuleImporterin@eggjs/typingsand declared onglobalThis, mirroringBundleModuleLoader.Test
Added 3 cases to
tegg/core/loader/test/Loader.test.tsmirroring the bundle-loader tests (load through importer / fall through on null / wrap errors).vitest run tegg/core/loader/test/Loader.test.ts→ 10 passed.🤖 Generated with Claude Code
Summary by CodeRabbit
null, and wrapped error reporting when it throws.egg-bin bundlecommand parameter table formatting (no functional changes).