fix(dev/lazy): avoid module reinitialization in lazy compilation patches#9179
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. |
ff3c657 to
d2c0b1d
Compare
7487a99 to
496f339
Compare
d2c0b1d to
01b0ad7
Compare
@rolldown/browser
@rolldown/debug
@rolldown/pluginutils
rolldown
@rolldown/binding-android-arm64
@rolldown/binding-darwin-arm64
@rolldown/binding-darwin-x64
@rolldown/binding-freebsd-x64
@rolldown/binding-linux-arm-gnueabihf
@rolldown/binding-linux-arm64-gnu
@rolldown/binding-linux-arm64-musl
@rolldown/binding-linux-ppc64-gnu
@rolldown/binding-linux-s390x-gnu
@rolldown/binding-linux-x64-gnu
@rolldown/binding-linux-x64-musl
@rolldown/binding-openharmony-arm64
@rolldown/binding-wasm32-wasi
@rolldown/binding-win32-arm64-msvc
@rolldown/binding-win32-x64-msvc
commit: |
01b0ad7 to
43390b3
Compare
43390b3 to
f3c3c87
Compare
496f339 to
7f39bc5
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
7f39bc5 to
b8fc6ba
Compare
f3c3c87 to
b4dc062
Compare
b4dc062 to
90d94c1
Compare
Merge activity
|
…hes (#9179) ## Summary When a module appears in two lazy-compilation chunks served concurrently (e.g. two `compileLazyEntry` requests racing for `shared.js`), the browser runs the module body twice. Non-idempotent module code blows up — concretely, `Object.defineProperty(this, "key", { configurable: false })` on the second run throws `Cannot redefine property`. This PR makes module initializers opt into runtime-side deduping: - `createEsmInitializer` / `createCjsInitializer` take a new `dedup` flag. When truthy and the stable id is already in `runtime.modules`, the factory is skipped (or, for CJS, the registered exports are reused). - The Rust emitter (`HmrAstFinalizer`) gains a `dedup_module_initializer` field. `compile_lazy_entry` sets it to `true`; the two HMR-patch render paths set it to `false` so HMR keeps re-running the factory and replacing exports — that's the whole point of a patch. - The stable module id is now passed into the factory as `__rolldown_module_id__`, so `registerModule` and `createModuleHotContext` reference it by identifier instead of duplicating the string literal. - The lazy proxy template deletes its own `runtime.modules` entry before triggering the real chunk, so the dedup gate on the real module's id starts from a clean slate. The dedup gate is a workaround. The proper fix is a runtime API for module disposal that HMR can call before re-execution; see the TODO on `HmrAstFinalizer::dedup_module_initializer`. ## Test plan - [x] `cargo test -p rolldown --test integration` (1690 passed, 70 ignored) - [x] `cargo clippy -p rolldown --tests --no-deps` clean
b8fc6ba to
0689e35
Compare
90d94c1 to
27026df
Compare
## [1.0.0-rc.18] - 2026-04-29 ### 💥 BREAKING CHANGES - optimization: default unspecified inlineConst.mode to smart (#9248) by @IWANABETHATGUY ### 🐛 Bug Fixes - rolldown_plugin_vite_import_glob: return error instead of panicking when virtual module uses a relative glob (#9241) by @shulaoda - binding: treat empty inlineConst object as omitted (#9247) by @IWANABETHATGUY - rolldown: keep enum declaration for optional-chain access (#9229) by @Dunqing - link_stage: restore inline let-else in exports-kind filter (#9237) by @IWANABETHATGUY - dev/lazy: avoid module reinitialization in lazy compilation patches (#9179) by @h-a-n-a - dev: visit identifier references for runtime rewrites in HMR finalizer (#9191) by @h-a-n-a - chunk-optimizer: pick dominator for runtime placement to avoid cycles (#9164) by @IWANABETHATGUY - make `this.emitFile` chunk path synchronous to avoid deadlock (#9031) by @lazarv - use sentinel id for `browser: false` ignored modules (#9192) by @shulaoda - prevent chunk optimizer from creating import cycles (#9228) by @IWANABETHATGUY ### 🚜 Refactor - replace tokio::sync::Mutex with std::sync::Mutex for non-IO data (#9176) by @shulaoda - rolldown_plugin_vite_import_glob: do not rewrite import path for absolute base (#9195) by @shulaoda - runtime_helper: wrap DependedRuntimeHelperMap in a struct (#9215) by @IWANABETHATGUY - drop redundant clear() in determine_safely_merge_cjs_ns (#9206) by @IWANABETHATGUY - clean up generate_lazy_export (#9208) by @IWANABETHATGUY - bitset: return bool from set_bit to fuse guard-and-set (#9207) by @IWANABETHATGUY - link_stage: simplify exports-kind filter and clarify safety comments (#9205) by @IWANABETHATGUY ### 📚 Documentation - determine_module_exports_kind (#9252) by @IWANABETHATGUY - fix dead link to esbuild ESM/CJS interop tests (#9230) by @Copilot - remove CSS bundling references (#9234) by @shulaoda - correct IncrementalFullBuild row in BundleMode table (#9214) by @IWANABETHATGUY - design: add bundler data lifecycle design doc (#9212) by @hyf0 - remove minifier alpha status notices (#9202) by @sapphi-red ### ⚙️ Miscellaneous Tasks - upgrade oxc to 0.128.0 (#9260) by @shulaoda - deps: bump rolldown-ariadne to 0.6.0 (#9254) by @IWANABETHATGUY - deps: update github actions (#9259) by @renovate[bot] - deps: update github actions (#9258) by @renovate[bot] - remove renovate overrides (#9257) by @Boshen - use ubuntu-latest for security workflow (#9256) by @Boshen - notify Discord around release publish (#9251) by @Boshen - add release environment to npm publish workflow (#9250) by @Boshen - justfile: drop the `--` separator before forwarded args in `vp run` (#9246) by @shulaoda - deps: update test262 submodule for tests (#9243) by @sapphi-red - add more tracing instrumentations (#9220) by @sapphi-red - rolldown_plugin_vite_import_glob: remove outdated sourcemap doc comment (#9213) by @shulaoda - update security workflow (#9201) by @Boshen ### ❤️ New Contributors * @lazarv made their first contribution in [#9031](#9031) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>

Summary
When a module appears in two lazy-compilation chunks served concurrently
(e.g. two
compileLazyEntryrequests racing forshared.js), the browserruns the module body twice. Non-idempotent module code blows up — concretely,
Object.defineProperty(this, "key", { configurable: false })on the secondrun throws
Cannot redefine property.This PR makes module initializers opt into runtime-side deduping:
createEsmInitializer/createCjsInitializertake a newdedupflag.When truthy and the stable id is already in
runtime.modules, the factoryis skipped (or, for CJS, the registered exports are reused).
HmrAstFinalizer) gains adedup_module_initializerfield.
compile_lazy_entrysets it totrue; the two HMR-patch renderpaths set it to
falseso HMR keeps re-running the factory and replacingexports — that's the whole point of a patch.
__rolldown_module_id__, soregisterModuleandcreateModuleHotContextreference it by identifier instead of duplicating the string literal.
runtime.modulesentry beforetriggering the real chunk, so the dedup gate on the real module's id
starts from a clean slate.
The dedup gate is a workaround. The proper fix is a runtime API for module
disposal that HMR can call before re-execution; see the TODO on
HmrAstFinalizer::dedup_module_initializer.Test plan
cargo test -p rolldown --test integration(1690 passed, 70 ignored)cargo clippy -p rolldown --tests --no-depsclean