Skip to content

fix(dev): force incremental build on in DevEngine::new - #10958

Merged
h-a-n-a merged 1 commit into
rolldown:mainfrom
o-alexandrov:fix/dev-engine-force-incremental-build
Sep 23, 2026
Merged

h-a-n-a merged 1 commit into
rolldown:mainfrom
o-alexandrov:fix/dev-engine-force-incremental-build

Conversation

@o-alexandrov

@o-alexandrov o-alexandrov commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Closes #10957.

Problem

HmrStage's module_table() / index_ecma_ast() accessors call ScanStageCache::get_snapshot(), which unwraps unconditionally (crates/rolldown/src/types/scan_stage_cache.rs:78). The snapshot only exists when experimental.incremental_build is on.

prepare_build_context already forces that flag whenever experimental.dev_mode is set (crates/rolldown/src/utils/prepare_build_context.rs:279), so Vite's bundledDev is unaffected. But dev() accepts input options with neither flag. The engine constructs and builds fine, then panics the worker thread on the first file change:

thread 'rolldown-worker' panicked at crates/rolldown/src/types/scan_stage_cache.rs:79:28:
called `Option::unwrap()` on a `None` value

thread 'rolldown-worker' panicked at futures-util/src/future/future/shared.rs:300:25:
inner future panicked during poll

error: inner future panicked during poll  code: "GenericFailure"

The panic poisons the shared future, so the engine is unusable afterwards. Same unwrap as #9728 / #9730, reached from a plain transform hook using addWatchFile.

Fix

A dev engine cannot function without the snapshot, so DevEngine::new enables incremental_build at construction instead of leaving the invalid configuration representable. This mirrors what prepare_build_context already does for dev_mode.

I also prototyped the alternative (an early has_snapshot() check in compute_hmr_update_for_file_changes returning HmrUpdate::FullReload, matching the existing guard in compile_lazy_entry at hmr_stage.rs:542). It works, but it is dead code once the option can no longer be off, so I dropped it to keep the diff minimal. Happy to switch if you prefer defence in depth there.

Note on [lib] test = false

crates/rolldown_dev/Cargo.toml had test = false under [lib], so unit tests in this crate were never compiled or run, including by just test-rust (cargo test --workspace --exclude rolldown_binding). The crate has no other tests, so this drops the setting rather than working around it. Restore that line if it was deliberate, and tell me where you would prefer the regression test to live.

Verification

  • New unit test dev_engine_forces_incremental_build_on asserts the constructed bundler has incremental build enabled. cargo test -p rolldown_dev runs it and passes (1 passed).
  • End to end through the JS API: built rolldown_binding with this change, swapped it into a vite@8.3.0 install, and ran a dev() script with a transform hook calling addWatchFile on a graph module, with no devMode and no incrementalBuild. Before: the scan_stage_cache panic above. After: no panic, and the engine emits a correct Patch carrying the re-transformed code.
  • cargo test -p rolldown --test integration: 2006 passed, 5 failed. The same 5 fail identically on unmodified main in my environment (missing fixture node_modules for cjs_module_lexer_compat / util_deprecate, and the test262 submodule not cloned).
  • cargo fmt and cargo clippy -p rolldown_dev --all-targets are clean.
  • Not verified locally: the full just test-rust workspace suite and the Node test suites.

The HMR stage reads the scan-stage snapshot through module_table() and
index_ecma_ast(), which unwrap ScanStageCache::get_snapshot(). That
snapshot is only stored when experimental.incrementalBuild is on.

prepare_build_context already forces it whenever experimental.devMode is
set, so Vite is unaffected, but dev() accepts input options with neither
flag. The engine then boots fine and panics the rolldown-worker thread
on the first file change, unwrapping a None snapshot at
crates/rolldown/src/types/scan_stage_cache.rs:79. The panic poisons the
shared future, so the engine is unusable afterwards.

A dev engine cannot work without the snapshot, so enable it at
construction rather than leaving the invalid configuration
representable.

Closes rolldown#10957
@o-alexandrov
o-alexandrov force-pushed the fix/dev-engine-force-incremental-build branch from 9a49b79 to 40ffc47 Compare September 22, 2026 17:42
@codspeed

codspeed Bot commented Sep 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing o-alexandrov:fix/dev-engine-force-incremental-build (40ffc47) with main (5c676e5)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@h-a-n-a h-a-n-a left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@h-a-n-a
h-a-n-a merged commit 6da8ed6 into rolldown:main Sep 23, 2026
33 checks passed
@rolldown-guard rolldown-guard Bot mentioned this pull request Sep 23, 2026
shulaoda added a commit that referenced this pull request Sep 23, 2026
## [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>
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.

[Panic]: dev() without experimental.devMode panics in scan_stage_cache on the first file change instead of erroring

2 participants