Reproduction link or steps
Destructuring the namespace binding inside a dynamic import callback body keeps every export, even the ones I never touch. If I use member access instead it tree-shakes fine, so the trigger is specifically the const { x } = ns form in the body.
I ran into this on a real app that has a deferred import('@sentry/react') that destructured a couple of helpers ended up dragging the entire browser SDK (Session Replay, Feedback, etc.) onto the critical path, ~1MB that should have been shaken out.
Repro:
https://repl.rolldown.rs/#eNptUstypDAM/BWVLzBbE8+d1Oxlf2MuBsTgHSNTWM6jKP49snkkqeQCVstqu9s9q05VsxqMJf0/pCWp6ijPqpHKDqOfuCz0xdla4OKkuUcqSwonuP6F+UYAlwv4yBB6H10LPCE+hd48MKORKAZs077GU2CYIdWwwBUoPO+4d6idv5epdxJ04238MCAxyKHgjEw3Po5OmDtwljCAJejMi58Swr0N4AnPaTsBebYNft7H0h1e/fQI+iD/PJeC3o9e0kf0o6p4iric1eSda/0raZno7F3z4dYvna/GidgWO7nov9wW0d3kByj2sSIJxbe8VTaa6PL/GCizv5bGyBWslHpANjpD5yQiOdNZJ1YEdNiwWFu/Z7SOzJ7EEEDT9MCm3h9rZCvSk1IRKvpG0zzMHeV9vYhalX3DVk35MjfV4ojUIjUWw01VMC/CtBKtIdkptmod3lT+2RzQl2R3SpSY/bOXM7N1E/FW7sx7+Y16jVcO1xWK9C+e8+xOdkwfwG/zuZkZ8kqik2L9VG+hpkQqrC9JoGEMrJYPN6ocWQ==
If you need a real-world full-fledged repro, I made one that show-cases this with @sentry/react package.
What is expected?
I'm expecting the output bundle to not include the unused member.
//#region used.js
const used = "used";
//#endregion
export { used };
What is actually happening?
Right now, both used and unused members are kept:
//#region used.js
const used = "used";
//#endregion
//#region unused.js
const unused = "unused-should-be-shaken";
//#endregion
export { unused, used };
System Info
System:
OS: macOS 26.5.1
CPU: (12) arm64 Apple M3 Pro
Memory: 400.27 MB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.11.0 - /Users/awad/.volta/tools/image/node/24.11.0/bin/node
Yarn: 1.22.22 - /Users/awad/.volta/tools/image/yarn/1.22.22/bin/yarn
npm: 11.6.1 - /Users/awad/.volta/tools/image/node/24.11.0/bin/npm
pnpm: 11.9.0 - /Users/awad/.volta/tools/image/node/24.11.0/bin/pnpm
bun: 1.3.14 - /Users/awad/.bun/bin/bun
Deno: 2.8.0 - /Users/awad/.deno/bin/deno
Browsers:
Brave Browser: 150.1.92.139
Chrome: 150.0.7871.115
Chrome Canary: 152.0.7941.2
Firefox: 152.0.5
Safari: 26.5
Safari Technology Preview: 26.0
Any additional comments?
I first thought it was .then specific, but it isn't. It happens with await too, as long as the namespace binding can't be inlined away.
// shakes (alias inlined away)
const ns = await import('./lib.js');
const { used } = ns;
console.log(used);
// does NOT shake (ns can't be inlined)
const ns = await import('./lib.js');
const { used } = ns;
const { used: u2 } = ns;
console.log(used, u2);
I think I figured a quick fix for it, will submit a PR for it due to this bug causing Sentry bundle size to be unjustifiable in some Vite 8 setups that defer Sentry imports.
I thought it was related to #7874 but doesn't seem like the same issue, also #9578 / #9574 but they seem to be dealing with static exports only.
Reproduction link or steps
Destructuring the namespace binding inside a dynamic import callback body keeps every export, even the ones I never touch. If I use member access instead it tree-shakes fine, so the trigger is specifically the
const { x } = nsform in the body.I ran into this on a real app that has a deferred
import('@sentry/react')that destructured a couple of helpers ended up dragging the entire browser SDK (Session Replay, Feedback, etc.) onto the critical path, ~1MB that should have been shaken out.Repro:
https://repl.rolldown.rs/#eNptUstypDAM/BWVLzBbE8+d1Oxlf2MuBsTgHSNTWM6jKP49snkkqeQCVstqu9s9q05VsxqMJf0/pCWp6ijPqpHKDqOfuCz0xdla4OKkuUcqSwonuP6F+UYAlwv4yBB6H10LPCE+hd48MKORKAZs077GU2CYIdWwwBUoPO+4d6idv5epdxJ04238MCAxyKHgjEw3Po5OmDtwljCAJejMi58Swr0N4AnPaTsBebYNft7H0h1e/fQI+iD/PJeC3o9e0kf0o6p4iric1eSda/0raZno7F3z4dYvna/GidgWO7nov9wW0d3kByj2sSIJxbe8VTaa6PL/GCizv5bGyBWslHpANjpD5yQiOdNZJ1YEdNiwWFu/Z7SOzJ7EEEDT9MCm3h9rZCvSk1IRKvpG0zzMHeV9vYhalX3DVk35MjfV4ojUIjUWw01VMC/CtBKtIdkptmod3lT+2RzQl2R3SpSY/bOXM7N1E/FW7sx7+Y16jVcO1xWK9C+e8+xOdkwfwG/zuZkZ8kqik2L9VG+hpkQqrC9JoGEMrJYPN6ocWQ==
If you need a real-world full-fledged repro, I made one that show-cases this with
@sentry/reactpackage.What is expected?
I'm expecting the output bundle to not include the
unusedmember.What is actually happening?
Right now, both used and unused members are kept:
System Info
System: OS: macOS 26.5.1 CPU: (12) arm64 Apple M3 Pro Memory: 400.27 MB / 36.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 24.11.0 - /Users/awad/.volta/tools/image/node/24.11.0/bin/node Yarn: 1.22.22 - /Users/awad/.volta/tools/image/yarn/1.22.22/bin/yarn npm: 11.6.1 - /Users/awad/.volta/tools/image/node/24.11.0/bin/npm pnpm: 11.9.0 - /Users/awad/.volta/tools/image/node/24.11.0/bin/pnpm bun: 1.3.14 - /Users/awad/.bun/bin/bun Deno: 2.8.0 - /Users/awad/.deno/bin/deno Browsers: Brave Browser: 150.1.92.139 Chrome: 150.0.7871.115 Chrome Canary: 152.0.7941.2 Firefox: 152.0.5 Safari: 26.5 Safari Technology Preview: 26.0Any additional comments?
I first thought it was .then specific, but it isn't. It happens with await too, as long as the namespace binding can't be inlined away.
I think I figured a quick fix for it, will submit a PR for it due to this bug causing Sentry bundle size to be unjustifiable in some Vite 8 setups that defer Sentry imports.
I thought it was related to #7874 but doesn't seem like the same issue, also #9578 / #9574 but they seem to be dealing with static exports only.