fix(code-splitting): initialize leaves behind a re-exported namespace import - #10694
Conversation
✅ Deploy Preview for rolldown-rs canceled.
|
There was a problem hiding this comment.
Pull request overview
Fixes a strict-execution-order tree-shaking regression where excluded plain imports that declare bindings (notably namespace imports) could still have importer-local “binding demand” (re-exported facade / folded member reads), but their leaf initializers were skipped—leading to missing init_*() calls in wrapped shared chunks (issue #10690).
Changes:
- Extends wrapped ESM init obligation routing to account for binding-demand on excluded plain imports, using
used_symbol_refsto decide when routing is necessary. - Treats re-exported import bindings (including re-exported namespace bindings) as opaque for init-target routing so downstream consumers can’t be broken by statically folded reads.
- Adds multiple strictExecutionOrder regression fixtures + snapshots covering pure barrels, effectful leaves, literal
export * as ns, and folded-member reads; updates internal code-splitting design notes accordingly.
Reviewed changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| internal-docs/code-splitting/design.md | Documents the expanded notion of init obligations (execution deps + importer-local binding demand). |
| crates/rolldown/src/stages/generate_stage/order_wrapping.rs | Threads used_symbol_refs into wrapped init metadata computation. |
| crates/rolldown/src/stages/generate_stage/mod.rs | Threads used_symbol_refs into wrapped init metadata computation. |
| crates/rolldown/src/stages/generate_stage/compute_wrapped_esm_init_metadata.rs | Adds excluded-plain-import binding-demand routing and per-record target collection for metadata. |
| crates/rolldown/src/stages/generate_stage/code_splitting.rs | Updates record_is_init_obligation caller to pass the importer module reference. |
| crates/rolldown/src/module_finalizers/mod.rs | Updates record_is_init_obligation callers to pass the importer module reference. |
| crates/rolldown/src/esm_init_obligations.rs | Expands init-obligation predicate + makes re-exported bindings opaque for routing. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/_config.json | New strictExecutionOrder fixture config (on-demand + wrap-all variant). |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/_test.mjs | Executes the built fixture output. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/artifacts.snap | Snapshot asserting correct init ordering / leaf availability. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/color.js | Leaf module for the pure-barrel namespace re-export fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/css.js | Namespace import + re-export surface for the fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/first.js | Imports through the re-export surface and asserts behavior. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/fns.js | Pure barrel re-export hop. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/main.js | Drives the two-chunk scenario via sequential dynamic imports. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/second.js | Second consumer to ensure shared wrapper init remains correct. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_pure_barrel/theme.js | Uses cssFns.setOpacity via the namespace chain. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/_config.json | New strictExecutionOrder fixture config (on-demand + wrap-all variant). |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/_test.mjs | Executes the built fixture output. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/artifacts.snap | Snapshot asserting effectful leaf init executes exactly once. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/color.js | Effectful leaf module to validate init ordering/retention. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/css.js | Namespace import + re-export surface for the effectful-leaf fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/first.js | Consumer asserting correct folded-read + effect ordering. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/fns.js | Barrel re-export hop. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/main.js | Drives the two-consumer scenario. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/second.js | Second consumer asserting effect isn’t duplicated/dropped. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_namespace_effectful_leaf/theme.js | Validates colorLoaded before using the namespace member. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/_config.json | New strictExecutionOrder fixture config (on-demand + wrap-all variant). |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/_test.mjs | Executes the built fixture output. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/artifacts.snap | Snapshot for export * as ns chain + init behavior. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/color.js | Leaf module for literal namespace binding fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/css.js | Re-exports the literal namespace binding from a barrel hop. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/first.js | Consumer asserting behavior through literal namespace binding. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/fns.js | Declares export * as colorNs from './color.js'. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/main.js | Drives two-consumer execution to validate shared behavior. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/second.js | Second consumer asserting behavior remains correct. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/reexported_literal_namespace_binding/theme.js | Uses colorNs.setOpacity via the re-export chain. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/_config.json | New strictExecutionOrder fixture config (on-demand + wrap-all variant). |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/_test.mjs | Executes the built fixture output. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/artifacts.snap | Snapshot pinning folded-member behavior with namespace import present. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/color.js | Leaf module for folded-member fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/css.js | Performs folded member read from a namespace import. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/first.js | Consumer asserting folded result. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/fns.js | Barrel re-export hop for folded-member fixture. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/main.js | Drives the two-consumer scenario. |
| crates/rolldown/tests/rolldown/function/experimental/strict_execution_order/dead_namespace_import_folded_member/second.js | Second consumer asserting folded result remains stable. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Merging this PR will not alter performance
Comparing Footnotes
|
|
Waiting for this fix 🙏 |
I will review this pr ASAP. |
Merge activity
|
… import (#10694) Fixes #10690 Notes: - Tree shaking statically folds every downstream `cssFns.setOpacity` read into a direct reference to the leaf vars, so the `namespace object` doesn't get included. - `export { setOpacity } from './color.js'` in the middle got skipped, so it doesn't get rewrite to `init_xxx`, which causes the issue - Only strictExecutionOrder builds are affected Input ```js // css.js import * as cssFns from './fns.js'; // fns.js: export { setOpacity } from './color.js' globalThis.cssLoaded = true; export { cssFns }; ``` Before, the generated `init_css` never assigns the leaf: ```js globalThis.cssLoaded = true; // setOpacity is never assigned ``` After, the leaf initializer runs first: ```js init_color(); globalThis.cssLoaded = true; ```
15adf1e to
25ca9a9
Compare
|
Hey @hyfdev, any updates? Looks like this got stuck on CI. We’re still waiting for the fix 🙏 |
## [1.2.10] - 2026-09-23 ### 🚀 Features - rolldown_plugin_esm_external_require: prefer 'module.exports' over a namespace spread (#10901) by @isker - watch: support directories in addWatchFile (#10944) by @shulaoda - label code-splitting timing rows (#10896) by @IWANABETHATGUY - separate code-splitting group timing rows (#10892) by @IWANABETHATGUY - clarify callback owners in the timing report (#10890) by @IWANABETHATGUY - add checks.bundlerTimings with deprecated pluginTimings alias (#10883) by @IWANABETHATGUY ### 🐛 Bug Fixes - code-splitting: initialize leaves behind a re-exported namespace import (#10694) by @hyfdev - node: record the WASI target on the WebContainer binding fallback (#10952) by @shulaoda - dev: force incremental build on in DevEngine::new (#10958) by @o-alexandrov - tree-shaking: propagate side effects through cycles (#10893) by @IWANABETHATGUY - deconflict CJS locals that shadow a renamed require wrapper (#10793) by @logaretm - include group indexes in labeled `codeSplitting` timing rows (#10916) by @IWANABETHATGUY - docs: type-check Algolia `indices` option (#10934) by @shulaoda - docs: restore Algolia search (#10929) by @isker - tell code-splitting timing rows apart by position (#10903) by @IWANABETHATGUY ### 🚜 Refactor - rolldown_fs_watcher: remove the single-path API (#10950) by @shulaoda - dev: register watch files through FsWatcher (#10949) by @shulaoda - watch: let FsWatcher own the watched paths (#10948) by @shulaoda - watch: identify watch files by normalized absolute path (#10943) by @shulaoda - plugin: build a plugin-resolved `ResolvedId` in one place (#10921) by @IWANABETHATGUY ### 📚 Documentation - dev: add HMR design, align dev docs and engine comments (#10271) by @h-a-n-a ### ⚡ Performance - batch `sourcemapPathTransform` calls (#10763) by @IWANABETHATGUY - batch `sourcemapIgnoreList` calls (#10762) by @IWANABETHATGUY - hmr: drop the unused kind argument from registerFactory (#10915) by @h-a-n-a ### 🧪 Testing - handle Windows paths in sourcemap callback tests (#10942) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - replace custom `ban-expect-assertions` JS lint with builtin `no-restricted-properties` (#10933) by @isker - deps: update napi (#10959) by @renovate[bot] - deps: upgrade oxc to 0.151.0 (#10939) by @camc314 - deps: update test262 submodule for tests (#10941) by @rolldown-guard[bot] - deps: update npm packages (#10931) by @renovate[bot] - deps: update napi (#10928) by @renovate[bot] - deps: update dependency vite-plus to v0.3.3 (#10914) by @renovate[bot] - deps: update rust crates (#10932) by @renovate[bot] - deps: update github actions (#10930) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to v0.28.6 (#10924) by @renovate[bot] - deps: update crate-ci/typos action to v1.50.2 (#10917) by @renovate[bot] - deps: update dependency @napi-rs/cli to v3.10.4 (#10913) by @renovate[bot] - deps: update napi (#10897) by @renovate[bot] ### ❤️ New Contributors * @o-alexandrov made their first contribution in [#10958](#10958) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Fixes #10690
Notes:
cssFns.setOpacityread into a direct reference to the leaf vars, so thenamespace objectdoesn't get included.export { setOpacity } from './color.js'in the middle got skipped, so it doesn't get rewrite toinit_xxx, which causes the issueInput
Before, the generated
init_cssnever assigns the leaf:After, the leaf initializer runs first: