Skip to content

feat(utils): consume __EGG_MODULE_IMPORTER__ in importModule#5990

Closed
elrrrrrrr wants to merge 1 commit into
eggjs:nextfrom
elrrrrrrr:feat/utils-module-importer
Closed

feat(utils): consume __EGG_MODULE_IMPORTER__ in importModule#5990
elrrrrrrr wants to merge 1 commit into
eggjs:nextfrom
elrrrrrrr:feat/utils-module-importer

Conversation

@elrrrrrrr

@elrrrrrrr elrrrrrrr commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What

Follow-up to #5989. That PR added the ModuleImporter type + __EGG_MODULE_IMPORTER__ global declaration and made @eggjs/tegg-loader consume it. This PR extends the same hook to egg-core's module/boot-file loading in @eggjs/utils importModule():

const _moduleImporter = globalThis.__EGG_MODULE_IMPORTER__;
if (_moduleImporter) {
  let obj = await _moduleImporter(moduleFilePath);
  // …same default/__esModule unwrapping as the other loaders
  return obj;
}
// …existing native `await import(fileUrl)` fallback

Why

importModule() is what egg-core uses to load plugin boot files (app.ts, app/extend/*.ts) and config. Under a bundler-based test runner (Vitest) the worker thread loads these via native import(), which (a) can't transpile TS enum/decorators in boot files ("enum is not supported in strip-only mode"), and (b) puts them in a different module realm than the test file — so the tegg-loader hook alone (which covers tegg modules) isn't enough; the app's boot graph still loads natively.

Routing importModule through the same __EGG_MODULE_IMPORTER__ (e.g. filePath => import(filePath) evaluated in the runner context) makes the whole app — boot files, config, and tegg modules — resolve through one module graph. This is what lets a downstream tegg distribution (published @scope/* packages, externalized by the runner) boot its app fixtures and run ctx.getEggObject(ImportedClass) against a single class instance.

Notes

Test

Added packages/utils/test/module-importer.test.ts (load through importer / importDefaultOnly / __esModule unwrap / precedence over native import). vitest run packages/utils/test/module-importer.test.ts → green; existing bundle/snapshot import tests unaffected.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Introduced customizable module loading framework supporting external resolution strategies and flexible export handling, enabling advanced import configurations and third-party system integration
  • Tests
    • Added comprehensive test coverage for module interception capabilities, validating export behavior, selective export modes, and custom resolution precedence over standard imports

Follow-up to eggjs#5989 (which added the `ModuleImporter` type/global and the
@eggjs/tegg-loader consumer). Extend the same async module-importer override to
egg-core's module/boot-file loading via `importModule`: when a test runner sets
`globalThis.__EGG_MODULE_IMPORTER__`, app boot files (app.ts, app/extend/*.ts)
and config load through the runner's module graph too, so enums/decorators in
boot files transpile and the whole app shares one module realm with the test
(matching how the tegg loader already routes tegg modules).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 24, 2026 02:46
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

importModule in packages/utils/src/import.ts gains a new async override branch: when globalThis.__EGG_MODULE_IMPORTER__ is set, the function delegates to it with the resolved moduleFilePath, applies __esModule default-unwrapping and importDefaultOnly handling, and returns immediately, bypassing ESM/CJS loading. A new Vitest test file validates this behavior across five cases.

Changes

Module Importer Hook

Layer / File(s) Summary
__EGG_MODULE_IMPORTER__ hook implementation and tests
packages/utils/src/import.ts, packages/utils/test/module-importer.test.ts
Inserts a new async branch in importModule that calls globalThis.__EGG_MODULE_IMPORTER__ with moduleFilePath, applies the same __esModule/importDefaultOnly unwrapping used elsewhere, and short-circuits the ESM/CJS paths. Test suite covers: no-op when unset, interception with path assertion, importDefaultOnly extraction, __esModule double-default unwrapping, and importer precedence over native dynamic import.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • eggjs/egg#5853: Modifies the same importModule() pipeline in packages/utils/src/import.ts with a global-hook–based resolution pattern and shares the same __esModule/importDefaultOnly unwrapping logic.
  • eggjs/egg#5924: Changes importModule control flow around the bundle-module-loader path, directly affecting the native dynamic import fallback that this PR's hook precedes.
  • eggjs/egg#5989: Implements the identical globalThis.__EGG_MODULE_IMPORTER__ async hook concept, wired into tegg/core/loader/src/LoaderUtil.ts's loadFile().

Suggested reviewers

  • fengmk2
  • killagu

🐇 A little hook slipped in with care,
Before ESM and CJS were there.
__EGG_MODULE_IMPORTER__ takes the lead,
Unwraps defaults with bunny speed!
Five tests confirm it hops just right —
The module path resolved in sight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main change: adding support for the __EGG_MODULE_IMPORTER__ hook in the importModule function, which is the core objective of the PR.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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 an async module importer override mechanism via globalThis.__EGG_MODULE_IMPORTER__ in packages/utils/src/import.ts to allow custom loaders (like Vitest) to intercept and resolve modules. It includes handling for unwrapping double-default shapes and respecting the importDefaultOnly option. Comprehensive unit tests have been added in packages/utils/test/module-importer.test.ts to verify these behaviors. No review comments were provided, and there is no additional feedback to address.

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.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.95%. Comparing base (0d2a357) to head (b9ba1a2).

Additional details and impacted files
@@           Coverage Diff           @@
##             next    #5990   +/-   ##
=======================================
  Coverage   85.94%   85.95%           
=======================================
  Files         669      669           
  Lines       19929    19937    +8     
  Branches     3962     3965    +3     
=======================================
+ Hits        17128    17136    +8     
  Misses       2423     2423           
  Partials      378      378           

☔ 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.

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.

Pull request overview

Extends @eggjs/utils’s importModule() to optionally delegate module loading to the global __EGG_MODULE_IMPORTER__ hook (introduced in #5989) so Egg’s app boot/config/module loading can be routed through a bundler/test-runner module graph (e.g. Vitest), avoiding cross-realm module duplication.

Changes:

  • Add __EGG_MODULE_IMPORTER__ interception path to importModule() with the same default/__esModule unwrapping behavior used by existing loaders.
  • Add a new Vitest test suite validating importer interception, importDefaultOnly, and __esModule “double-default” unwrapping.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/utils/src/import.ts Adds __EGG_MODULE_IMPORTER__ hook support to importModule() before the native dynamic import/require fallback.
packages/utils/test/module-importer.test.ts Introduces tests covering importer override behaviors for importModule().

Comment thread packages/utils/src/import.ts
Comment thread packages/utils/test/module-importer.test.ts

@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)
packages/utils/test/module-importer.test.ts (1)

50-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for null importer return fallback.

The suite validates precedence, but it does not lock the null/undefined fallback behavior that sibling loader flows already rely on.

Suggested test addition
   it('takes precedence over the native dynamic import', async () => {
     globalThis.__EGG_MODULE_IMPORTER__ = async () => ({ fromImporter: true });

     const result = await importModule(getFilepath('esm'));
     assert.deepEqual(result, { fromImporter: true });
   });
+
+  it('falls back to native import when importer returns null', async () => {
+    globalThis.__EGG_MODULE_IMPORTER__ = async () => null;
+
+    const result = await importModule(getFilepath('esm'));
+    assert.ok(result);
+    assert.equal(typeof result, 'object');
+  });
 });
🤖 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 `@packages/utils/test/module-importer.test.ts` around lines 50 - 56, Add a
regression test case after the existing precedence test in the test suite to
verify that when the globalThis.__EGG_MODULE_IMPORTER__ returns null or
undefined, the importModule function falls back to the native dynamic import
behavior. The new test should set the custom importer to return null/undefined,
call importModule with the same esm module filepath, and assert that the result
matches what the native import would return rather than a null/undefined value
from the importer.
🤖 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 `@packages/utils/src/import.ts`:
- Around line 504-517: The conditional check for `_moduleImporter` should verify
it is a function using `typeof _moduleImporter === 'function'` to prevent
crashes from non-function truthy values. The path passed to the
`_moduleImporter` function call should be normalized to POSIX format for
cross-platform compatibility instead of using the raw `moduleFilePath`.
Additionally, the logic should only short-circuit and return the importer result
when it returns a non-null value, allowing fallback to native import when the
custom importer returns null or undefined.

---

Nitpick comments:
In `@packages/utils/test/module-importer.test.ts`:
- Around line 50-56: Add a regression test case after the existing precedence
test in the test suite to verify that when the
globalThis.__EGG_MODULE_IMPORTER__ returns null or undefined, the importModule
function falls back to the native dynamic import behavior. The new test should
set the custom importer to return null/undefined, call importModule with the
same esm module filepath, and assert that the result matches what the native
import would return rather than a null/undefined value from the importer.
🪄 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: 877aa3c8-b17d-40bd-b6ee-8d93689d5261

📥 Commits

Reviewing files that changed from the base of the PR and between 0d2a357 and b9ba1a2.

📒 Files selected for processing (2)
  • packages/utils/src/import.ts
  • packages/utils/test/module-importer.test.ts

Comment thread packages/utils/src/import.ts
@elrrrrrrr elrrrrrrr requested a review from killagu June 24, 2026 02:51
@elrrrrrrr

Copy link
Copy Markdown
Contributor Author

@killagu this is the small follow-up to #5989 — it just extends the same __EGG_MODULE_IMPORTER__ hook (type/global already merged) to @eggjs/utils importModule, so egg-core's boot-file loading also routes through a test runner's module graph. Would appreciate a look when you have a moment 🙏

@killagu killagu 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.

LGTM

@elrrrrrrr

Copy link
Copy Markdown
Contributor Author

Superseded by #5992 (same change, re-opened from a branch on eggjs/egg so CodeQL code scanning runs — this fork PR was permanently blocked waiting on CodeQL results).

@elrrrrrrr elrrrrrrr closed this Jun 24, 2026
auto-merge was automatically disabled June 24, 2026 06:08

Pull request was closed

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.

3 participants