fix: skip inlining stale CJS export constants on module.exports reassignment#8990
Conversation
How to use the Graphite Merge QueueAdd the label graphite: merge-when-ready to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
✅ Deploy Preview for rolldown-rs canceled.
|
042d271 to
2d41d65
Compare
8f331c0 to
6397dbe
Compare
Merge activity
|
…ignment (#8990) ## Summary When a CJS module uses both `exports.foo = 1` and `module.exports = { foo: 2 }`, the `exports.foo` constant was incorrectly inlined as `1` instead of preserving the runtime property access that yields `2`. This PR adds fine-grained detection of `module.exports` reassignment during scanning: - **Object literal RHS** (`module.exports = { foo, bar }`): only invalidates `exports.xxx` constants whose names overlap with the object's static properties. Non-conflicting constants are still eligible for inlining. - **Non-analyzable RHS** (function call, variable, computed keys, spread): invalidates all `exports.xxx` constants since we can't statically determine the property set. Uses a `ModuleExportsReassignment` enum (`None` / `KnownProps(set)` / `Unknown`) to track this, feeding stale symbol IDs into the existing `bailout_inlined_cjs_exports_symbol_ids` set. ## Test plan - [x] Added `default_property_read_should_not_use_stale_exports` — object literal RHS, conflicting prop is not inlined, non-conflicting prop is still inlined - [x] Added `default_property_read_should_not_use_stale_exports_non_object` — function call RHS, blanket bailout on all CJS constants - [x] All 768 fixture tests pass - [x] `just lint` passes (clippy + fmt + check)
6397dbe to
7bcb2e0
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
## [1.0.0-rc.14] - 2026-04-08 ### 🚀 Features - rust: add `disable_panic_hook` feature to disable the panic hook (#9023) by @sapphi-red - support inlineConst for CJS exports accessed through module.exports (#8976) by @h-a-n-a ### 🐛 Bug Fixes - rolldown_plugin_vite_import_glob: normalize resolved alias path to prevent double slashes (#9032) by @shulaoda - rolldown_plugin_vite_import_glob: follow symlinks in file scanning (#9000) by @Copilot - wrap CJS entry modules for IIFE/UMD when using exports/module (#8999) by @IWANABETHATGUY - emit separate __toESM bindings for mixed ESM/CJS external imports (#8987) by @IWANABETHATGUY - tree-shake dead dynamic imports to side-effect-free CJS modules (#8529) by @sapphi-red - skip inlining stale CJS export constants on module.exports reassignment (#8990) by @IWANABETHATGUY ### 🚜 Refactor - generator: migrate ecma formatting from npx oxfmt to vp fmt (#9022) by @shulaoda - generator: replace npx oxfmt with vp fmt for ecma formatting (#9021) by @shulaoda ### 📚 Documentation - contrib-guide: mention that running tests on older Node.js version will have different stat results (#8996) by @claude ### ⚙️ Miscellaneous Tasks - deps: update npm packages (#9002) by @renovate[bot] - deps: update dependency @napi-rs/cli to v3.6.1 (#9034) by @renovate[bot] - deps: upgrade oxc to 0.124.0 (#9018) by @shulaoda - deps: update test262 submodule for tests (#9010) by @sapphi-red - deps: update dependency oxfmt to ^0.44.0 (#9012) by @renovate[bot] - deps: update dependency vite to v8.0.5 [security] (#9009) by @renovate[bot] - deps: update dependency vite-plus to v0.1.16 (#9008) by @renovate[bot] - deps: update rust crates (#9003) by @renovate[bot] - deps: update github-actions (#9004) by @renovate[bot] - deps: update dependency lodash-es to v4.18.1 [security] (#8992) by @renovate[bot] - deps: update crate-ci/typos action to v1.45.0 (#8988) by @renovate[bot] - upgrade oxc npm packages to 0.123.0 (#8985) by @shulaoda ###◀️ Revert - "chore(deps): update dependency oxfmt to ^0.44.0 (#9012)" (#9019) by @shulaoda Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>

Summary
When a CJS module uses both
exports.foo = 1andmodule.exports = { foo: 2 }, theexports.fooconstant was incorrectly inlined as1instead of preserving the runtime property access that yields2.This PR adds fine-grained detection of
module.exportsreassignment during scanning:module.exports = { foo, bar }): only invalidatesexports.xxxconstants whose names overlap with the object's static properties. Non-conflicting constants are still eligible for inlining.exports.xxxconstants since we can't statically determine the property set.Uses a
ModuleExportsReassignmentenum (None/KnownProps(set)/Unknown) to track this, feeding stale symbol IDs into the existingbailout_inlined_cjs_exports_symbol_idsset.Test plan
default_property_read_should_not_use_stale_exports— object literal RHS, conflicting prop is not inlined, non-conflicting prop is still inlineddefault_property_read_should_not_use_stale_exports_non_object— function call RHS, blanket bailout on all CJS constantsjust lintpasses (clippy + fmt + check)