refactor(rolldown): route interop ESM init emission through a shared init-target view#10202
Conversation
How to use the Graphite Merge QueueAdd 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. |
Merge activity
|
There was a problem hiding this comment.
Pull request overview
This PR refactors how the module finalizer decides and emits interop wrappers (CJS __commonJS and wrapped-ESM __esm) by routing wrapper classification through a single ModuleWrapperMode enum and routing wrapped-ESM init symbol access through a shared EsmInitTarget view derived from LinkingMetadata. It also makes AstFactory::make_esm_wrapper_stmt take a single options struct instead of a long positional argument list, to support future wrapper-emission modes without duplicating branching logic.
Changes:
- Introduce
EsmInitTarget+esm_init_target()accessor and use it to centralize wrapped-ESM init target resolution. - Add
ModuleWrapperModeto classify wrapper emission once and use it in the scope-hoisting finalizer instead of repeated inline checks. - Replace
make_esm_wrapper_stmt’s multi-arg signature withEsmWrapperStmtOptions+EsmWrapperCallKind/EsmWrapperBodyKind.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| crates/rolldown/src/stages/generate_stage/mod.rs | Exposes the new esm_init_target module from generate_stage. |
| crates/rolldown/src/stages/generate_stage/esm_init_target.rs | Adds the EsmInitTarget view and accessor over LinkingMetadata. |
| crates/rolldown/src/module_finalizers/mod.rs | Makes ModuleWrapperMode available within module_finalizers for wrapper-mode matching. |
| crates/rolldown/src/module_finalizers/impl_visit_mut.rs | Refactors wrapper emission logic to match on wrapper_mode() and uses EsmWrapperStmtOptions. |
| crates/rolldown/src/module_finalizers/finalizer_context.rs | Introduces ModuleWrapperMode and the wrapper_mode() classifier; imports the new init-target accessor. |
| crates/rolldown_ecmascript_utils/src/lib.rs | Re-exports new ESM wrapper option/enum types from ast_factory. |
| crates/rolldown_ecmascript_utils/src/ast_factory.rs | Adds EsmWrapperStmtOptions and updates make_esm_wrapper_stmt to accept it. |
55bea8b to
21be68e
Compare
21be68e to
d586b57
Compare
29e9103 to
4424048
Compare
d586b57 to
6e45894
Compare
✅ Deploy Preview for rolldown-rs canceled.
|
…init-target view (#10202) **What this does (and doesn't).** A byte-neutral decoupling refactor of how the module finalizer emits interop module wrappers. It introduces a small `EsmInitTarget` view (a wrapped-ESM module's `init_*()` wrapper symbol plus whether calling it is a no-op) with a free-standing accessor over `LinkingMetadata`, and a finalizer `ModuleWrapperMode` enum (`None` / `InteropCjs` / `InteropEsm`) that classifies a module's wrapper once. The finalizer's inline `wrap_kind()`-plus-"wrapper statement included" checks now route through these seams, and `AstFactory::make_esm_wrapper_stmt` takes an `EsmWrapperStmtOptions` struct (call/body kind) instead of a 7-argument positional list. It does NOT change output, and it deliberately omits the execution-order wrapper mode and the `HoistedFunction` declaration kind (no caller here yet) — those land with the feature. **Why.** Today the finalizer decides how to wrap a module by reading several `LinkingMetadata` fields inline at each emission site (`wrap_kind()`, `wrapper_stmt_info`, `wrapper_ref`, `init_is_noop`). The strict-execution-order work needs a second way to construct these wrappers (order wrappers) without duplicating that scattered logic. This PR pulls the "what init does this module's wrapper expose?" question behind one accessor and the "how do I wrap it?" decision behind one enum, so the later feature adds a construction path instead of editing every emission site. Grouping `make_esm_wrapper_stmt`'s arguments into a struct is the same move at the AST layer. **Validation.** `cargo test -p rolldown --test integration`: 1801 fixtures pass (same 5 pre-existing environment-only failures as the rest of the stack). `cargo clippy -p rolldown --all-targets -- -D warnings` is clean and `cargo fmt --all` produces no diff. Zero snapshot changes across ~1800 fixtures — byte-identical output. Equivalence was checked by case analysis: for every (`wrap_kind`, wrapper-included) combination, `wrapper_mode()` selects the same arm the old inline match did, and `InteropEsm`/`InteropCjs` carry exactly the `wrapper_ref` the old code unwrapped. **Stack.** Builds on layer (1) of the `strictExecutionOrder` slicing stack. Next: (3) skip CJS namespace merging under strict execution order. Later layers move/extend `EsmInitTarget` and add the execution-order wrapper mode.
6e45894 to
febbcf3
Compare
## [1.2.0] - 2026-07-15 ### 🚀 Features - dev: skip shipping factories for newly imported top-level modules (#10223) by @h-a-n-a - dev: per-client ship map for HMR patch sizing (#10208) by @h-a-n-a - dev: client-side HMR (#10164) by @h-a-n-a - dev: send a full-reload update to clients when a tsconfig changes (#10262) by @shulaoda - treat `import.meta['url']` and `import.meta['ROLLUP_FILE_URL_*']` as side-effect free (#10267) by @sapphi-red - rewrite `import.meta['url']` (#10251) by @sapphi-red - add `FILE_NOT_FOUND` error (#10220) by @sapphi-red - treat `import.meta.ROLLUP_FILE_URL_*` as side-effect free (#10217) by @sapphi-red ### 🐛 Bug Fixes - sourcemap: preserve unmapped boundaries during composition (#10254) by @hyfdev - `[format]` in `*FileNames` option for ESM format should be `es` instead of `esm` (#10214) by @sapphi-red - sourcemap: preserve coarse mappings during composition (#10249) by @hyfdev - rolldown_plugin_vite_import_glob: support tsconfig paths with `import.meta.glob` (#10167) by @sapphi-red - dev: clear tsconfig caches for bare full builds (#10276) by @shulaoda - dev: force a full rebuild when a tsconfig changes (#10261) by @shulaoda - treat rooted drive-less module ids as absolute in preserveModules naming (#10235) by @IWANABETHATGUY - watch: rebuild when tsconfig files change (#10258) by @shulaoda - watch: drop tsconfig-merged transform options on each rebuild (#10257) by @shulaoda - incorrect `EMPTY_IMPORT_META` warning for `import.meta.ROLLUP_FILE_URL_*` for CJS output (#10221) by @sapphi-red - deconflict: rename CJS locals shadowing wrapped-ESM namespace objects (#9970) by @IWANABETHATGUY - rolldown: drop the unused runtime module after entry-level external flattening (#10237) by @IWANABETHATGUY - rolldown: re-propagate has_dynamic_exports to transitive star importers (#10239) by @IWANABETHATGUY - tree-shaking: tree-shake destructured dynamic import namespace bindings (#10213) by @logaretm - s390x: use json-escape-simd 3.1.1 for big-endian JSON escaping fix (#10211) by @satyamg1620 ### 🚜 Refactor - dev: move full-reload to client side (#10207) by @h-a-n-a - readability follow-ups to the ReplaceWith migration (#10286) by @IWANABETHATGUY - replace take_in-then-write-back with ReplaceWith and by-value moves (#10285) by @Boshen - share the main resolver's cache with the transformer's tsconfig lookups (#10205) by @shulaoda - rolldown: extract the ns star-external __reExport emission rule into LinkingMetadata (#10238) by @IWANABETHATGUY - rolldown: unify link/generate diagnostics into a Diagnostics accumulator (#10234) by @IWANABETHATGUY - sourcemap_filenames: drop dead sourcemap-filename plumbing (#10189) by @IWANABETHATGUY - extract external import symbol merging into a method (#10224) by @IWANABETHATGUY - rolldown: skip CJS namespace merging under strict execution order (#10203) by @hyfdev - resolve the manual tsconfig per file instead of once at startup (#10200) by @shulaoda - rolldown: route interop ESM init emission through a shared init-target view (#10202) by @hyfdev - rolldown: collapse vestigial wrap-kind state and share chunk sort helper (#10201) by @hyfdev ### 📚 Documentation - show plugin kinds in JSDoc and each hook's description (#10218) by @sapphi-red - add an explanation about removing imports from external modules without any messages (#10215) by @sapphi-red ### ⚡ Performance - sourcemap: owned merge in SourceJoiner::join (4005->5 allocs/chunk) (#10250) by @Boshen - avoid redundant sourcemap string copies in collapse and minify paths (#10093) by @Boshen ### 🧪 Testing - code-splitting: establish strict-order review baselines (#10287) by @hyfdev - dev: add hot API test cases (#10181) by @h-a-n-a - code-splitting: normalize strict execution order variants (#10277) by @hyfdev - code-splitting: harden strict execution order coverage (#10252) by @hyfdev - code-splitting: add strict execution order regressions (#10253) by @hyfdev ### ⚙️ Miscellaneous Tasks - deps: update github actions (#10241) by @renovate[bot] - deps: update oxc to 0.140.0 (#10274) by @shulaoda - update Yunfei's GitHub username (#10275) by @hyfdev - deps: update napi (#10260) by @renovate[bot] - deps: update test262 submodule for tests (#10266) by @rolldown-guard[bot] - deps: update dependency vite-plus to v0.2.4 (#10256) by @renovate[bot] - deps: update napi (#10240) by @renovate[bot] - deps: update oxc resolver to v11.24.2 (#10245) by @renovate[bot] - deps: update rust crates (#10244) by @renovate[bot] - disable Renovate updates for idna_adapter (#10248) by @shulaoda - deps: update oxc resolver to v11.24.1 (#10232) by @renovate[bot] - deps: update rust crate oxc_sourcemap to v8.1.1 (#10233) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to ^0.27.0 (#10206) by @renovate[bot] - deps: upgrade sugar_path to v3 (#10230) by @hyfdev - add `dist-*` to `.gitignore` in sourcemap-filenames/hash-final-content fixture (#10216) by @sapphi-red - deps: update dependency rust to v1.97.0 (#10209) by @renovate[bot] ### ❤️ New Contributors * @satyamg1620 made their first contribution in [#10211](#10211) Co-authored-by: shulaoda <[email protected]>

What this does (and doesn't). A byte-neutral decoupling refactor of how the module finalizer emits interop module wrappers. It introduces a small
EsmInitTargetview (a wrapped-ESM module'sinit_*()wrapper symbol plus whether calling it is a no-op) with a free-standing accessor overLinkingMetadata, and a finalizerModuleWrapperModeenum (None/InteropCjs/InteropEsm) that classifies a module's wrapper once. The finalizer's inlinewrap_kind()-plus-"wrapper statement included" checks now route through these seams, andAstFactory::make_esm_wrapper_stmttakes anEsmWrapperStmtOptionsstruct (call/body kind) instead of a 7-argument positional list. It does NOT change output, and it deliberately omits the execution-order wrapper mode and theHoistedFunctiondeclaration kind (no caller here yet) — those land with the feature.Why. Today the finalizer decides how to wrap a module by reading several
LinkingMetadatafields inline at each emission site (wrap_kind(),wrapper_stmt_info,wrapper_ref,init_is_noop). The strict-execution-order work needs a second way to construct these wrappers (order wrappers) without duplicating that scattered logic. This PR pulls the "what init does this module's wrapper expose?" question behind one accessor and the "how do I wrap it?" decision behind one enum, so the later feature adds a construction path instead of editing every emission site. Groupingmake_esm_wrapper_stmt's arguments into a struct is the same move at the AST layer.Validation.
cargo test -p rolldown --test integration: 1801 fixtures pass (same 5 pre-existing environment-only failures as the rest of the stack).cargo clippy -p rolldown --all-targets -- -D warningsis clean andcargo fmt --allproduces no diff. Zero snapshot changes across ~1800 fixtures — byte-identical output. Equivalence was checked by case analysis: for every (wrap_kind, wrapper-included) combination,wrapper_mode()selects the same arm the old inline match did, andInteropEsm/InteropCjscarry exactly thewrapper_refthe old code unwrapped.Stack. Builds on layer (1) of the
strictExecutionOrderslicing stack. Next: (3) skip CJS namespace merging under strict execution order. Later layers move/extendEsmInitTargetand add the execution-order wrapper mode.