Skip to content

refactor(rolldown): route interop ESM init emission through a shared init-target view#10202

Merged
graphite-app[bot] merged 1 commit into
mainfrom
strict-prep-init-target-view
Jul 10, 2026
Merged

refactor(rolldown): route interop ESM init emission through a shared init-target view#10202
graphite-app[bot] merged 1 commit into
mainfrom
strict-prep-init-target-view

Conversation

@hyfdev

@hyfdev hyfdev commented Jul 9, 2026

Copy link
Copy Markdown
Member

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.

hyfdev commented Jul 9, 2026

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.

hyfdev commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Merge activity

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ModuleWrapperMode to 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 with EsmWrapperStmtOptions + 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.

Comment thread crates/rolldown/src/module_finalizers/finalizer_context.rs Outdated
@hyfdev
hyfdev force-pushed the strict-prep-init-target-view branch from 55bea8b to 21be68e Compare July 9, 2026 12:44
@hyfdev
hyfdev changed the base branch from strict-prep-wrap-kind-state to graphite-base/10202 July 9, 2026 12:45
@graphite-app
graphite-app Bot force-pushed the strict-prep-init-target-view branch from 21be68e to d586b57 Compare July 9, 2026 14:25
@graphite-app
graphite-app Bot force-pushed the graphite-base/10202 branch from 29e9103 to 4424048 Compare July 9, 2026 14:25
@graphite-app
graphite-app Bot changed the base branch from graphite-base/10202 to main July 9, 2026 14:26
@graphite-app
graphite-app Bot force-pushed the strict-prep-init-target-view branch from d586b57 to 6e45894 Compare July 9, 2026 14:26
@netlify

netlify Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

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

…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.
@graphite-app
graphite-app Bot force-pushed the strict-prep-init-target-view branch from 6e45894 to febbcf3 Compare July 10, 2026 05:34
@graphite-app
graphite-app Bot merged commit febbcf3 into main Jul 10, 2026
33 of 34 checks passed
@graphite-app
graphite-app Bot deleted the strict-prep-init-target-view branch July 10, 2026 05:39
@rolldown-guard rolldown-guard Bot mentioned this pull request Jul 15, 2026
shulaoda added a commit that referenced this pull request Jul 15, 2026
## [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]>
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.

4 participants