Skip to content

feat(utils): add setBundleModuleLoader runtime hook#5867

Merged
killagu merged 9 commits intoeggjs:nextfrom
killagu:split/01-utils-bundle-module-loader
Apr 26, 2026
Merged

feat(utils): add setBundleModuleLoader runtime hook#5867
killagu merged 9 commits intoeggjs:nextfrom
killagu:split/01-utils-bundle-module-loader

Conversation

@killagu
Copy link
Copy Markdown
Contributor

@killagu killagu commented Apr 14, 2026

Summary

Adds setBundleModuleLoader(loader) to @eggjs/utils so a bundled Egg application can intercept importModule() and short-circuit module lookups to an in-bundle map. Reuses the existing __esModule double-default and importDefaultOnly handling.

State is stored on globalThis.__EGG_BUNDLE_MODULE_LOADER__ so the bundled entry and an external egg framework instance — which may be different module instances under turbopack — share the same hook.

Why

This is batch 1, part of a 19-PR split of #5863 (a 4812-line monolith adding @eggjs/egg-bundler). #5863 is kept open as a tracking reference. This PR is independent (base = next) and can land in any order with the other batch-1 PRs.

The runtime hook is a prerequisite for the upcoming @eggjs/egg-bundler package: the generated worker entry calls it before `new Application()` to redirect every loader `importModule()` to the bundled namespace, eliminating filesystem scanning at runtime.

Test plan

  • `pnpm --filter=@eggjs/utils test` — 60 passed + 13 skipped (6 new `bundle-import.test.ts` cases)
  • typecheck clean

Stack context

Other batch-1 PRs (independent, can land in any order):

  • `refactor(onerror): inline error page template as string constant`
  • `refactor(development): inline loader trace template as string constant`
  • `refactor(watcher): use direct class imports for event sources`

Subsequent batches (will be opened once batch-1 merges):

  • Group A continued: `feat(core): add ManifestStore.fromBundle`, `feat(core): add ManifestStore.setBundleStore hook`
  • Group C: 10 PRs adding `@eggjs/egg-bundler` package incrementally
  • Group D: `feat(egg-bin): add bundle subcommand`
  • Group E: `remove @eggjs/* from auto-externals`, `post-process turbopack output`

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Add a global bundle module loader that can intercept imports (including virtual/bundled specifiers), automatically unwrap default exports, and optionally return only the default; import falls back to normal behavior if no loader or loader returns undefined.
  • Tests

    • New tests covering interception, fallback behavior, default-unwrapping (including nested/default-wrapped cases), virtual/non-existent specifiers, path normalization, and updated raw HTTP request handling in request tests.

Copilot AI review requested due to automatic review settings April 14, 2026 03:59
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

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 a global bundle module loader and makes importModule consult it first (posix-normalizing paths), returning loader-provided values with interop/default-unwrapping and honoring importDefaultOnly; falls back to normal resolution when the loader returns undefined.

Changes

Cohort / File(s) Summary
Bundle Loader Implementation
packages/utils/src/import.ts
Adds BundleModuleLoader type and setBundleModuleLoader() (stored on globalThis.__EGG_BUNDLE_MODULE_LOADER__). importModule() normalizes input paths (backslash → forward slash), calls the loader before resolution, unwraps nested default per interop rules, respects importDefaultOnly, and falls back to prior resolution when the loader returns undefined.
Bundle Loader Test Suite
packages/utils/test/bundle-import.test.ts
New Vitest tests registering/clearing the loader, verifying interception for real and virtual specifiers, testing Windows-path normalization, importDefaultOnly behavior, double-default unwrapping, preservation of non-default/null values, and fallthrough when loader returns undefined.
Raw HTTP test adjustment
packages/egg/test/cluster1/app_worker.test.ts
Replaces supertest URI override approach with direct raw TCP HTTP request generation and updates assertions to validate full raw HTTP response (status line and body).

Sequence Diagram

sequenceDiagram
    participant Client
    participant ImportModule as importModule
    participant BundleLoader as BundleModuleLoader
    participant Resolver

    Client->>ImportModule: importModule(filepath, options)
    ImportModule->>ImportModule: normalize filepath (\\ -> /)
    alt loader registered
        ImportModule->>BundleLoader: BundleModuleLoader(normalized_filepath)
        BundleLoader-->>ImportModule: module | undefined
        alt module returned
            ImportModule->>ImportModule: unwrap defaults / apply importDefaultOnly
            ImportModule-->>Client: return processed module
        else undefined
            ImportModule->>Resolver: importResolve / existing flows
            Resolver-->>Client: return resolved module
        end
    else no loader
        ImportModule->>Resolver: importResolve / existing flows
        Resolver-->>Client: return resolved module
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • jerryliang64
  • fengmk2

Poem

🐰 I hopped from backslash into slash,

A tiny loader snug and brash,
I peeped inside a double-wrap,
Returned the default with a clap,
Now imports skip a dash!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 'feat(utils): add setBundleModuleLoader runtime hook' clearly and concisely describes the main change—adding a new runtime hook function to the utils package.
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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.51%. Comparing base (965f62c) to head (4800381).
⚠️ Report is 1 commits behind head on next.

Additional details and impacted files
@@            Coverage Diff             @@
##             next    #5867      +/-   ##
==========================================
- Coverage   85.51%   85.51%   -0.01%     
==========================================
  Files         662      662              
  Lines       18863    18876      +13     
  Branches     3658     3662       +4     
==========================================
+ Hits        16131    16142      +11     
  Misses       2361     2361              
- Partials      371      373       +2     

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown
Contributor

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

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 a BundleModuleLoader to allow bundled applications to serve modules that may not exist on disk. It adds a global registration mechanism via setBundleModuleLoader and updates importModule to intercept requests using the registered loader. Feedback highlights potential consistency issues with the isESM flag when multiple package instances exist and the need to restore the original isESM state when the loader is unset to ensure test isolation.

Comment thread packages/utils/src/import.ts Outdated
Comment thread packages/utils/src/import.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/utils/src/import.ts`:
- Around line 410-415: The code uses (globalThis as
any).__EGG_BUNDLE_MODULE_LOADER__ which weakens typing; declare a typed global
interface for __EGG_BUNDLE_MODULE_LOADER__ (e.g. interface Global {
__EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader }) via declare global so you
can replace both (globalThis as any).__EGG_BUNDLE_MODULE_LOADER__ occurrences
with properly typed accesses, and keep the existing logic in importModule and
the loader assignment (references: __EGG_BUNDLE_MODULE_LOADER__,
BundleModuleLoader, importModule, loader, isESM).
- Around line 409-412: setBundleModuleLoader currently sets (globalThis as
any).__EGG_BUNDLE_MODULE_LOADER__ and flips isESM = false on registration but
never restores isESM when loader is unset, causing importModule to incorrectly
use require(); update setBundleModuleLoader(loader) to set
__EGG_BUNDLE_MODULE_LOADER__ and set isESM = loader ? false : true (or restore
previous ESM state) so unregistering (loader === undefined) resets isESM; also
remove the (globalThis as any) casts by adding a globalThis augmentation that
declares __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader and use that typed
property instead of any to access the global.
🪄 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: a64667b7-dc8d-4ad0-9fcc-ba23be486dc5

📥 Commits

Reviewing files that changed from the base of the PR and between 490f849 and acb9a67.

⛔ Files ignored due to path filters (1)
  • packages/utils/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • packages/utils/src/import.ts
  • packages/utils/test/bundle-import.test.ts

Comment thread packages/utils/src/import.ts
Comment thread packages/utils/src/import.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a bundle-aware runtime hook to @eggjs/utils so a bundled Egg application can intercept importModule() calls and serve modules from an in-bundle map (avoiding disk-based resolution/scanning).

Changes:

  • Add BundleModuleLoader + setBundleModuleLoader() and consult the loader at the start of importModule().
  • Add Vitest coverage for bundle-loader behavior (hit, miss/fallthrough, default unwrapping, virtual paths).
  • Update the “export all” snapshot to include the new API.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/utils/src/import.ts Introduces setBundleModuleLoader() (stored on globalThis) and adds a pre-importResolve interception path in importModule().
packages/utils/test/bundle-import.test.ts New tests validating bundle interception, default handling, fallthrough behavior, and virtual module paths.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.

Comment thread packages/utils/src/import.ts
@killagu
Copy link
Copy Markdown
Contributor Author

killagu commented Apr 25, 2026

已按 review comments 调整完这轮修改:去掉了 setBundleModuleLoader()isESM 的改写,补了 typed global declaration,并加了 bundle miss 的回归测试。相关讨论线程我这边一并 resolve 了。

Copilot AI review requested due to automatic review settings April 25, 2026 12:12
@killagu
Copy link
Copy Markdown
Contributor Author

killagu commented Apr 25, 2026

Fixed the review comments in a0fc95b: setBundleModuleLoader no longer mutates isESM, globalThis.EGG_BUNDLE_MODULE_LOADER is typed, and bundle path normalization now uses node:path constants. Added regression coverage for ESM fallback on loader miss and Windows-style bundle paths. Local checks: pnpm exec vitest run packages/utils/test/bundle-import.test.ts packages/utils/test/index.test.ts; pnpm --filter @eggjs/utils run typecheck; pnpm exec oxfmt --check packages/utils/src/import.ts packages/utils/test/bundle-import.test.ts; pnpm exec oxlint --type-aware packages/utils/src/import.ts packages/utils/test/bundle-import.test.ts.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/utils/src/import.ts (1)

421-446: Optional: dedupe the __esModule double-default + importDefaultOnly interop logic.

The bundle branch (lines 426-429), the snapshot branch (lines 436-445), and the ESM branch (lines 461-493) all implement the same unwrap pattern with subtly different formulations (e.g. the snapshot/ESM branches include an extra typeof obj === 'object' guard around the outer check, while the bundle branch uses optional chaining only). The three are equivalent today, but as the interop rules evolve they will drift. Consider extracting a single helper, e.g.:

♻️ Proposed refactor
function unwrapInterop(obj: any, importDefaultOnly?: boolean): any {
  if (
    obj && typeof obj === 'object' &&
    obj.default?.__esModule === true && obj.default &&
    'default' in obj.default
  ) {
    obj = obj.default;
  }
  if (importDefaultOnly && obj && typeof obj === 'object' && 'default' in obj) {
    obj = obj.default;
  }
  return obj;
}

…and call it from all three sites. Non-blocking, can be deferred to a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/src/import.ts` around lines 421 - 446, The importModule
function duplicates the same interop/unwrap logic across the bundle, snapshot,
and ESM branches; extract a single helper (e.g., unwrapInterop(obj,
importDefaultOnly)) that implements the two-step unwrapping (first check for
obj.default.__esModule === true with nested default, then apply
importDefaultOnly to pick obj.default if present), and call this helper from the
_bundleModuleLoader hit handling, the _snapshotModuleLoader handling, and the
ESM branch to centralize behavior and remove the repeated conditional patterns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/utils/src/import.ts`:
- Around line 421-446: The importModule function duplicates the same
interop/unwrap logic across the bundle, snapshot, and ESM branches; extract a
single helper (e.g., unwrapInterop(obj, importDefaultOnly)) that implements the
two-step unwrapping (first check for obj.default.__esModule === true with nested
default, then apply importDefaultOnly to pick obj.default if present), and call
this helper from the _bundleModuleLoader hit handling, the _snapshotModuleLoader
handling, and the ESM branch to centralize behavior and remove the repeated
conditional patterns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb8dcf44-c470-47a6-9107-590ae80b74b8

📥 Commits

Reviewing files that changed from the base of the PR and between f97af39 and a0fc95b.

📒 Files selected for processing (2)
  • packages/utils/src/import.ts
  • packages/utils/test/bundle-import.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/utils/test/bundle-import.test.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a bundle-aware runtime hook to @eggjs/utils so bundled Egg applications can intercept importModule() and return modules from an in-bundle map (shared across module instances via globalThis), while preserving existing default-export unwrapping behavior.

Changes:

  • Add setBundleModuleLoader() and BundleModuleLoader to register a global bundle module loader hook.
  • Update importModule() to consult the bundle loader (with Windows-path normalization) before importResolve().
  • Add Vitest coverage for interception, fallback behavior, default-only behavior, double-default unwrapping, and virtual specifiers.

Reviewed changes

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

File Description
packages/utils/src/import.ts Introduces the global bundle module loader hook and integrates it into importModule() before disk-based resolution.
packages/utils/test/bundle-import.test.ts Adds test coverage for the new bundle loader behavior and path normalization.
packages/utils/test/snapshots/index.test.ts.snap Updates public export snapshot to include setBundleModuleLoader.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/egg/test/cluster1/app_worker.test.ts (1)

11-17: ⚠️ Potential issue | 🟡 Minor

Body comparison is sensitive to source-file line endings.

DEFAULT_BAD_REQUEST_HTML is a template literal whose embedded newlines are whatever bytes the source file uses on disk. If a contributor checks the repo out on Windows without core.autocrlf=input / a .gitattributes text eol=lf rule for .ts, this literal becomes CRLF while the server-emitted body remains LF, and response.includes(DEFAULT_BAD_REQUEST_HTML) (Line 37) silently fails. Consider normalizing on comparison, e.g. matching against a regex or stripping \r from response before the includes check, to make the test robust to checkout settings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/egg/test/cluster1/app_worker.test.ts` around lines 11 - 17, The
test's body comparison uses the template literal DEFAULT_BAD_REQUEST_HTML which
may contain CRLF depending on checkout, making
response.includes(DEFAULT_BAD_REQUEST_HTML) brittle; update the test to
normalize line endings before comparison (e.g., strip '\r' from the server
response or compare using a regex that ignores CRLF) so the includes check on
DEFAULT_BAD_REQUEST_HTML succeeds regardless of source-file EOLs; locate
DEFAULT_BAD_REQUEST_HTML and the response.includes usage in app_worker.test.ts
to apply the normalization.
🧹 Nitpick comments (1)
packages/egg/test/cluster1/app_worker.test.ts (1)

136-149: Add a socket timeout to avoid indefinite hangs.

requestRawPath only resolves on end and only rejects on error. If the server keeps the socket half-open (e.g. due to a regression in 400 handling or a stuck worker), the promise never settles and the suite relies entirely on Vitest's outer test timeout, producing a less actionable failure than a targeted timeout here. Since this helper is specifically used to probe broken-HTTP behavior, defending against the hang case is worthwhile.

♻️ Suggested hardening
 function requestRawPath(port: number, path: string) {
   return new Promise<string>((resolve, reject) => {
     let response = '';
     const socket = net.createConnection(port, '127.0.0.1', () => {
       socket.write(`GET ${path} HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nConnection: close\r\n\r\n`);
     });
+    socket.setTimeout(5000, () => {
+      socket.destroy(new Error(`requestRawPath timeout after 5s, partial response: ${response}`));
+    });
     socket.setEncoding('utf8');
     socket.on('data', (chunk) => {
       response += chunk;
     });
     socket.on('error', reject);
     socket.on('end', () => resolve(response));
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/egg/test/cluster1/app_worker.test.ts` around lines 136 - 149, The
requestRawPath helper can hang if the socket never closes; add a socket timeout
to fail fast: set a reasonable timeout (e.g. 5s) on the created socket
(socket.setTimeout) and listen for the 'timeout' event to destroy the socket and
reject the promise with a clear error; ensure the 'data'/'end'/'error' handlers
still clear any timeout and that you remove the timeout listener once the
promise settles so no listeners leak. Target the requestRawPath function and its
socket variable to implement this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/egg/test/cluster1/app_worker.test.ts`:
- Around line 11-17: The test's body comparison uses the template literal
DEFAULT_BAD_REQUEST_HTML which may contain CRLF depending on checkout, making
response.includes(DEFAULT_BAD_REQUEST_HTML) brittle; update the test to
normalize line endings before comparison (e.g., strip '\r' from the server
response or compare using a regex that ignores CRLF) so the includes check on
DEFAULT_BAD_REQUEST_HTML succeeds regardless of source-file EOLs; locate
DEFAULT_BAD_REQUEST_HTML and the response.includes usage in app_worker.test.ts
to apply the normalization.

---

Nitpick comments:
In `@packages/egg/test/cluster1/app_worker.test.ts`:
- Around line 136-149: The requestRawPath helper can hang if the socket never
closes; add a socket timeout to fail fast: set a reasonable timeout (e.g. 5s) on
the created socket (socket.setTimeout) and listen for the 'timeout' event to
destroy the socket and reject the promise with a clear error; ensure the
'data'/'end'/'error' handlers still clear any timeout and that you remove the
timeout listener once the promise settles so no listeners leak. Target the
requestRawPath function and its socket variable to implement this.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c493e0b3-33f4-41af-81fe-0e5924eb0419

📥 Commits

Reviewing files that changed from the base of the PR and between a0fc95b and 7e36762.

📒 Files selected for processing (1)
  • packages/egg/test/cluster1/app_worker.test.ts

killagu added a commit that referenced this pull request Apr 25, 2026
Adds static `ManifestStore.fromBundle(data, baseDir)` that creates a
store from pre-validated bundled manifest data, skipping lockfile/config
fingerprint checks.

Part of #5863 split. Tracking: #5871. Stacked on: #5867 (A1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Copilot AI review requested due to automatic review settings April 25, 2026 13:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (2)
packages/utils/test/bundle-import.test.ts (2)

9-12: Optional: also clear the loader in beforeEach for defensive symmetry.

The first test (lines 14–18) assumes globalThis.__EGG_BUNDLE_MODULE_LOADER__ is unset on entry. Vitest isolates files by default so this is fine in practice, but adding a beforeEach(() => setBundleModuleLoader(undefined)) would make the suite robust against any leakage from prior runs in the same worker context.

♻️ Suggested addition
-  afterEach(() => {
-    setBundleModuleLoader(undefined);
-  });
+  beforeEach(() => {
+    setBundleModuleLoader(undefined);
+  });
+  afterEach(() => {
+    setBundleModuleLoader(undefined);
+  });

(Remember to add beforeEach to the vitest import.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/test/bundle-import.test.ts` around lines 9 - 12, Add a
defensive beforeEach that calls setBundleModuleLoader(undefined) to mirror the
existing afterEach cleanup so tests cannot rely on prior worker state; update
the vitest import to include beforeEach (alongside afterEach) and ensure
setBundleModuleLoader is referenced in the new beforeEach, which will also reset
globalThis.__EGG_BUNDLE_MODULE_LOADER__ before each test.

81-89: Windows-path test is functional but slightly fragile across OSes.

getFilepath('esm').split(path.posix.sep).join(path.win32.sep) only converts forward slashes to backslashes. On a Windows runner getFilepath may already return backslashes, so the split becomes a no-op and filepath ends up unchanged — meaning the test then exercises the “already-normalized” path rather than the Windows→POSIX normalization in normalizeBundleModulePath. It still passes (loader matches either way), but it doesn't actually prove cross-OS normalization in that environment.

If you want the assertion to be meaningful regardless of host OS, build the input from a known POSIX-style path:

♻️ Suggested change
-    const fakeModule = { windows: true };
-    const filepath = getFilepath('esm').split(path.posix.sep).join(path.win32.sep);
-
-    setBundleModuleLoader((p) => (p.endsWith('/fixtures/esm') ? fakeModule : undefined));
-
-    const result = await importModule(filepath);
-    assert.deepEqual(result, fakeModule);
+    const fakeModule = { windows: true };
+    // Force a Windows-style input regardless of host OS.
+    const filepath = getFilepath('esm').split(/[\\/]/).join(path.win32.sep);
+
+    setBundleModuleLoader((p) => (p.endsWith('/fixtures/esm') ? fakeModule : undefined));
+
+    const result = await importModule(filepath);
+    assert.deepEqual(result, fakeModule);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/utils/test/bundle-import.test.ts` around lines 81 - 89, The test
currently builds a "Windows-style" filepath by swapping posix separators which
is a no-op on Windows hosts, so instead force a POSIX input first and then
convert it to Windows form so normalizeBundleModulePath is actually exercised;
specifically, derive a guaranteed POSIX path from getFilepath('esm') (e.g.
replace any win32 separators with posix ones) and then create filepath by
replacing posix separators with path.win32.sep, keeping the rest of the test
(setBundleModuleLoader, importModule, fakeModule) unchanged so the loader lookup
uses the normalized path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/utils/test/bundle-import.test.ts`:
- Around line 73-79: Rename the test title to accurately reflect what's being
verified: that the bundle loader can serve specifiers that don't exist on disk
rather than claiming it "short-circuits importResolve". Update the description
in the test containing setBundleModuleLoader, importModule and fakeModule to
something like "loader serves specifiers that need not exist on disk" (or
similar) so it references setBundleModuleLoader and importModule instead of
importResolve.

---

Nitpick comments:
In `@packages/utils/test/bundle-import.test.ts`:
- Around line 9-12: Add a defensive beforeEach that calls
setBundleModuleLoader(undefined) to mirror the existing afterEach cleanup so
tests cannot rely on prior worker state; update the vitest import to include
beforeEach (alongside afterEach) and ensure setBundleModuleLoader is referenced
in the new beforeEach, which will also reset
globalThis.__EGG_BUNDLE_MODULE_LOADER__ before each test.
- Around line 81-89: The test currently builds a "Windows-style" filepath by
swapping posix separators which is a no-op on Windows hosts, so instead force a
POSIX input first and then convert it to Windows form so
normalizeBundleModulePath is actually exercised; specifically, derive a
guaranteed POSIX path from getFilepath('esm') (e.g. replace any win32 separators
with posix ones) and then create filepath by replacing posix separators with
path.win32.sep, keeping the rest of the test (setBundleModuleLoader,
importModule, fakeModule) unchanged so the loader lookup uses the normalized
path.
🪄 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: 042e76fe-6676-4036-a3a2-5f4a1da5adc5

📥 Commits

Reviewing files that changed from the base of the PR and between 7e36762 and 93ce08e.

📒 Files selected for processing (1)
  • packages/utils/test/bundle-import.test.ts

Comment thread packages/utils/test/bundle-import.test.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a runtime hook in @eggjs/utils to let bundled Egg applications intercept importModule() calls via a shared globalThis loader, enabling in-bundle module maps to bypass filesystem resolution.

Changes:

  • Introduces BundleModuleLoader and setBundleModuleLoader() and wires the hook into importModule() before importResolve().
  • Adds comprehensive unit tests for bundle interception, default-unwrapping behavior, fallback behavior, and Windows path normalization.
  • Updates an Egg cluster test to use raw HTTP requests to exercise invalid-path parsing behavior.

Reviewed changes

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

File Description
packages/utils/src/import.ts Adds globalThis-backed bundle module loader hook and integrates it into importModule() resolution flow.
packages/utils/test/bundle-import.test.ts New tests covering bundle loader interception, fallthrough, importDefaultOnly, __esModule unwrapping, and path normalization.
packages/utils/test/snapshots/index.test.ts.snap Snapshot update to reflect new exported API.
packages/egg/test/cluster1/app_worker.test.ts Switches broken-HTTP-packet test to send raw requests to preserve unencoded paths.

@killagu
Copy link
Copy Markdown
Contributor Author

killagu commented Apr 25, 2026

Addressed the latest review feedback:

  • Hardened requestRawPath() with a targeted timeout and single-settle handling.
  • Normalized raw HTTP response line endings before comparing the default bad-request body.
  • Renamed the virtual bundle-loader test to avoid implying importResolve() is directly exercised.

Local checks run:

  • pnpm exec vitest run packages/egg/test/cluster1/app_worker.test.ts --bail 1 --retry 2 --testTimeout 20000 --hookTimeout 20000
  • pnpm exec vitest run packages/utils/test/bundle-import.test.ts --bail 1 --retry 2 --testTimeout 20000 --hookTimeout 20000
  • pnpm --filter=egg typecheck
  • pnpm --filter=@eggjs/utils typecheck
  • pnpm exec oxfmt --check packages/egg/test/cluster1/app_worker.test.ts
  • pnpm exec oxlint packages/egg/test/cluster1/app_worker.test.ts

Note: root pnpm run ci currently exits before tests because ut run clean --workspaces --if-present reports ERROR Script 'clean' not found in package.json; targeted checks above passed.

Copilot AI review requested due to automatic review settings April 26, 2026 02:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a runtime hook to @eggjs/utils to let bundled Egg apps intercept importModule() and serve modules from an in-bundle map (via a globalThis-stored loader), which is a prerequisite for the upcoming bundler work.

Changes:

  • Add setBundleModuleLoader() and a BundleModuleLoader type, storing the loader on globalThis.__EGG_BUNDLE_MODULE_LOADER__ and consulting it inside importModule() before importResolve().
  • Add Vitest coverage for bundle interception, fallthrough behavior, default-unwrapping, virtual specifiers, and Windows path normalization.
  • Stabilize an Egg cluster test by normalizing CRLF differences and improving raw socket request timeout handling.

Reviewed changes

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

File Description
packages/utils/src/import.ts Introduces the bundle module loader hook and integrates it into importModule() with existing default-unwrapping behavior.
packages/utils/test/bundle-import.test.ts Adds dedicated tests covering bundle loader behavior and edge cases.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.
packages/egg/test/cluster1/app_worker.test.ts Normalizes newline differences in assertions and refines raw socket request settling/timeout behavior to reduce flakiness.

Copilot AI review requested due to automatic review settings April 26, 2026 11:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a runtime hook to @eggjs/utils so bundled Egg applications can intercept importModule() resolution via a shared globalThis loader, enabling in-bundle module maps and avoiding filesystem lookups during boot.

Changes:

  • Add setBundleModuleLoader() and consult globalThis.__EGG_BUNDLE_MODULE_LOADER__ inside importModule() (with default-unwrapping + importDefaultOnly handling).
  • Add Vitest coverage for bundle-loader interception, fallback behavior, and Windows path normalization; update export snapshot.
  • Adjust an Egg cluster test helper to make raw HTTP request assertions and socket settling behavior more robust across platforms.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/utils/src/import.ts Adds the bundle module loader hook and early interception path in importModule().
packages/utils/test/bundle-import.test.ts New tests validating loader interception, fallback, default-unwrapping, and path normalization.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.
packages/utils/tsconfig.json Includes cross-package global typing file for __EGG_BUNDLE_MODULE_LOADER__.
packages/core/src/global.d.ts Introduces global type for __EGG_BUNDLE_MODULE_LOADER__.
packages/egg/test/cluster1/app_worker.test.ts Normalizes CRLF in assertions and refactors raw socket request settling.

Comment thread packages/utils/tsconfig.json Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a runtime hook to @eggjs/utils so bundled Egg applications can intercept importModule() and serve modules from an in-bundle map via a shared globalThis loader.

Changes:

  • Add setBundleModuleLoader() API and globalThis.__EGG_BUNDLE_MODULE_LOADER__ fast-path in importModule().
  • Add Vitest coverage for bundle-loader interception, fallthrough behavior, default-unwrapping, and Windows path normalization.
  • Update utils export snapshot and add @eggjs/typings dependency to provide the global typing.

Reviewed changes

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

Show a summary per file
File Description
packages/utils/src/import.ts Introduces setBundleModuleLoader() and checks the global loader before resolving/importing modules.
packages/utils/test/bundle-import.test.ts Adds new tests validating bundle-loader behavior and edge cases.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.
packages/utils/package.json Adds @eggjs/typings workspace dependency (required for @eggjs/typings/global import).
packages/egg/test/cluster1/app_worker.test.ts Refactors rawRequest() settling/timeout handling to be single-settle and include partial response on timeout.

@killagu killagu force-pushed the split/01-utils-bundle-module-loader branch from dea4f1b to d2912aa Compare April 26, 2026 15:01
Copilot AI review requested due to automatic review settings April 26, 2026 15:27
@killagu killagu force-pushed the split/01-utils-bundle-module-loader branch from d2912aa to e028bea Compare April 26, 2026 15:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new runtime hook in @eggjs/utils to let bundled Egg runtimes intercept importModule() via a shared globalThis loader, enabling bundled module maps to short-circuit filesystem/module resolution.

Changes:

  • Add setBundleModuleLoader() and globalThis.__EGG_BUNDLE_MODULE_LOADER__ lookup path (with Windows→POSIX separator normalization) to importModule().
  • Add Vitest coverage for bundle-loader interception, fallthrough, default-unwrapping, and virtual specifiers; update export snapshot.
  • Add @eggjs/typings dependency and adjust an Egg cluster test helper (rawRequest) for more robust timeout/error settling.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/utils/src/import.ts Introduces the global bundle module loader hook and wires it into importModule() before normal resolution.
packages/utils/package.json Adds @eggjs/typings as a dependency to source/export the BundleModuleLoader type.
packages/utils/test/bundle-import.test.ts New tests validating bundle loader behavior, fallthrough, and normalization.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.
packages/egg/test/cluster1/app_worker.test.ts Improves test stability: longer beforeAll timeout and safer raw socket request settling/timeout handling.

Comment thread packages/utils/src/import.ts
@killagu killagu force-pushed the split/01-utils-bundle-module-loader branch from e028bea to 87414b5 Compare April 26, 2026 15:54
Copilot AI review requested due to automatic review settings April 26, 2026 16:19
@killagu killagu force-pushed the split/01-utils-bundle-module-loader branch from 87414b5 to 4800381 Compare April 26, 2026 16:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a runtime hook to @eggjs/utils so bundled Egg apps can intercept importModule() resolution via a shared globalThis loader, enabling in-bundle module maps and reducing filesystem-based lookups.

Changes:

  • Add setBundleModuleLoader(loader) and invoke the registered loader before normal importResolve() in importModule().
  • Add new Vitest coverage for bundle-loader interception, fall-through behavior, default-unwrapping semantics, and path normalization.
  • Add a small test hardening change in packages/egg around raw socket requests/timeouts.

Reviewed changes

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

Show a summary per file
File Description
packages/utils/src/import.ts Introduces setBundleModuleLoader (globalThis-backed) and integrates it into importModule() with existing default-unwrapping behavior.
packages/utils/package.json Adds @eggjs/typings dependency for the exported BundleModuleLoader type.
packages/utils/test/bundle-import.test.ts New tests validating interception, fall-through, importDefaultOnly, virtual specifiers, and Windows-path normalization.
packages/utils/test/import.test.ts Extends existing importModule() tests to cover __esModule double-default + importDefaultOnly behavior.
packages/utils/test/fixtures/esm/es-module-default.js New fixture exercising __esModule double-default export shape.
packages/utils/test/snapshots/index.test.ts.snap Updates export snapshot to include setBundleModuleLoader.
packages/egg/test/cluster1/app_worker.test.ts Adjusts test timeout and makes raw socket request helper settle deterministically with clearer timeout rejection.

@killagu killagu merged commit 57507ea into eggjs:next Apr 26, 2026
25 checks passed
killagu added a commit that referenced this pull request May 1, 2026
Adds static `ManifestStore.fromBundle(data, baseDir)` that creates a
store from pre-validated bundled manifest data, skipping lockfile/config
fingerprint checks.

Part of #5863 split. Tracking: #5871. Stacked on: #5867 (A1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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