Skip to content

fix(bundled-dev): errors should be kept when incremental build fails#22617

Merged
sapphi-red merged 6 commits into
mainfrom
fix-fbm
Jun 23, 2026
Merged

fix(bundled-dev): errors should be kept when incremental build fails#22617
sapphi-red merged 6 commits into
mainfrom
fix-fbm

Conversation

@h-a-n-a

@h-a-n-a h-a-n-a commented Jun 5, 2026

Copy link
Copy Markdown
Member

This PR fixed a case where errors are not kept after page refreshes when incremental build fails.

For details, please refer to rolldown/rolldown#9652. In short, Rolldown previously retried every failed builds whether it's full build or it's an incremental rebuild. This always printed the error. After Rolldown's PR rolldown/rolldown#9552, this retry logic had gone. This means that the errors are not getting emitted, causing errors are not being able to get printed again.

In Rolldown's fix rolldown/rolldown#9652, based on the principles discussed within the team, we came to an agreement that we hold a very conservative stance on re-bundling. Thus, in this PR, Vite, as a consumer of Rolldown's dev engine, it caches the error and only rebuilds when it's necessary. This PR basically adapts the current dev server with Rolldown's new implementation.

For more details on the fix, refer to Rolldown's fix: rolldown/rolldown#9652

Vite test harness and dev server are in sync with Rolldown.

graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request Jun 11, 2026
…9652)

## Design Principles

This PR mainly clarifies a few principles that we hold while implementing Rolldown's dev engine and Vite's full-bundle mode dev server. Mostly are written in `meta/design/dev-engine.md`. Just to give you a briefing, here're these things:

1. **Be conservative** — rebuild only when the bundle is stale, and defer it until the full bundle is actually requested (e.g. a page refresh). By default we ship HMR-only output.
2. **Replay errors when needed** — the dev *engine* is stateless across HTTP requests and never caches results from `onHmrUpdates`/`onOutput`; caching and replay are the dev *server*'s job, so errors survive a refresh.
3. **File changes are the only recovery trigger** — after a failed build the engine waits for a file edit (Vite config or source). Not a page refresh, elapsed time, or UI dismissal.
4. **Build errors are always recoverable** — every error via `on_output`/`on_hmr_updates` is treated as a user error (source/plugin), fixable by editing source. Only a panic is unrecoverable.

### Problem

  #9552 made `ensure_latest_bundle_output` return `None` in `FullBuildFailed` /
  `Failed` instead of retrying a build. That fixed a double-`onOutput` and an
  infinite loop — but the retry it removed had been doing double duty: on a
  browser refresh, the dev-server middleware calls `ensureLatestBuildOutput`,
  the retry reran the build, and `onOutput` re-fired the error to the client.

  With the retry gone, a refresh no longer reruns the build, so **a project whose
  build output contains an error stops re-emitting that error on refresh** — the
  error overlay disappears even though the build is still broken.

  The right fix is *not* to rerun the build on access (that brings back the loop
  and the double-emit). It's to make `Failed` / `FullBuildFailed` work as the
  genuine resting states they now are:

  - the error must survive a refresh **without** a rebuild, and
  - recovery must be driven entirely by file changes, re-running the stage that
    actually broke.

### Changes

  **1. Expose errored-and-stale state to the consumer.**
  Snapshot field `last_full_build_failed` → `last_build_errored`, now true for
  *both* `FullBuildFailed` and `Failed { .. }`. This is the predicate the
  consumer pairs with `has_stale_output` to (a) replay its cached build error on
  client reconnect instead of triggering a doomed regen, and (b) avoid the
  access-triggered reload loop against a bundle the engine won't regenerate.

  **2. File-change-driven, stage-aware recovery.**
  Because access no longer retries, the next file change is the only recovery
  trigger — and it must re-run the broken stage:

  - New `ErrorStage` enum (`Hmr` | `Rebuild`); `BundlingTask` tracks
    `hmr_errored` / `rebuild_errored` and collapses them with precedence
    `Rebuild > Hmr` (covers the `Hmr → HmrRebuild` auto-upgrade that then fails
    in rebuild).
  - `CoordinatorState::Failed { last_error_stage }` carries the stage;
    `handle_file_changes` forces `HmrRebuild` on a `Rebuild`-stage failure
    (regardless of `rebuild_strategy`) so the rebuild re-runs, and uses an `Hmr`
    task for an `Hmr`-stage failure.

### Tests

Tests will be added when Rolldown's test-dev-server is fully aligned with Vite's full-bundle mode server.

This PR is meant to be released along with vitejs/vite#22617.
@h-a-n-a h-a-n-a force-pushed the fix-fbm branch 3 times, most recently from e936374 to 41fead5 Compare June 12, 2026 06:00
@h-a-n-a h-a-n-a changed the base branch from main to 06-16-feat_update_rolldown June 19, 2026 15:38
@h-a-n-a h-a-n-a marked this pull request as ready for review June 19, 2026 15:42
@h-a-n-a h-a-n-a requested a review from sapphi-red June 19, 2026 15:42
Comment thread packages/vite/src/node/server/bundledDev.ts
@shulaoda shulaoda force-pushed the 06-16-feat_update_rolldown branch from a1e3b31 to 9cd097e Compare June 22, 2026 06:02
Base automatically changed from 06-16-feat_update_rolldown to main June 23, 2026 01:51
@sapphi-red sapphi-red changed the title fix(bundled-dev): errors should be kept when incremental build fails fix(bundled-dev): errors should be kept when incremental build fail Jun 23, 2026
@sapphi-red sapphi-red changed the title fix(bundled-dev): errors should be kept when incremental build fail fix(bundled-dev): errors should be kept when incremental build fails Jun 23, 2026
@sapphi-red sapphi-red merged commit 9a0dd48 into main Jun 23, 2026
19 checks passed
@sapphi-red sapphi-red deleted the fix-fbm branch June 23, 2026 01:54
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Jun 24, 2026
| datasource | package | from   | to    |
| ---------- | ------- | ------ | ----- |
| npm        | vite    | 8.0.14 | 8.1.0 |


## [v8.1.0](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#810-2026-06-23)

##### Features

- extend `server.fs.deny` list with common files ([#22707](vitejs/vite#22707)) ([61ba8fd](vitejs/vite@61ba8fd))
- update rolldown to 1.1.2 ([#22695](vitejs/vite#22695)) ([4f008a6](vitejs/vite@4f008a6))
- use `~` for Rolldown ([#22693](vitejs/vite#22693)) ([9928722](vitejs/vite@9928722))

##### Bug Fixes

- **bundled-dev:** errors should be kept when incremental build fails ([#22617](vitejs/vite#22617)) ([9a0dd48](vitejs/vite@9a0dd48))
- cache falsy values in perEnvironmentState ([#22715](vitejs/vite#22715)) ([0e91e79](vitejs/vite@0e91e79))
- **glob:** respect caseSensitive option in hmr matcher ([#22711](vitejs/vite#22711)) ([65f525e](vitejs/vite@65f525e))
- **html:** omit nonce on import map when cspNonce is unset ([#22713](vitejs/vite#22713)) ([8340bb5](vitejs/vite@8340bb5))
- **optimizer:** skip null-valued exports in expandGlobIds glob resolution ([#22611](vitejs/vite#22611)) ([8b9f5cd](vitejs/vite@8b9f5cd))
- resolved build options should be kept as a getter ([#22691](vitejs/vite#22691)) ([3527191](vitejs/vite@3527191))
- **server:** handle malformed URI in memory files middleware ([#22714](vitejs/vite#22714)) ([df9e0a5](vitejs/vite@df9e0a5))
- use literal envPrefix queries for Vite Task ([#22706](vitejs/vite#22706)) ([da72733](vitejs/vite@da72733))
- warn on deprecated envFile ([#22555](vitejs/vite#22555)) ([ed7b283](vitejs/vite@ed7b283))

##### Code Refactoring

- **client:** inline dev-id value in CSS selector ([#22736](vitejs/vite#22736)) ([57f59bc](vitejs/vite@57f59bc))
- remove unused removeRawQuery util ([#22724](vitejs/vite#22724)) ([403cc60](vitejs/vite@403cc60))
- use `rolldownOptions` property for chunkImportMap ([#22692](vitejs/vite#22692)) ([8e8816c](vitejs/vite@8e8816c))


## [v8.0.16](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8016-2026-06-01-small)

##### Bug Fixes

- **deps:** reject UNC paths for launch-editor-middleware ([#22571](vitejs/vite#22571)) ([50b9512](vitejs/vite@50b9512))
- reject windows alternate paths ([#22572](vitejs/vite#22572)) ([dc245c7](vitejs/vite@dc245c7))


## [v8.0.15](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8015-2026-06-01-small)

##### Features

- send 408 on request timeout ([#22476](vitejs/vite#22476)) ([c85c9ee](vitejs/vite@c85c9ee))
- update rolldown to 1.0.3 ([#22538](vitejs/vite#22538)) ([646dbed](vitejs/vite@646dbed))

##### Bug Fixes

- capitalize error messages and remove spurious space in parse error ([#22488](vitejs/vite#22488)) ([85a0eff](vitejs/vite@85a0eff))
- **deps:** update all non-major dependencies ([#22511](vitejs/vite#22511)) ([2686d7d](vitejs/vite@2686d7d))
- **dev:** fix html-proxy cache key mismatch for /@fs/ HTML paths ([#21762](vitejs/vite#21762)) ([47c4213](vitejs/vite@47c4213))
- **glob:** error on relative glob in virtual module when no files match ([#22497](vitejs/vite#22497)) ([5c8e98f](vitejs/vite@5c8e98f))
- **optimizer:** close the rolldown bundle when write() rejects ([#22528](vitejs/vite#22528)) ([e3cfb9d](vitejs/vite@e3cfb9d))
- **resolve:** provide onWarn for viteResolvePlugin in JS plugin containers ([#22509](vitejs/vite#22509)) ([40985f1](vitejs/vite@40985f1))

##### Miscellaneous Chores

- **deps:** update rolldown-related dependencies ([#22566](vitejs/vite#22566)) ([3052a67](vitejs/vite@3052a67))

##### Code Refactoring

- correct logic in `collectAllModules` function ([#22562](vitejs/vite#22562)) ([6978a9c](vitejs/vite@6978a9c))
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Jun 24, 2026
| datasource | package | from   | to    |
| ---------- | ------- | ------ | ----- |
| npm        | vite    | 8.0.14 | 8.1.0 |


## [v8.1.0](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#810-2026-06-23)

##### Features

- extend `server.fs.deny` list with common files ([#22707](vitejs/vite#22707)) ([61ba8fd](vitejs/vite@61ba8fd))
- update rolldown to 1.1.2 ([#22695](vitejs/vite#22695)) ([4f008a6](vitejs/vite@4f008a6))
- use `~` for Rolldown ([#22693](vitejs/vite#22693)) ([9928722](vitejs/vite@9928722))

##### Bug Fixes

- **bundled-dev:** errors should be kept when incremental build fails ([#22617](vitejs/vite#22617)) ([9a0dd48](vitejs/vite@9a0dd48))
- cache falsy values in perEnvironmentState ([#22715](vitejs/vite#22715)) ([0e91e79](vitejs/vite@0e91e79))
- **glob:** respect caseSensitive option in hmr matcher ([#22711](vitejs/vite#22711)) ([65f525e](vitejs/vite@65f525e))
- **html:** omit nonce on import map when cspNonce is unset ([#22713](vitejs/vite#22713)) ([8340bb5](vitejs/vite@8340bb5))
- **optimizer:** skip null-valued exports in expandGlobIds glob resolution ([#22611](vitejs/vite#22611)) ([8b9f5cd](vitejs/vite@8b9f5cd))
- resolved build options should be kept as a getter ([#22691](vitejs/vite#22691)) ([3527191](vitejs/vite@3527191))
- **server:** handle malformed URI in memory files middleware ([#22714](vitejs/vite#22714)) ([df9e0a5](vitejs/vite@df9e0a5))
- use literal envPrefix queries for Vite Task ([#22706](vitejs/vite#22706)) ([da72733](vitejs/vite@da72733))
- warn on deprecated envFile ([#22555](vitejs/vite#22555)) ([ed7b283](vitejs/vite@ed7b283))

##### Code Refactoring

- **client:** inline dev-id value in CSS selector ([#22736](vitejs/vite#22736)) ([57f59bc](vitejs/vite@57f59bc))
- remove unused removeRawQuery util ([#22724](vitejs/vite#22724)) ([403cc60](vitejs/vite@403cc60))
- use `rolldownOptions` property for chunkImportMap ([#22692](vitejs/vite#22692)) ([8e8816c](vitejs/vite@8e8816c))


## [v8.0.16](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8016-2026-06-01-small)

##### Bug Fixes

- **deps:** reject UNC paths for launch-editor-middleware ([#22571](vitejs/vite#22571)) ([50b9512](vitejs/vite@50b9512))
- reject windows alternate paths ([#22572](vitejs/vite#22572)) ([dc245c7](vitejs/vite@dc245c7))


## [v8.0.15](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8015-2026-06-01-small)

##### Features

- send 408 on request timeout ([#22476](vitejs/vite#22476)) ([c85c9ee](vitejs/vite@c85c9ee))
- update rolldown to 1.0.3 ([#22538](vitejs/vite#22538)) ([646dbed](vitejs/vite@646dbed))

##### Bug Fixes

- capitalize error messages and remove spurious space in parse error ([#22488](vitejs/vite#22488)) ([85a0eff](vitejs/vite@85a0eff))
- **deps:** update all non-major dependencies ([#22511](vitejs/vite#22511)) ([2686d7d](vitejs/vite@2686d7d))
- **dev:** fix html-proxy cache key mismatch for /@fs/ HTML paths ([#21762](vitejs/vite#21762)) ([47c4213](vitejs/vite@47c4213))
- **glob:** error on relative glob in virtual module when no files match ([#22497](vitejs/vite#22497)) ([5c8e98f](vitejs/vite@5c8e98f))
- **optimizer:** close the rolldown bundle when write() rejects ([#22528](vitejs/vite#22528)) ([e3cfb9d](vitejs/vite@e3cfb9d))
- **resolve:** provide onWarn for viteResolvePlugin in JS plugin containers ([#22509](vitejs/vite#22509)) ([40985f1](vitejs/vite@40985f1))

##### Miscellaneous Chores

- **deps:** update rolldown-related dependencies ([#22566](vitejs/vite#22566)) ([3052a67](vitejs/vite@3052a67))

##### Code Refactoring

- correct logic in `collectAllModules` function ([#22562](vitejs/vite#22562)) ([6978a9c](vitejs/vite@6978a9c))
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.

3 participants