Skip to content

feat: tree-shake export default <namespace> re-exports#9574

Draft
IWANABETHATGUY wants to merge 1 commit into
mainfrom
05-26-feat_treeshake_ns_reexport
Draft

feat: tree-shake export default <namespace> re-exports#9574
IWANABETHATGUY wants to merge 1 commit into
mainfrom
05-26-feat_treeshake_ns_reexport

Conversation

@IWANABETHATGUY

@IWANABETHATGUY IWANABETHATGUY commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Extends the existing namespace re-export tree-shaking so that export default ns (where ns is import * as ns from '...') chains through the underlying module, just like the named export { ns as default } shape already does. In the scanner, LocalExport["default"].referenced is rewired to the namespace local, letting bind_imports_and_exports's re-export chain propagate and member-expression resolution descend into only the accessed exports.

Before

// main.js
import api from './middle';
console.log(api.used);
// middle.js
import * as api from './api';
export default api;
// api.js
export const used = 'used';
export const unused = 'unused';

bundled to:

var api_exports = /* @__PURE__ */ __exportAll({ unused: () => unused, used: () => used });
const used = "used";
const unused = "unused";
console.log(api_exports.used);

After

console.log("used");

Test plan

  • Added integration fixtures under crates/rolldown/tests/rolldown/tree_shaking/:
    • default_export_namespace — primary case
    • default_export_namespace_with_star — combined with export * from
    • default_export_namespace_cjs — CJS importee
    • default_export_namespace_member_write — write through the chain
    • default_export_namespace_aliased — both export default ns and export { ns as foo }
  • Full integration suite passes with no regressions in other fixtures
  • cargo clippy -p rolldown --tests clean

IWANABETHATGUY commented May 26, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

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

@netlify

netlify Bot commented May 26, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit b40c00b
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a15c4be09d36b00085b2b3c
😎 Deploy Preview https://deploy-preview-9574--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 26, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit b4a0ce1
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a15d39281e2bb00070acd45

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 05-26-feat_treeshake_ns_reexport branch 2 times, most recently from 9ba18a0 to 77d0d8c Compare May 26, 2026 17:04
@IWANABETHATGUY
IWANABETHATGUY force-pushed the 05-26-feat_treeshake_ns_reexport branch from 77d0d8c to b4a0ce1 Compare May 26, 2026 17:08
@IWANABETHATGUY IWANABETHATGUY changed the title feat: treeshake ns reexport feat: tree-shake re-exports May 26, 2026
@IWANABETHATGUY IWANABETHATGUY changed the title feat: tree-shake re-exports feat: tree-shake export default <namespace> re-exports May 26, 2026
@IWANABETHATGUY
IWANABETHATGUY marked this pull request as ready for review May 27, 2026 06:37
@codspeed-hq

codspeed-hq Bot commented May 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing 05-26-feat_treeshake_ns_reexport (b4a0ce1) with main (6164676)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@shulaoda
shulaoda marked this pull request as draft May 27, 2026 06:58
@shulaoda

shulaoda commented May 27, 2026

Copy link
Copy Markdown
Member

Concern: sideEffects: false interaction with export default <namespace> rewiring

Context

It rewires LocalExport["default"].referenced so that export default ns (where ns is import * as ns from '...') is treated like a re-export chain, enabling member-expression tree-shaking through the default import.

However, export default <expr> is semantically an own export of the module, not a re-export. This distinction matters when sideEffects: false is involved.

The scenario

// package.json
{
  "sideEffects": false
}

// main.js
import api from './middle';
console.log(api.used);

// middle.js
import * as api from './api';
console.log(123);       // <-- side effect in the module body
export default api;

// api.js
export const used = 'used';
export const unused = 'unused';

Expected behavior

Because export default api is an own export of middle.js (not a re-export like export { api as default }), consuming the default import means middle.js itself is "used". Even with sideEffects: false, the module's own statements should execute, so console.log(123) must appear in the output.

Potential issue

The optimization rewires LocalExport["default"].referenced to point at the namespace local (the api symbol from import * as api from './api'). This effectively makes the default export look like a re-export chain to the linker. Combined with sideEffects: false, the bundler may conclude that middle.js has no own exports being consumed (only pass-through re-exports), and therefore skip the module's own statements, incorrectly dropping console.log(123).

This is the same distinction described in Re-export vs Own export for default:

  • export { ns as default } is a re-export (the binding stays in sync with the source).
  • export default ns is an own export (it captures the value into a separate binding).

The optimization collapses the two cases into one, but they have different implications for module inclusion under sideEffects: false.

Suggested test case

Add a fixture that combines this optimization with sideEffects: false and a side-effectful statement in the middle module:

// _config.json
{
  "config": {
    "treeshake": {
      "moduleSideEffects": false
    }
  }
}

// main.js
import api from './middle';
console.log(api.used);

// middle.js
import * as api from './api';
console.log(123);
export default api;

// api.js
export const used = 'used';
export const unused = 'unused';

The expected output should still contain console.log(123), since the module's default export is consumed and export default ns makes it an own export that anchors the module's inclusion.

@pkg-pr-new

pkg-pr-new Bot commented May 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@rolldown/browser

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/browser@9574

@rolldown/debug

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/debug@9574

rolldown

npm i https://pkg.pr.new/rolldown/rolldown@9574

@rolldown/binding-android-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-android-arm64@9574

@rolldown/binding-darwin-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@9574

@rolldown/binding-darwin-x64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@9574

@rolldown/binding-freebsd-x64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@9574

@rolldown/binding-linux-arm-gnueabihf

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@9574

@rolldown/binding-linux-arm64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@9574

@rolldown/binding-linux-arm64-musl

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@9574

@rolldown/binding-linux-ppc64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-ppc64-gnu@9574

@rolldown/binding-linux-s390x-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-s390x-gnu@9574

@rolldown/binding-linux-x64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@9574

@rolldown/binding-linux-x64-musl

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@9574

@rolldown/binding-openharmony-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-openharmony-arm64@9574

@rolldown/binding-wasm32-wasi

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@9574

@rolldown/binding-win32-arm64-msvc

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@9574

@rolldown/binding-win32-x64-msvc

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@9574

commit: b4a0ce1

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants