Skip to content

fix(bundler): rely on @utoo/pack upstream CJS/ESM require interop fix#5981

Merged
killagu merged 1 commit into
nextfrom
fix/bundler-cjs-esm-interop
Jun 23, 2026
Merged

fix(bundler): rely on @utoo/pack upstream CJS/ESM require interop fix#5981
killagu merged 1 commit into
nextfrom
fix/bundler-cjs-esm-interop

Conversation

@killagu

@killagu killagu commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Background

A bundled egg app crashed at runtime with <path>/tsconfig.json is malformed JSON5.parse is not a function, and only started if the user passed --pack-alias json5=<lib/index.js> (EGG-69).

Root cause: @utoo/pack (Turbopack, target: node) resolved the CJS require('json5') inside tsconfig-paths to json5's ESM module entry (dist/index.mjs, default-only). commonJsRequire returned the ESM namespace { __esModule: true, default: JSON5 }, so JSON5.parse was undefined. Node would instead resolve such dual packages to their CommonJS main.

This is already fixed upstream

@utoo/pack >= 1.4.16 fixes the root cause (utooland/utoo#3185): a CJS require() of such a dual package now resolves to its CommonJS main, matching Node's own CommonJS resolution. The real fix lives upstream; this PR does not fix anything itself.

An earlier version of this PR carried an in-repo workaround (a post-build patch of _turbopack__runtime.js). That workaround is now removed in favor of the upstream fix.

What this PR actually does

This is hygiene, not a functional fix:

  • Raise the @utoo/pack floor: catalog range ^1.2.7^1.4.16, so the fixed version is the documented minimum and nobody can pin a pre-fix version. (The repo ships no lockfile, so fresh installs already resolve to the latest 1.4.x; this just makes the requirement explicit.)
  • Add a regression guard: tools/egg-bundler/test/cjsEsmInterop.realbuild.test.ts runs a real @utoo/pack build over a json5-shaped dual fixture (main + module, ESM exports only default, no exports field) required from authored CJS via a named member, then executes the bundled worker.js. It prints the CJS implementation's output (cjs:ok) with no crash, proving require('pkg').member resolves like Node. A future @utoo/pack regression of that resolution fails this test.

Test evidence

cjsEsmInterop.realbuild.test.ts passes against @utoo/pack 1.4.16; the same fixture crashes (pkg.parse is not a function) on 1.4.14, confirming the test exercises the real behavior.

Note: the public npm registry has @utoo/pack@1.4.16; some internal mirrors may still lag at 1.4.14 (no fix) until they sync.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed CommonJS/ESM interoperability issue in the bundler that caused runtime crashes when resolving dual-entry packages.
  • Tests

    • Added regression test to verify correct CommonJS/ESM package resolution behavior in the bundler.
  • Chores

    • Updated bundler dependency to address the interoperability issue.

Copilot AI review requested due to automatic review settings June 20, 2026 16:06

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c835e207-e6a6-414c-a613-2b2f7d463eed

📥 Commits

Reviewing files that changed from the base of the PR and between e0f90c1 and dd53171.

📒 Files selected for processing (3)
  • pnpm-workspace.yaml
  • tools/egg-bundler/test/cjs-esm-interop.realbuild.test.ts
  • wiki/log.md
✅ Files skipped from review due to trivial changes (2)
  • pnpm-workspace.yaml
  • wiki/log.md

📝 Walkthrough

Walkthrough

Bumps the @utoo/pack catalog dependency from ^1.2.7 to ^1.4.16 and adds @utoo/* to the minimumReleaseAgeExclude list. Removes the prior _turbopack__runtime.js default-unwrapping workaround in favor of the upstream fix. Adds a real-build Vitest regression test that verifies require() of a dual CJS/ESM package resolves to the CommonJS entry. Logs the fix in wiki/log.md as EGG-69.

Changes

EGG-69: Upstream @utoo/pack CJS/ESM Interop Fix and Regression Test

Layer / File(s) Summary
@utoo/pack version bump, exclusion rule, and changelog
pnpm-workspace.yaml, wiki/log.md
Bumps @utoo/pack from ^1.2.7 to ^1.4.16 in the pnpm catalog; adds @utoo/* to minimumReleaseAgeExclude; records the EGG-69 fix and removal of the prior _turbopack__runtime.js workaround in the wiki changelog.
Real-build regression test for dual-package CJS require
tools/egg-bundler/test/cjs-esm-interop.realbuild.test.ts
Adds a full regression test that stubs ManifestLoader, ExternalsResolver, and EntryGenerator, writes a synthetic dual CJS/ESM fixture package, runs the real bundle(), executes the output worker.js with Node, and asserts stdout === "cjs:ok" to confirm the named parse member is reachable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • gxkl
  • akitaSummer
  • jerryliang64

Poem

🐇 A bundler once tripped on a dual-package snare,
ESM crept in where CJS should be there.
JSON5.parse is not a function — oh dear!
But ^1.4.16 made the crash disappear.
Now cjs:ok rings out loud and clear! 🥚

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: relying on an upstream @utoo/pack fix for CJS/ESM require interop instead of maintaining a local workaround.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bundler-cjs-esm-interop

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.

@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 a post-processing patch to the emitted Turbopack runtime in egg-bundler to resolve CJS/ESM require interop issues (EGG-69), specifically unwrapping genuine ESM-only-default modules so that CommonJS require calls can access their members correctly. It also adds comprehensive unit and real-build regression tests. The reviewer suggested making the ESM-only-default check more robust by allowing __esModule to be enumerable when checking for other keys.

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.

Comment thread tools/egg-bundler/src/lib/Bundler.ts Outdated
Comment on lines +76 to +79
const keys = Object.keys(moduleExports);
if (keys.length === 1 && keys[0] === 'default') {
return moduleExports.default;
}

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.

medium

To make the ESM-only-default check more robust, consider allowing __esModule to be enumerable. In some transpiled or custom environments, __esModule might be defined as an enumerable property, which would cause keys.length === 1 to fail even if default is the only actual export. Checking that there are no other keys besides default and __esModule is safer and more defensive.

Suggested change
const keys = Object.keys(moduleExports);
if (keys.length === 1 && keys[0] === 'default') {
return moduleExports.default;
}
const keys = Object.keys(moduleExports);
const hasOtherKeys = keys.some(key => key !== 'default' && key !== '__esModule');
if (!hasOtherKeys) {
return moduleExports.default;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — applied in 8175d2b8. The unwrap is now gated on "no own key other than default/__esModule" instead of keys.length === 1, so it stays correct even if a transpiler makes __esModule enumerable. Added a regression test covering an ESM-only-default module with an enumerable __esModule marker. (Note: in the Turbopack runtime __esModule is defined non-enumerable, so this is purely defensive.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No longer applicable. The PR has been reworked: the root cause is fixed upstream in @utoo/pack >= 1.4.16 (utooland/utoo#3185), so the in-repo _turbopack__runtime.js patch this comment refers to has been removed entirely. The defensive __esModule handling is therefore moot — there is no longer any runtime unwrap code in the bundler.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: dd53171
Status: ✅  Deploy successful!
Preview URL: https://701123e3.egg-cci.pages.dev
Branch Preview URL: https://fix-bundler-cjs-esm-interop.egg-cci.pages.dev

View logs

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.91%. Comparing base (4f49d4b) to head (dd53171).
⚠️ Report is 5 commits behind head on next.

Additional details and impacted files
@@            Coverage Diff             @@
##             next    #5981      +/-   ##
==========================================
+ Coverage   85.59%   85.91%   +0.32%     
==========================================
  Files         669      669              
  Lines       19892    19907      +15     
  Branches     3942     3949       +7     
==========================================
+ Hits        17026    17104      +78     
+ Misses       2478     2425      -53     
+ Partials      388      378      -10     

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: dd53171
Status: ✅  Deploy successful!
Preview URL: https://4b77fa63.egg-v3.pages.dev
Branch Preview URL: https://fix-bundler-cjs-esm-interop.egg-v3.pages.dev

View logs

@killagu killagu force-pushed the fix/bundler-cjs-esm-interop branch from d3cd2d6 to 95de624 Compare June 22, 2026 13:14
@killagu killagu changed the title fix(bundler): interop CJS require of ESM-only-default deps fix(bundler): rely on @utoo/pack upstream CJS/ESM require interop fix Jun 22, 2026
@socket-security

socket-security Bot commented Jun 22, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@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

🤖 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 `@tools/egg-bundler/test/cjsEsmInterop.realbuild.test.ts`:
- Line 1: The test file cjsEsmInterop.realbuild.test.ts uses camelCase naming
which violates the coding guidelines requiring all file names to use lowercase
with hyphens. Rename the file from cjsEsmInterop.realbuild.test.ts to
cjs-esm-interop.realbuild.test.ts to comply with the lowercase-with-hyphens
convention. This is a file-level change and does not require code modifications,
just the file name update in your version control system.
🪄 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: 2d4082ab-b357-4e09-a980-415902568292

📥 Commits

Reviewing files that changed from the base of the PR and between d3cd2d6 and 95de624.

📒 Files selected for processing (3)
  • pnpm-workspace.yaml
  • tools/egg-bundler/test/cjsEsmInterop.realbuild.test.ts
  • wiki/log.md
✅ Files skipped from review due to trivial changes (1)
  • wiki/log.md

Comment thread tools/egg-bundler/test/cjs-esm-interop.realbuild.test.ts
@killagu killagu force-pushed the fix/bundler-cjs-esm-interop branch from 95de624 to e0f90c1 Compare June 22, 2026 13:21
Copilot AI review requested due to automatic review settings June 22, 2026 13:21

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The EGG-69 crash (`JSON5.parse is not a function` in a bundled app, where
@utoo/pack resolved a CJS `require('json5')` to json5's ESM default-only
`module` entry) is now fixed upstream in @utoo/pack (utooland/utoo#3185):
>= 1.4.16 resolves a CJS `require()` of such a dual package to its CommonJS
`main`, matching Node's own CommonJS resolution.

Drop the in-repo workaround (the post-build `_turbopack__runtime.js` patch
that unwrapped the lone `default`) and its synthetic-runtime tests, and bump
the `@utoo/pack` catalog range to `^1.4.16`. Add `@utoo/*` to
`minimumReleaseAgeExclude` so the freshly-published fixed version (and its
platform binaries) installs without waiting out the 24h maturity gate, matching
how the rest of the first-party toolchain is treated.

Keep `cjsEsmInterop.realbuild.test.ts` as a real @utoo/pack build regression
guard over a json5-shaped dual fixture (`main`+`module`, ESM exports only
`default`) required from authored CJS via a named member: the bundled worker
runs the CJS implementation with no crash, proving `require('pkg').member`
resolves like Node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@killagu killagu force-pushed the fix/bundler-cjs-esm-interop branch from e0f90c1 to dd53171 Compare June 22, 2026 13:53
@killagu killagu merged commit cf3230e into next Jun 23, 2026
26 checks passed
@killagu killagu deleted the fix/bundler-cjs-esm-interop branch June 23, 2026 03:04
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