Skip to content

fix(cjs): build a CommonJS re-export of an ES module inside a cycle - #23090

Merged
alexander-akait merged 1 commit into
webpack:mainfrom
zerotohero3:fix/commonjs-reexport-esm-cycle
Nov 3, 2026
Merged

alexander-akait merged 1 commit into
webpack:mainfrom
zerotohero3:fix/commonjs-reexport-esm-cycle

Conversation

@zerotohero3

@zerotohero3 zerotohero3 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #23088.

Since #22276 (5.112.0), a CommonJS re-export of an ES module owns a nested exports info (for __esModule) that also redirects to the required module's. In a cycle this breaks production builds:

Cycle Before After
export * as of a CommonJS module re-exporting the namespace RangeError: Maximum call stack size exceeded builds
ES module requiring a CommonJS module that re-exports it Cannot get final name for export '__esModule' builds
  • FlagDependencyExportsPlugin merged nested names through the redirect, marking __esModule provided on the target module. It now uses getOwnExportInfo.
  • ExportsInfo#setHasUseInfo followed redirects into each other's module forever. It now stops at a redirect, as _resetUsedExports does.

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes: the namespace-reexport-cycle and reexport-esm-cycle cases under cjs-tree-shaking. Each fails in both config suites without its part of the fix.

Does this PR introduce a breaking change?

No.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

n/a

Use of AI

No.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed build failures involving circular dependencies between CommonJS modules and ES modules. These builds now correctly handle re-exports and preserve expected namespace and module metadata, including when a CommonJS module re-exports an ES module that references it in return. This improves reliability for projects that use these mixed-module patterns.

Since webpack#22276 the re-export declares a nested `__esModule`, so its export
owns a nested exports info that redirects to the required module's. In a
cycle, merging it through the redirect marked the target as providing
`__esModule`, and two such infos made `setHasUseInfo` recurse forever.

Merge a spec's names into the nested info it owns, and stop
`setHasUseInfo` at a redirect, as `_resetUsedExports` does.
@linux-foundation-easycla

linux-foundation-easycla Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: zerotohero3 / name: Victor Hugo (07c80d6)

@changeset-bot

changeset-bot Bot commented Nov 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 07c80d6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the area: optimization Tree-shaking, splitChunks, concatenation, ids (lib/optimize, lib/ids) label Nov 2, 2026
@coderabbitai coderabbitai Bot added the regression Worked in a previous release; pair with the Affected version field label Nov 2, 2026
@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 05eb6bd and 07c80d6.

Files selected for processing: 14
  • .changeset/050-commonjs-reexport-esm-cycle.md
  • lib/graph/ExportsInfo.js
  • lib/optimize/FlagDependencyExportsPlugin.js
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/binding-namespace.js
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/binding-reexport.cjs
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/index.js
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/namespace.js
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/reexport.cjs
  • test/configCases/cjs-tree-shaking/namespace-reexport-cycle/webpack.config.js
  • test/configCases/cjs-tree-shaking/reexport-esm-cycle/index.js
  • test/configCases/cjs-tree-shaking/reexport-esm-cycle/module.js
  • test/configCases/cjs-tree-shaking/reexport-esm-cycle/reexport.cjs
  • test/configCases/cjs-tree-shaking/reexport-esm-cycle/require-reexport.js
  • test/configCases/cjs-tree-shaking/reexport-esm-cycle/webpack.config.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The changes adjust export-use initialization and export merging for cyclic CommonJS and ES module re-exports. New configuration tests cover namespace re-export cycles and a cycle that checks the exported value and __esModule flag.

Changes: Cyclic re-export handling

Layer / File(s) Summary
Namespace re-export cycle handling
lib/graph/ExportsInfo.js, test/configCases/cjs-tree-shaking/namespace-reexport-cycle/*
setHasUseInfo no longer follows a redirect when initializing use information. Regression cases check direct and binding-mediated namespace cycles, including exported keys and the back-reference.
ES module export merge handling
lib/optimize/FlagDependencyExportsPlugin.js, test/configCases/cjs-tree-shaking/reexport-esm-cycle/*, .changeset/050-commonjs-reexport-esm-cycle.md
mergeExports now uses the module’s own export info for each export name. The regression case checks the exported value and the CommonJS re-export’s __esModule flag. A patch changeset describes the cycle build failure.

Suggested labels: area: optimization, regression

Suggested reviewers: alexander-akait

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · up to 07c80

The change fixes production-build failures for cyclic CommonJS and ES module re-exports, and adds regression tests for both reported cases. No remaining concrete risk was identified in the supplied context.

Pre-merge checks: Passed 3 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Title check Inconclusive The title uses the required Conventional Commit form, fix(cjs): subject, and accurately describes the CommonJS/ES module cycle fix. The branch prefix is not provided, so the required type-to-branch-… Provide the branch name or confirm that its prefix is fix.
Passed Passed checks: 3 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check Passed For #23088, ExportsInfo.setHasUseInfo now stops before _redirectTo when a redirect exists, which prevents recursive use-info initialization in redirect cycles. FlagDependencyExportsPlugin now ca…
Out of Scope Changes check Passed The changes remain within #23088. The two library changes address the reported export-info and redirect-cycle failures. The added config cases reproduce both issue patterns. The changeset documents th…
Full details: Linked Issues check

Explanation

For #23088, ExportsInfo.setHasUseInfo now stops before _redirectTo when a redirect exists, which prevents recursive use-info initialization in redirect cycles. FlagDependencyExportsPlugin now calls getOwnExportInfo(name), which prevents nested export analysis from marking the redirected target as providing __esModule. The new production config cases cover both cyclic patterns. They assert the namespace identity and __esModule === true, and assert the 1:true re-export result.

Full details: Out of Scope Changes check

Explanation

The changes remain within #23088. The two library changes address the reported export-info and redirect-cycle failures. The added config cases reproduce both issue patterns. The changeset documents the fix and does not add unrelated behavior.

Full details: Title check

Explanation

The title uses the required Conventional Commit form, fix(cjs): subject, and accurately describes the CommonJS/ES module cycle fix. The branch prefix is not provided, so the required type-to-branch-prefix match cannot be verified.

Resolution

Provide the branch name or confirm that its prefix is fix.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Reported by Module Loading #95 for 07c80d6, computed by this pull request's own code.

Module loading

How many files a fresh Node.js process loads (require or import) to do each thing below; a change from the pull request's base is marked 🔴 when it grew and 🟢 when it shrank. Every file is startup time, so fewer is better. webpack files are this package's own (lib/, hot/); node_modules files are its dependencies'.

What the process does webpack files node_modules files
require("webpack") 2 0
import "webpack" 2 0
JavaScript syntax 11 0
CSS syntax 12 0
HTML syntax 11 0
XML syntax 11 0
JavaScript minifier 12 0
CSS minifier 14 0
HTML minifier 19 0
XML minifier 14 0
JSON minifier 1 0
development build 438 103
production build 445 133
watch with HMR, one rebuild 449 110

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Reported by Types Coverage #2851 for 07c80d6, computed by this pull request's own code.

Types Coverage

Typed lines: 99.50% (167113 of 167945)

Escape hatch Base Head Change
EXPECTED_ANY 59 59 —
EXPECTED_OBJECT 32 32 —
EXPECTED_FUNCTION 1 1 —
unknown 90 90 —
inferred any 0 0 —

Escape hatches count the raw types they stand for too: any, object and Function. Inferred any counts the identifiers whose type TypeScript infers as any though no JSDoc names it, and the new or generic calls whose type arguments it fills with any, such as const map = new Map().

This pull request adds or removes no escape-hatch type or inferred any.

107 file(s) in lib/, hot/ and declarations/ still use an escape hatch, infer `any` or have untyped lines
File Typed lines EXPECTED_ANY EXPECTED_OBJECT EXPECTED_FUNCTION unknown inferred any
declarations/WebpackOptions.ts 99.94% 15
lib/javascript/syntax-parser.js 99.96% 14
lib/hmr/JavascriptHotModuleReplacement.runtime.js 100.00% 2 7
lib/util/cleverMerge.js 100.00% 1 8
lib/javascript/syntax-printer.js 99.99% 1 6 1
lib/serialization/ObjectMiddleware.js 99.79% 3 4
lib/Compiler.js 100.00% 2 4
lib/hmr/HotModuleReplacement.runtime.js 100.00% 1 5
lib/serialization/SerializerMiddleware.js 100.00% 3 3
lib/util/SourceProcessor.js 100.00% 6
lib/Compilation.js 99.77% 5
lib/define/DefinePlugin.js 100.00% 1 4
lib/loaders/LoaderOptionsPlugin.js 90.63% 5
lib/module/NormalModule.js 99.54% 4 1
lib/resolve/getResolveOptionsKey.js 100.00% 1 4
lib/config/normalization.js 99.14% 4
lib/debug/ProfilingPlugin.js 100.00% 4
lib/module/Module.js 99.51% 4
lib/performance/HotspotsPlugin.js 100.00% 4
lib/util/SortableSet.js 100.00% 4
lib/css/syntax-parser.js 99.90% 3
lib/html/syntax-parser.js 99.98% 3
lib/stats/StatsFactory.js 96.99% 2 1
lib/util/WeakTupleMap.js 100.00% 3
lib/config/defineConfig.js 100.00% 2
lib/css/syntax.js 100.00% 2
lib/graph/ModuleGraph.js 100.00% 1 1
lib/html/syntax.js 100.00% 2
lib/loaders/LoaderRunner.js 97.70% 2
lib/module/Parser.js 100.00% 2
lib/node/nodeConsole.js 100.00% 2
lib/config/cli.js 99.73% 1
lib/dependencies/import/ImportParserPlugin.js 100.00% 1
lib/dll/DllReferencePlugin.js 100.00% 1
lib/errors/NonErrorEmittedError.js 100.00% 1
lib/errors/WebpackError.js 100.00% 1
lib/hmr/HotModuleReplacementPlugin.js 100.00% 1
lib/index.js 100.00% 1
lib/javascript/JavascriptParser.js 100.00% 1
lib/javascript/syntax.js 100.00% 1
lib/logging/Logger.js 100.00% 1
lib/optimize/TupleQueue.js 100.00% 1
lib/output/CopyPlugin.js 100.00% 1
lib/output/ManifestPlugin.js 100.00% 1
lib/rules/BasicEffectRulePlugin.js 100.00% 1
lib/rules/RuleSetCompiler.js 99.73% 1
lib/serialization/types.js 100.00% 1
lib/stats/DefaultStatsFactoryPlugin.js 99.87% 1
lib/stats/StatsPrinter.js 99.11% 1
lib/template/Template.js 100.00% 1
lib/util/TupleSet.js 100.00% 1
lib/util/identifier.js 100.00% 1
lib/util/magicComment.js 100.00% 1
lib/util/makeSerializable.js 100.00% 1
lib/util/terminalColors.js 100.00% 1
lib/util/validateSchema.js 100.00% 1
lib/MultiCompiler.js 99.74%
lib/asset/AssetBytesParser.js 91.67%
lib/asset/AssetModulesPlugin.js 99.38%
lib/asset/AssetParser.js 96.15%
lib/asset/AssetSourceParser.js 91.67%
lib/cache/PackFileCacheStrategy.js 99.23%
lib/config/validateSchema.js 94.67%
lib/css/CssGenerator.js 99.65%
lib/css/conventions.js 98.68%
lib/css/syntax-printer.js 99.73%
lib/dependencies/amd/AMDPlugin.js 99.31%
lib/dependencies/amd/AMDRuntimeModules.js 95.45%
lib/dependencies/commonjs/CommonJsExportsParserPlugin.js 99.79%
lib/dependencies/esm/ESMExportInitFragment.js 99.35%
lib/dependencies/html/HtmlEntryDependency.js 99.83%
lib/dependencies/import-meta/ImportMetaPlugin.js 99.63%
lib/devtool/ModuleFilenameHelpers.js 99.47%
lib/devtool/SourceMapDevToolPlugin.js 99.78%
lib/errors/CacheEffectivenessWarning.js 93.75%
lib/externals/ExternalModule.js 99.87%
lib/fs/FileSystemInfo.js 99.96%
lib/hmr/HotModuleReplacementChunkVersionCheckRuntimeModule.js 87.50%
lib/hmr/HotModuleReplacementRuntimeModule.js 77.78%
lib/hmr/JavascriptHotModuleReplacementHelper.js 91.30%
lib/hmr/lazyCompilationBackend.js 99.07%
lib/html/HtmlGenerator.js 99.87%
lib/html/HtmlModulesPlugin.js 99.51%
lib/javascript/JavascriptMetaInfoPlugin.js 98.44%
lib/json/JsonParser.js 96.88%
lib/loaders/LoaderPlugin.js 99.42%
lib/logging/createConsoleLogger.js 99.00%
lib/module/NormalModuleFactory.js 98.34%
lib/optimize/ConcatenatedModule.js 99.89%
lib/optimize/LazyBucketSortedSet.js 96.85%
lib/optimize/SideEffectsFlagPlugin.js 99.84%
lib/performance/UnusedDefinesPlugin.js 96.97%
lib/performance/UnusedExternalsPlugin.js 95.35%
lib/runtime/MakeDeferredNamespaceObjectRuntime.js 99.65%
lib/schemes/HttpUriPlugin.js 99.87%
lib/serialization/BinaryMiddleware.js 99.27%
lib/sharing/semver.js 99.55%
lib/stats/DefaultStatsPrinterPlugin.js 99.88%
lib/template/RuntimeTemplate.js 99.95%
lib/util/async.js 97.62%
lib/util/createMappings.js 98.45%
lib/util/globUtils.js 99.83%
lib/util/registerExternalSerializer.js 98.31%
lib/util/serialization.js 98.68%
lib/wasm-sync/WebAssemblyParser.js 98.94%
lib/wasm/AsyncWebAssemblyParser.js 97.83%
lib/webpack.js 98.62%

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Reported by Code Size #6461 for 07c80d6, computed by this pull request's own code.

Generated code size

Comparing base (05eb6bd) to head (07c80d6). Merging this pull request adds 2 new asset(s).

What moved Tests Assets Gzip Raw Test edit
Changed, test untouched 0 0 — — —
Changed, test edited 0 0 — — —
New 2 2 ⚪ added +2.64 KiB ⚪ added +9.66 KiB —
Deleted 0 0 — — —
Unchanged — 9938 — — —

Read Changed, test untouched first: the test is byte-identical on both sides, so webpack generated the difference. Changed, test edited moved partly because the test did — Test edit is how many bytes the test gained, and a bundle that grew by less is not a regression. New and Deleted are whole files, not deltas — a new asset was created, not grown, so it reads gray (⚪ added) — and adding tests cannot bury a real change. Gzip decides, because that is what users download; raw is the tiebreak, and brotli and zstd are per asset below.

Sample apps

No sample app changed size.

26 unchanged app(s)
App Gzip Gzip change Raw Raw change
library amd 1.38 KiB — 2.68 KiB —
library amd-require 1.38 KiB — 2.67 KiB —
library assign 1.38 KiB — 2.67 KiB —
library assign-properties 1.42 KiB — 2.80 KiB —
library commonjs 1.38 KiB — 2.66 KiB —
library commonjs-module 1.38 KiB — 2.67 KiB —
library commonjs-static 1.55 KiB — 3.54 KiB —
library commonjs2 1.38 KiB — 2.67 KiB —
library global 1.38 KiB — 2.66 KiB —
library jsonp 1.37 KiB — 2.66 KiB —
library module 1.21 KiB — 2.35 KiB —
library self 1.38 KiB — 2.66 KiB —
library system 1.41 KiB — 2.73 KiB —
library this 1.38 KiB — 2.66 KiB —
library umd 1.44 KiB — 2.86 KiB —
library umd2 1.44 KiB — 2.86 KiB —
library var 1.38 KiB — 2.67 KiB —
library window 1.38 KiB — 2.66 KiB —
node-ssr 96.36 KiB — 371.30 KiB —
node-ssr-esm 96.61 KiB — 372.00 KiB —
react-100 32.06 KiB — 150.15 KiB —
react-1k 73.05 KiB — 906.51 KiB —
react-1k-esm 72.54 KiB — 906.44 KiB —
react-5k 190.87 KiB — 4.08 MiB —
webworker 36.15 KiB — 93.71 KiB —
webworker-esm 35.67 KiB — 94.21 KiB —

No asset that both runs emit changed size — everything below is new or deleted.

2 asset(s) this pull request adds
Asset Raw Gzip (9) Brotli (11) Zstd (19)
⚪ added cjs-tree-shaking/namespace-reexport-cycle bundle0.js 6.24 KiB 1.49 KiB 1.33 KiB 1.47 KiB
⚪ added cjs-tree-shaking/reexport-esm-cycle bundle0.js 3.42 KiB 1.15 KiB 1.01 KiB 1.15 KiB

No runtime that both runs build changed which runtime modules it carries.

2 runtime(s) this pull request adds or no longer builds
Runtime Modules
➕ cjs-tree-shaking/namespace-reexport-cycle main 4
➕ cjs-tree-shaking/reexport-esm-cycle main 3

Built test/configCases with the defaults a user gets, and the apps in test/sizeCoreReports (apps/): 2372 test(s), 9940 asset(s), 104 emitted nothing.

@alexander-akait
alexander-akait merged commit 14012f9 into webpack:main Nov 3, 2026
49 of 50 checks passed
@codspeed

codspeed Bot commented Nov 3, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning, 1 note

⚠️ 117 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

ℹ️ 40 benchmarks have no flamegraph

The flamegraph wasn't generated: most of the time was spent in runtime internals, which would leave nothing useful to analyze.

See affected benchmarks

✅ 351 untouched benchmarks


Comparing zerotohero3:fix/commonjs-reexport-esm-cycle (07c80d6) with main (05eb6bd)

Open in CodSpeed

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview of its latest commit is available.

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@23090
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@23090
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@23090

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: optimization Tree-shaking, splitChunks, concatenation, ids (lib/optimize, lib/ids) regression Worked in a previous release; pair with the Affected version field

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Production build crashes or fails when a CommonJS module re-exports an ES module in a cycle

2 participants