Skip to content

feat(bundler): support app-supplied pack aliases#5914

Merged
killagu merged 4 commits into
nextfrom
agent/egg-dev/0fb70fdf
May 3, 2026
Merged

feat(bundler): support app-supplied pack aliases#5914
killagu merged 4 commits into
nextfrom
agent/egg-dev/0fb70fdf

Conversation

@killagu
Copy link
Copy Markdown
Contributor

@killagu killagu commented May 2, 2026

Summary

  • Replace the framework-specific supports-hyperlinks/supports-color workaround with generic app-supplied pack.resolve.alias support.
  • Add repeatable egg-bin bundle --pack-alias <specifier>=<target> wiring so applications can provide dependency-specific pack aliases at the app layer.
  • Resolve dot-relative alias targets from the application base directory, and document both CLI and programmatic configuration.
  • Cover CLI -> Bundler -> PackRunner alias forwarding with tests.

Verification

  • pnpm vitest run test/Bundler.test.ts test/PackRunner.test.ts from tools/egg-bundler
  • pnpm vitest run test/commands/bundle.test.ts from tools/egg-bin
  • pnpm --filter @eggjs/egg-bundler typecheck
  • pnpm --filter @eggjs/bin typecheck
  • pnpm --filter @eggjs/egg-bundler lint
  • pnpm --filter @eggjs/egg-bundler test

Notes

  • The dependency-specific alias now belongs to the application command/config layer instead of being hardcoded in the framework bundler.
  • --pack-alias keeps package-style targets as-is; only dot-relative targets such as ./target.js and ../target.js are resolved from the application base directory.

Copilot AI review requested due to automatic review settings May 2, 2026 17:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

Adds optional pack resolve alias support: new PackRunnerResolveConfig and pack.resolve.alias option, CLI flag --pack-alias to supply aliases, wiring from CLI → egg-bin → Bundler → PackRunner → forwarded into @utoo/pack build config when non-empty. Tests and docs updated.

Changes

Pack resolve alias wiring

Layer / File(s) Summary
Data Shape / Types
tools/egg-bundler/src/lib/PackRunner.ts, tools/egg-bundler/src/index.ts
Adds exported PackRunnerResolveConfig { alias?: Readonly<Record<string,string>> } and extends PackRunnerOptions and BundlerPackConfig with optional resolve field.
Core Implementation
tools/egg-bundler/src/lib/PackRunner.ts
PackRunner.run() builds a shallow resolve config (#buildResolveConfig) and injects it into the @utoo/pack build config as resolve when aliases exist.
Bundler Wiring
tools/egg-bundler/src/lib/Bundler.ts
Bundler.run() passes pack?.resolve into PackRunner constructor options.
CLI Parsing / Invocation
tools/egg-bin/src/commands/bundle.ts
Adds repeatable --pack-alias <specifier>=<target> flag and parsePackAliases(values, baseDir) to validate/resolve entries (dot-relative targets resolved against baseDir); injects parsed aliases into pack.resolve.alias when present.
Tests
tools/egg-bundler/test/PackRunner.test.ts, tools/egg-bundler/test/Bundler.test.ts, tools/egg-bin/test/commands/bundle.test.ts
Updated/added tests to assert config.resolve is undefined by default and that provided aliases are forwarded and resolved through CLI → bundler → pack build config.
Documentation / Examples
tools/egg-bin/README.md, tools/egg-bundler/README.md
Docs and examples updated to document --pack-alias <specifier>=<target> usage and pack.resolve.alias configuration.

Sequence Diagram

sequenceDiagram
  participant CLI as egg-bin CLI
  participant Bundler as Bundler
  participant PackRunner as PackRunner
  participant Pack as "@utoo/pack" (buildFunc)

  CLI->>Bundler: parse `--pack-alias` → alias map (resolve relative targets)
  Bundler->>PackRunner: construct with options (rootPath, mode, buildFunc, resolve.alias)
  PackRunner->>Pack: call buildFunc({ ..., resolve: { alias } }) when non-empty
  Pack->>PackRunner: return build result
  PackRunner->>Bundler: return build output
  Bundler->>CLI: complete bundle
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • fengmk2

Poem

🐰
I nibble on flags and map a path so neat,
Turning specifiers to targets beneath my feet.
From CLI to pack I thread each alias fine,
Hop—now builds find modules right on time. 🎋

🚥 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(bundler): support app-supplied pack aliases' directly and clearly describes the main implementation feature across the changeset: adding support for application-supplied webpack/pack resolve aliases.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/egg-dev/0fb70fdf

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 2, 2026

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: 54a8592
Status: ✅  Deploy successful!
Preview URL: https://d08344dc.egg-cci.pages.dev
Branch Preview URL: https://agent-egg-dev-0fb70fdf.egg-cci.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 2, 2026

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 54a8592
Status: ✅  Deploy successful!
Preview URL: https://80f33471.egg-v3.pages.dev
Branch Preview URL: https://agent-egg-dev-0fb70fdf.egg-v3.pages.dev

View logs

@codecov
Copy link
Copy Markdown

codecov Bot commented May 2, 2026

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 85.04%. Comparing base (30514fa) to head (54a8592).
⚠️ Report is 4 commits behind head on next.

Files with missing lines Patch % Lines
tools/egg-bin/src/commands/bundle.ts 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #5914      +/-   ##
==========================================
+ Coverage   85.03%   85.04%   +0.01%     
==========================================
  Files         667      667              
  Lines       19110    19123      +13     
  Branches     3719     3723       +4     
==========================================
+ Hits        16250    16263      +13     
  Misses       2467     2467              
  Partials      393      393              

☔ 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

@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 (2)
tools/egg-bundler/test/PackRunner.test.ts (1)

112-141: ⚡ Quick win

Cover the hoisted node_modules layout too.

This fixture only proves the direct supports-hyperlinks/node_modules/supports-color case. The new resolver’s easy-to-break branch is the upward walk to a parent node_modules, which is the layout the pnpm/hoisted reproduction is more likely to hit.

Fixture tweak that exercises the upward walk
     const supportsHyperlinksDir = path.join(tmpDir, 'node_modules', 'supports-hyperlinks');
-    const supportsColorDir = path.join(supportsHyperlinksDir, 'node_modules', 'supports-color');
+    const supportsColorDir = path.join(tmpDir, 'node_modules', 'supports-color');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/egg-bundler/test/PackRunner.test.ts` around lines 112 - 141, The test
'aliases supports-color to its node export when bundled through
supports-hyperlinks' only creates supports-color under
supports-hyperlinks/node_modules; to cover the hoisted layout also create a
hoisted package in the project-level node_modules and its files so the
resolver's upward-walk code is exercised. Concretely: in the test add a
supportsColorHoistedDir = path.join(tmpDir, 'node_modules', 'supports-color'),
write a package.json and index.js/index.d.ts (matching the exports shape used)
into that hoisted dir (and ensure supports-hyperlinks package.json still
exists), then run makeRunner({ buildFunc }).run() and assert the alias resolves
to the hoisted path (path.join(tmpDir, 'node_modules', 'supports-color',
'index.js')) so the upward-walk branch is exercised; reference the existing
symbols buildFunc, makeRunner, supportsHyperlinksDir, supportsColorDir, and the
expect on config.resolve.alias to locate where to change the fixture.
tools/egg-bundler/src/lib/PackRunner.ts (1)

185-199: Handle nested condition objects within exports.node.

The node condition can itself contain a conditional object with require, import, or default properties. With shapes like { node: { require: './index.cjs', default: './index.js' } }, the current implementation returns undefined and silently falls back to non-node resolution.

Suggested fix
-  `#resolvePackageTarget`(target: unknown): string | undefined {
+  `#resolvePackageTarget`(target: unknown, inNodeCondition = false): string | undefined {
     if (typeof target === 'string') return target;
     if (Array.isArray(target)) {
       for (const item of target) {
-        const resolved = this.#resolvePackageTarget(item);
+        const resolved = this.#resolvePackageTarget(item, inNodeCondition);
         if (resolved) return resolved;
       }
       return undefined;
     }
     if (!target || typeof target !== 'object') return undefined;
 
     const map = target as Record<string, unknown>;
-    if (!Object.hasOwn(map, 'node')) return undefined;
-    return this.#resolvePackageTarget(map.node);
+    if (Object.hasOwn(map, 'node')) {
+      return this.#resolvePackageTarget(map.node, true);
+    }
+    if (inNodeCondition) {
+      return this.#resolvePackageTarget(map.require ?? map.default ?? map.import, true);
+    }
+    return undefined;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/egg-bundler/src/lib/PackRunner.ts` around lines 185 - 199, The
`#resolvePackageTarget` currently treats map.node as a scalar or a nested
conditional array but doesn't handle when map.node is itself a conditional
object with keys like require/import/default; update `#resolvePackageTarget` to
detect when map.node is an object containing any of 'require', 'import', or
'default' and recursively resolve in a preferred order (e.g., prefer 'require',
then 'import', then 'default') by calling this.#resolvePackageTarget on the
chosen property value, falling back to other keys if the chosen one returns
undefined; ensure you still handle arrays and other nested condition shapes the
same way so that inputs like { node: { require: './index.cjs', default:
'./index.js' } } return './index.cjs'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/egg-bundler/src/lib/PackRunner.ts`:
- Around line 185-199: The `#resolvePackageTarget` currently treats map.node as a
scalar or a nested conditional array but doesn't handle when map.node is itself
a conditional object with keys like require/import/default; update
`#resolvePackageTarget` to detect when map.node is an object containing any of
'require', 'import', or 'default' and recursively resolve in a preferred order
(e.g., prefer 'require', then 'import', then 'default') by calling
this.#resolvePackageTarget on the chosen property value, falling back to other
keys if the chosen one returns undefined; ensure you still handle arrays and
other nested condition shapes the same way so that inputs like { node: {
require: './index.cjs', default: './index.js' } } return './index.cjs'.

In `@tools/egg-bundler/test/PackRunner.test.ts`:
- Around line 112-141: The test 'aliases supports-color to its node export when
bundled through supports-hyperlinks' only creates supports-color under
supports-hyperlinks/node_modules; to cover the hoisted layout also create a
hoisted package in the project-level node_modules and its files so the
resolver's upward-walk code is exercised. Concretely: in the test add a
supportsColorHoistedDir = path.join(tmpDir, 'node_modules', 'supports-color'),
write a package.json and index.js/index.d.ts (matching the exports shape used)
into that hoisted dir (and ensure supports-hyperlinks package.json still
exists), then run makeRunner({ buildFunc }).run() and assert the alias resolves
to the hoisted path (path.join(tmpDir, 'node_modules', 'supports-color',
'index.js')) so the upward-walk branch is exercised; reference the existing
symbols buildFunc, makeRunner, supportsHyperlinksDir, supportsColorDir, and the
expect on config.resolve.alias to locate where to change the fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 164fbbb7-4027-47b7-80b3-545724dfaff1

📥 Commits

Reviewing files that changed from the base of the PR and between 30514fa and 35803d9.

📒 Files selected for processing (2)
  • tools/egg-bundler/src/lib/PackRunner.ts
  • tools/egg-bundler/test/PackRunner.test.ts

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 mechanism to resolve and alias specific dependencies to their Node.js entry points, addressing an issue where the bundler incorrectly selects browser exports. It includes logic for traversing the file system to find package directories and parsing the exports field in package.json. Feedback was provided to improve the robustness of the export resolution logic by supporting additional Node.js conditions such as require, import, and default as fallbacks.

Comment thread tools/egg-bundler/src/lib/PackRunner.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

Fixes a Node bundling runtime issue where @utoo/pack selects supports-color’s browser/default export (via the supports-hyperlinks -> supports-color chain) instead of the Node export.

Changes:

  • Add conditional resolve.alias injection in PackRunner to point supports-color at its exports.node entry when applicable.
  • Add a unit test covering the alias behavior and assert resolve is otherwise omitted.

Reviewed changes

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

File Description
tools/egg-bundler/src/lib/PackRunner.ts Detects supports-hyperlinks + supports-color and conditionally injects resolve.alias to force the Node export path.
tools/egg-bundler/test/PackRunner.test.ts Adds coverage validating the alias is emitted only when the dependency chain exists.

Comment thread tools/egg-bundler/src/lib/PackRunner.ts Outdated
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)
tools/egg-bundler/src/lib/PackRunner.ts (1)

175-207: 💤 Low value

#resolveExportsNodeEntry: top-level array exports are silently dropped while nested arrays are handled.

Line 177 rejects array-form root exports early and returns undefined:

if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) return undefined;

#resolvePackageTarget handles arrays recursively (lines 187-193), so the gap only affects top-level fallback arrays like "exports": ["./index.cjs", "./index.mjs"]. These are rare and the Node.js spec discourages them at root level, but if a future NODE_CONDITION_ALIAS_DEPS entry targets such a package the alias would be silently skipped rather than resolved.

Consider routing top-level arrays through #resolvePackageTarget for consistency:

♻️ Optional fix
 `#resolveExportsNodeEntry`(exportsField: unknown): string | undefined {
   if (typeof exportsField === 'string') return exportsField;
-  if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) return undefined;
+  if (!exportsField || typeof exportsField !== 'object') return undefined;
+  if (Array.isArray(exportsField)) return this.#resolvePackageTarget(exportsField);

   const map = exportsField as Record<string, unknown>;

For #resolvePackageTarget line 197: the node condition exits early even when it resolves to undefined (e.g. "node": null or a types-only node block). This is correct per the Node.js exports spec — a matched-but-unresolved condition does not fall through to default. In the current workaround this is fine since supports-color's node condition always yields a valid path.

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

In `@tools/egg-bundler/src/lib/PackRunner.ts` around lines 175 - 207, The
top-level array branch in `#resolveExportsNodeEntry` silently returns undefined
for array-form exports; instead allow arrays to be processed by the existing
recursive resolver: change the early-return so that only null/non-objects are
rejected (keep the typeof check) but if Array.isArray(exportsField) (or
generally when it's an object/array) call and return
this.#resolvePackageTarget(exportsField) so root-level arrays like
["./index.cjs","./index.mjs"] are handled consistently by `#resolvePackageTarget`;
leave the current `#resolvePackageTarget` node/condition behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/egg-bundler/src/lib/PackRunner.ts`:
- Around line 175-207: The top-level array branch in `#resolveExportsNodeEntry`
silently returns undefined for array-form exports; instead allow arrays to be
processed by the existing recursive resolver: change the early-return so that
only null/non-objects are rejected (keep the typeof check) but if
Array.isArray(exportsField) (or generally when it's an object/array) call and
return this.#resolvePackageTarget(exportsField) so root-level arrays like
["./index.cjs","./index.mjs"] are handled consistently by `#resolvePackageTarget`;
leave the current `#resolvePackageTarget` node/condition behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24e0dbbf-a759-4629-b777-3e12ffb42b0f

📥 Commits

Reviewing files that changed from the base of the PR and between 35803d9 and beb47e4.

📒 Files selected for processing (2)
  • tools/egg-bundler/src/lib/PackRunner.ts
  • tools/egg-bundler/test/PackRunner.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/egg-bundler/test/PackRunner.test.ts

Copilot AI review requested due to automatic review settings May 3, 2026 02:30
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

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

Comments suppressed due to low confidence (1)

tools/egg-bundler/src/lib/PackRunner.ts:106

  • The PR title/description mention forcing the Node export for the supports-hyperlinks -> supports-color chain, but this PackRunner change only adds pass-through support for application-supplied resolve.alias and does not implement any automatic aliasing for supports-color (nor is it covered by tests in this PR). Either add the intended auto-alias behavior here, or update the PR title/description to reflect the actual change (exposing pack.resolve.alias).
    const resolveConfig = this.#buildResolveConfig(resolve);

    const config = {
      entry: entries.map((e) => ({ name: e.name, import: e.filepath })),
      target: 'node 22',
      platform: 'node',
      mode,
      output: {
        path: outputDir,
        type: 'standalone',
      },
      externals: umdExternals,
      ...(resolveConfig ? { resolve: resolveConfig } : {}),
      optimization: {
        treeShaking: false,
        minify: false,
      },
    };

Comment thread tools/egg-bin/src/commands/bundle.ts
@killagu killagu changed the title fix(bundler): force node export for supports-color feat(bundler): support app-supplied pack aliases May 3, 2026
@killagu killagu merged commit 5baa1ed into next May 3, 2026
26 checks passed
@killagu killagu deleted the agent/egg-dev/0fb70fdf branch May 3, 2026 05:08
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