fix(cjs): rename CJS-wrapped locals that would shadow chunk-scope names#9392
Conversation
✅ Deploy Preview for rolldown-rs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Fixes issue #9375 where IIFE/UMD output produced invalid code when a CJS-wrapped module had a root-scope binding whose name matched an external factory param. The hoisted var pinia = ... inside the __commonJS closure would shadow the captured pinia factory param at runtime. The fix forces deconfliction for any CJS-wrapped real-AST root-scope symbol whose canonical name matches a factory param name, leaving ESM/CJS output unchanged.
Changes:
- Collect canonical names of IIFE/UMD factory params (from
direct_imports_from_external_modules) after they've been added to the renamer. - Extend the
is_cjs_wrapped_modulebranch indeconflict_chunk_symbolsto also deconflict bindings that would shadow a factory param. - Add regression fixture covering both
iifeandumdformats viaconfigVariants.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| crates/rolldown/src/utils/chunk/deconflict_chunk_symbols.rs | Adds iife_factory_param_names set and uses it to force deconfliction for shadowing names in CJS-wrapped modules under IIFE/UMD. |
| crates/rolldown/tests/rolldown/issues/9375/_config.json | Configures the regression test with iife format and a umd variant. |
| crates/rolldown/tests/rolldown/issues/9375/index.ts | Repro source: imports createPinia from external pinia and declares a local var pinia. |
| crates/rolldown/tests/rolldown/issues/9375/artifacts.snap | Snapshot asserting local pinia is renamed to pinia$1 in both IIFE and UMD outputs. |
Merging this PR will not alter performance
Comparing Footnotes
|
Merge activity
|
…es (#9392) ## Summary - Closes #9375 and #9055. - A CJS-wrapped module is emitted as `__commonJS((exports, module) => { ... })`. The closure captures the chunk's outer scope, so any user-source `var/const` declaration inside the closure that shares a name with something already at chunk scope shadows the captured value at runtime. - The existing logic in `deconflict_chunk_symbols.rs` deliberately set `needs_deconflict=false` for non-facade real-AST symbols in CJS-wrapped modules ("they live inside the closure and don't pollute the chunk's root scope"). That reasoning ignored that the closure *captures* the chunk's root scope. Examples of names captured at chunk scope: - **iife/umd factory params** (#9375): `(function(pinia) { __commonJS(((exports, module) => { var pinia = (0, pinia.createPinia)(); ... })) })(Pinia)` — local `pinia` shadowed the iife factory param. - **CJS wrapper facades** (#9055): `var require_greet = __commonJS(...); ... const require_greet = require_greet();` — local `const require_greet` shadowed the synthesized `require_greet` wrapper. Fix: collect those two specific kinds of names into `chunk_scope_captured_names` (using the symbols' *original* names, since wrappers haven't been renamed at this point), and force `needs_deconflict=true` for any CJS-wrapped module's root-scope symbol whose name is in that set. Other names reserved in the renamer (e.g. ESM module import bindings that get rewritten to `import_x.default`) are intentionally left out because they don't actually render at chunk scope — including them would cause unnecessary renames in many existing fixtures. ## Test plan - [x] Regression fixtures at `crates/rolldown/tests/rolldown/issues/9375/` (covers `iife` and `umd` via `configVariants`) and `crates/rolldown/tests/rolldown/issues/9055/`. - [x] Full integration suite identical to baseline: 1711 passed, 64 pre-existing failures unrelated to this change. - [x] No existing fixture snapshots regressed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
653542d to
ffb8abd
Compare
…es (#9392) ## Summary - Closes #9375 and #9055. - A CJS-wrapped module is emitted as `__commonJS((exports, module) => { ... })`. The closure captures the chunk's outer scope, so any user-source `var/const` declaration inside the closure that shares a name with something already at chunk scope shadows the captured value at runtime. - The existing logic in `deconflict_chunk_symbols.rs` deliberately set `needs_deconflict=false` for non-facade real-AST symbols in CJS-wrapped modules ("they live inside the closure and don't pollute the chunk's root scope"). That reasoning ignored that the closure *captures* the chunk's root scope. Examples of names captured at chunk scope: - **iife/umd factory params** (#9375): `(function(pinia) { __commonJS(((exports, module) => { var pinia = (0, pinia.createPinia)(); ... })) })(Pinia)` — local `pinia` shadowed the iife factory param. - **CJS wrapper facades** (#9055): `var require_greet = __commonJS(...); ... const require_greet = require_greet();` — local `const require_greet` shadowed the synthesized `require_greet` wrapper. Fix: collect those two specific kinds of names into `chunk_scope_captured_names` (using the symbols' *original* names, since wrappers haven't been renamed at this point), and force `needs_deconflict=true` for any CJS-wrapped module's root-scope symbol whose name is in that set. Other names reserved in the renamer (e.g. ESM module import bindings that get rewritten to `import_x.default`) are intentionally left out because they don't actually render at chunk scope — including them would cause unnecessary renames in many existing fixtures. ## Test plan - [x] Regression fixtures at `crates/rolldown/tests/rolldown/issues/9375/` (covers `iife` and `umd` via `configVariants`) and `crates/rolldown/tests/rolldown/issues/9055/`. - [x] Full integration suite identical to baseline: 1711 passed, 64 pre-existing failures unrelated to this change. - [x] No existing fixture snapshots regressed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
## [1.0.2] - 2026-05-20 ### 🚀 Features - devtools: emit package size in PackageGraphReady (#9434) by @IWANABETHATGUY - devtools: classify package dependency types (#9427) by @IWANABETHATGUY - devtools: map packages to modules and chunks (#9426) by @IWANABETHATGUY - devtools: mark used packages (#9423) by @IWANABETHATGUY - devtools: make duplicate packages discoverable (#9422) by @IWANABETHATGUY - devtools: emit package metadata (#9421) by @IWANABETHATGUY - update oxc to 0.132.0 (#9449) by @shulaoda - update oxc to 0.131.0 (#9424) by @shulaoda - allow checks.* to escalate emissions to hard errors (#9388) by @IWANABETHATGUY - dev: support watcher options `include` and `exclude` (#9395) by @h-a-n-a - emit warnings for invalid pure annotations (#9381) by @Kyujenius ### 🐛 Bug Fixes - hash: keep chunk file names stable when an unrelated entry is added (#9444) by @hyf0 - call `codeSplitting.groups[].name` in deterministic order (#9457) by @sapphi-red - dev/lazy: make `resolve_id` idempotent when the resolved id is already a lazy entry (#9439) by @h-a-n-a - chunk-optimization: publish absorbed dynamic-entry namespace cross-chunk (#9448) by @IWANABETHATGUY - treeshake: propagate pure annotation through compound exprs (#9431) by @Dunqing - finalizer: skip redundant init call when barrel executes in same chunk (#9354) by @IWANABETHATGUY - linking: initialize wrapped ESM re-export owners (#9353) by @IWANABETHATGUY - do not inherit __toESM across chunks for named-only external imports (#9333) (#9415) by @IWANABETHATGUY - watcher: don't write output or emit events after close() (#9328) by @situ2001 - chunk-optimization: avoid unsafe dynamic-only merges (#9398) by @IWANABETHATGUY - cjs: rename CJS-wrapped locals that would shadow chunk-scope names (#9392) by @hyf0 - dev/lazy: watch lazy modules added in rebuilds (#9391) by @h-a-n-a ### 🚜 Refactor - rolldown_dev: move dev example to break publish cycle (#9465) by @Boshen - binding: drop unsafe napi string helper, hoist transform ArcStr (#9456) by @hyf0 - ecmascript_utils: split rewrite_ident_reference off JsxExt trait (#9417) by @IWANABETHATGUY - use `ThreadsafeFunction::call_async_catch` (#9390) by @sapphi-red ### 📚 Documentation - devtools: document @rolldown/debug usage and package graph consumption (#9435) by @IWANABETHATGUY - replace `Inter` with system font stack in OG template SVG (#9240) by @yvbopeng - remove `output.comments` warning as all issues have been resolved (#9393) by @sapphi-red - in-depth: clarify @__PURE__ scope and document positions (#9389) by @Kyujenius - readme: remove release candidate notice (#9387) by @shulaoda ### ⚡ Performance - vite-resolve: cache importer existence checks (#9443) by @Brooooooklyn - binding: reduce plugin string clones (#9436) by @Brooooooklyn ### 🧪 Testing - enable `legal_comments_inline` test (#9394) by @sapphi-red ### ⚙️ Miscellaneous Tasks - bump pnpm to v11.1.2 (#9447) by @Boshen - deps: update rust crates (#9461) by @renovate[bot] - deps: update rollup submodule for tests to v4.60.4 (#9453) by @rolldown-guard[bot] - deps: update test262 submodule for tests (#9454) by @rolldown-guard[bot] - deps: update npm packages (#9430) by @renovate[bot] - deps: update github actions (#9429) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to v0.25.1 (#9452) by @renovate[bot] - deps: update rust crates (#9428) by @renovate[bot] - revert allow checks.* to escalate emissions to hard errors (#9388) (#9438) by @IWANABETHATGUY - update mimalloc-safe to 0.1.61 (#9413) by @shulaoda - deny `todo`, `unimplemented`, and `print_stderr` clippy lints (#9412) by @Boshen - deps: update mimalloc-safe to 0.1.60 (#9410) by @shulaoda - remove `pip install setuptools` workaround for node-gyp on macOS (#9370) by @sapphi-red - renovate: disable automerge to require manual approval (#9386) by @shulaoda - deps: update napi (#9385) by @renovate[bot] ### ❤️ New Contributors * @yvbopeng made their first contribution in [#9240](#9240) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
## [1.0.2] - 2026-05-20 ### 🚀 Features - devtools: emit package size in PackageGraphReady (rolldown#9434) by @IWANABETHATGUY - devtools: classify package dependency types (rolldown#9427) by @IWANABETHATGUY - devtools: map packages to modules and chunks (rolldown#9426) by @IWANABETHATGUY - devtools: mark used packages (rolldown#9423) by @IWANABETHATGUY - devtools: make duplicate packages discoverable (rolldown#9422) by @IWANABETHATGUY - devtools: emit package metadata (rolldown#9421) by @IWANABETHATGUY - update oxc to 0.132.0 (rolldown#9449) by @shulaoda - update oxc to 0.131.0 (rolldown#9424) by @shulaoda - allow checks.* to escalate emissions to hard errors (rolldown#9388) by @IWANABETHATGUY - dev: support watcher options `include` and `exclude` (rolldown#9395) by @h-a-n-a - emit warnings for invalid pure annotations (rolldown#9381) by @Kyujenius ### 🐛 Bug Fixes - hash: keep chunk file names stable when an unrelated entry is added (rolldown#9444) by @hyf0 - call `codeSplitting.groups[].name` in deterministic order (rolldown#9457) by @sapphi-red - dev/lazy: make `resolve_id` idempotent when the resolved id is already a lazy entry (rolldown#9439) by @h-a-n-a - chunk-optimization: publish absorbed dynamic-entry namespace cross-chunk (rolldown#9448) by @IWANABETHATGUY - treeshake: propagate pure annotation through compound exprs (rolldown#9431) by @Dunqing - finalizer: skip redundant init call when barrel executes in same chunk (rolldown#9354) by @IWANABETHATGUY - linking: initialize wrapped ESM re-export owners (rolldown#9353) by @IWANABETHATGUY - do not inherit __toESM across chunks for named-only external imports (rolldown#9333) (rolldown#9415) by @IWANABETHATGUY - watcher: don't write output or emit events after close() (rolldown#9328) by @situ2001 - chunk-optimization: avoid unsafe dynamic-only merges (rolldown#9398) by @IWANABETHATGUY - cjs: rename CJS-wrapped locals that would shadow chunk-scope names (rolldown#9392) by @hyf0 - dev/lazy: watch lazy modules added in rebuilds (rolldown#9391) by @h-a-n-a ### 🚜 Refactor - rolldown_dev: move dev example to break publish cycle (rolldown#9465) by @Boshen - binding: drop unsafe napi string helper, hoist transform ArcStr (rolldown#9456) by @hyf0 - ecmascript_utils: split rewrite_ident_reference off JsxExt trait (rolldown#9417) by @IWANABETHATGUY - use `ThreadsafeFunction::call_async_catch` (rolldown#9390) by @sapphi-red ### 📚 Documentation - devtools: document @rolldown/debug usage and package graph consumption (rolldown#9435) by @IWANABETHATGUY - replace `Inter` with system font stack in OG template SVG (rolldown#9240) by @yvbopeng - remove `output.comments` warning as all issues have been resolved (rolldown#9393) by @sapphi-red - in-depth: clarify @__PURE__ scope and document positions (rolldown#9389) by @Kyujenius - readme: remove release candidate notice (rolldown#9387) by @shulaoda ### ⚡ Performance - vite-resolve: cache importer existence checks (rolldown#9443) by @Brooooooklyn - binding: reduce plugin string clones (rolldown#9436) by @Brooooooklyn ### 🧪 Testing - enable `legal_comments_inline` test (rolldown#9394) by @sapphi-red ### ⚙️ Miscellaneous Tasks - bump pnpm to v11.1.2 (rolldown#9447) by @Boshen - deps: update rust crates (rolldown#9461) by @renovate[bot] - deps: update rollup submodule for tests to v4.60.4 (rolldown#9453) by @rolldown-guard[bot] - deps: update test262 submodule for tests (rolldown#9454) by @rolldown-guard[bot] - deps: update npm packages (rolldown#9430) by @renovate[bot] - deps: update github actions (rolldown#9429) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to v0.25.1 (rolldown#9452) by @renovate[bot] - deps: update rust crates (rolldown#9428) by @renovate[bot] - revert allow checks.* to escalate emissions to hard errors (rolldown#9388) (rolldown#9438) by @IWANABETHATGUY - update mimalloc-safe to 0.1.61 (rolldown#9413) by @shulaoda - deny `todo`, `unimplemented`, and `print_stderr` clippy lints (rolldown#9412) by @Boshen - deps: update mimalloc-safe to 0.1.60 (rolldown#9410) by @shulaoda - remove `pip install setuptools` workaround for node-gyp on macOS (rolldown#9370) by @sapphi-red - renovate: disable automerge to require manual approval (rolldown#9386) by @shulaoda - deps: update napi (rolldown#9385) by @renovate[bot] ### ❤️ New Contributors * @yvbopeng made their first contribution in [rolldown#9240](rolldown#9240) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
|
I am still getting errors in my project. It uses |
Summary
__commonJS((exports, module) => { ... }). The closure captures the chunk's outer scope, so any user-sourcevar/constdeclaration inside the closure that shares a name with something already at chunk scope shadows the captured value at runtime.deconflict_chunk_symbols.rsdeliberately setneeds_deconflict=falsefor non-facade real-AST symbols in CJS-wrapped modules ("they live inside the closure and don't pollute the chunk's root scope"). That reasoning ignored that the closure captures the chunk's root scope.Examples of names captured at chunk scope:
(function(pinia) { __commonJS(((exports, module) => { var pinia = (0, pinia.createPinia)(); ... })) })(Pinia)— localpiniashadowed the iife factory param.var require_greet = __commonJS(...); ... const require_greet = require_greet();— localconst require_greetshadowed the synthesizedrequire_greetwrapper.Fix: collect those two specific kinds of names into
chunk_scope_captured_names(using the symbols' original names, since wrappers haven't been renamed at this point), and forceneeds_deconflict=truefor any CJS-wrapped module's root-scope symbol whose name is in that set. Other names reserved in the renamer (e.g. ESM module import bindings that get rewritten toimport_x.default) are intentionally left out because they don't actually render at chunk scope — including them would cause unnecessary renames in many existing fixtures.Test plan
crates/rolldown/tests/rolldown/issues/9375/(coversiifeandumdviaconfigVariants) andcrates/rolldown/tests/rolldown/issues/9055/.🤖 Generated with Claude Code