Skip to content

[Bug]: Destructuring a dynamic import namespace bypasses tree-shaking #10212

Description

@logaretm

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions