Skip to content

fix(treeshake): drop dead star re-export sources of wrapped barrels#9709

Closed
IWANABETHATGUY wants to merge 2 commits into
mainfrom
06-10-fix_9691_lazy_star_reexport_init
Closed

fix(treeshake): drop dead star re-export sources of wrapped barrels#9709
IWANABETHATGUY wants to merge 2 commits into
mainfrom
06-10-fix_9691_lazy_star_reexport_init

Conversation

@IWANABETHATGUY

@IWANABETHATGUY IWANABETHATGUY commented Jun 10, 2026

Copy link
Copy Markdown
Member

Description

Fixes #9691.

With codeSplitting: false, a dynamic import() wraps its target, and the wrap propagates through export * barrels into every star source. reference_needed_symbols.rs then force-marked the barrel's export * statements as side-effectful, so a sideEffects: false star source whose exports are all unused was force-included anyway:

  • the retained dead module bloats the bundle, and
  • combined with lazyBarrel (which drops the imports the dead body needs, since its exports are never requested), the retained body reads an undefined binding at top level — the ReferenceError: dep_b is not defined crash reported with @aws-sdk/client-s3.

Fix

Treat export * from an ESM-wrapped importee like a named re-export: let tree-shaking drop the statement, and rely on the finalizer's existing fallback for excluded re-export statements (generate_transitive_esm_init), which re-creates the init_*() call at the statement's position only when the star source is actually included. Initialization order is preserved, while a fully-unused, side-effect-free star source disappears from the bundle.

The statement is still forced eager when the lazy fallback can't substitute for it:

  • the importee has dynamic exports — the eager __reExport(...) namespace copy must run;
  • the importer is not ESM-wrapped (e.g. an entry module) — the fallback only fires for wrapped importers;
  • the importee is TLA-tainted — the fallback emits the call without await.

Tests

  • New regression fixture tests/rolldown/issues/9691 mirrors the @aws-sdk/client-s3 shape. Without the fix, its node-execution step fails with the original ReferenceError: dep_b is not defined; with the fix, the dead handler module is fully dropped in both the codeSplitting: false build and the code-splitting-on variant, and both execute cleanly.
  • Two existing snapshots improve: export_forms_common_js drops a no-op init_a wrapper, and strict_execution_order/issue_8777 no longer emits duplicate init_*() calls (the force-included export * path used to bypass the generated_init_esm_importee_ids dedup).

🤖 Generated with Claude Code

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 Jun 10, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

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

@IWANABETHATGUY IWANABETHATGUY changed the title init fix(treeshake): drop dead star re-export sources of wrapped barrels Jun 10, 2026
IWANABETHATGUY added a commit that referenced this pull request Jun 29, 2026
… barrels; fix #9806, #9691, #9964

Add `experimental.wrappedModuleTreeshaking` (off by default), a new side-effect
tree-shaking implementation for wrapped ESM modules. The legacy impl force-includes
a wrapped `sideEffects:false` re-export barrel; the new impl prunes it — the used
binding's canonical owner is initialized via the binding-use path, and the finalizer
follows canonical owners when a barrel is pruned.

A module is wrapped (WrapKind::Esm) by many triggers — strictExecutionOrder,
require(esm), code-splitting / CJS interop — so this gate is the legacy-vs-new switch
for ALL wrap paths, not just strictExecutionOrder. It fixes the #9961 family of
over-retention / dangling-init ReferenceErrors.

Safety guards (so flag-on never produces broken output):
- namespace-taint: weakly-connected import components containing a star-import edge
  revert to legacy wrapping (prevents included-but-empty wrapped modules /
  code-splitting panics; member-resolution-aware pruning is future work).
- external-barrel: never prune a barrel that directly imports from an external module
  (its import would be dropped while still referenced).
- off by default => byte-identical legacy output.

Also fix #9806, #9691, #9964 (lazyBarrel, default-on) — one root cause: lazyBarrel
deferred an import record whose binding is read by a retained statement of a wrapped
module (a barrel-own `const Bar = Foo.Bar;` for #9806; a re-exported `sideEffects:false`
leaf's top-level read for #9691/#9964), so the finalizer dropped the import declaration
while the reading statement survived => ReferenceError. The loader now force-loads any
such record (BarrelState::initialize_barrel_tracking), keeping the import and
initializing it before the read. This also removes the lazyBarrel crash-layer of #9961.
(#9691 supersedes the correctness goal of PR #9709; its dead-star-source pruning is now
reachable via the wrappedModuleTreeshaking flag — see the #9691 flag variant snapshot.)

Plumbing: ExperimentalOptions + napi binding + TS type/validator/bindingify +
config-variant test support + auto-regenerated schema. A shadow-mode diagnostic pass
(init_obligations.rs, env-gated by ROLLDOWN_DUMP_INIT_OBLIGATIONS) validates the model
without affecting output.

Tests: node-executed regression fixtures — #9961 (bug/fix), #9964 (require(esm) wrap)
and #9691 (codeSplitting:false wrap; flag variant prunes the dead star source), each
verified necessary (crashes on the parent commit with the documented ReferenceError,
passes here), exports_chain prune, namespace-taint revert, per-binding activation,
side-effect soundness, external-barrel guard, #9806, and a non-strict require(esm)
witness. Full integration suite green.

Design: internal-docs/linking/strict-execution-order-rewrite-proposal.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing import when lazy barrel is enabled

1 participant