Skip to content

refactor(dev): reuse Vite's bundledDev server#10081

Merged
graphite-app[bot] merged 1 commit into
mainfrom
07-02-test_dev_server_use_vite_submodule
Jul 3, 2026
Merged

refactor(dev): reuse Vite's bundledDev server#10081
graphite-app[bot] merged 1 commit into
mainfrom
07-02-test_dev_server_use_vite_submodule

Conversation

@h-a-n-a

@h-a-n-a h-a-n-a commented Jul 2, 2026

Copy link
Copy Markdown
Member

What

The @rolldown/test-dev-server browser suite now runs on Vite's real full bundle mode (experimental.bundledDev) instead of the harness's own TS re-implementation of it. Vite is loaded at runtime from a vendored submodule at packages/test-dev-server/vite (pinned to vitejs/vite 578ffb8, v8.1.3) with its rolldown resolution symlink-swapped to the workspace's packages/rolldown — so every browser test exercises the local rolldown binding through the actual Vite integration.

The node fixtures suite is unchanged in behavior: Vite's bundled dev is client-environment-only, so the custom disk-serving transport (DevServer + FullBundleDevEnvironment) stays for the node platform. createDevServer() / serve() dispatch on build.platform.

Why

The browser harness was a port of Vite's full-bundle serving (in-memory output store, index-html / memory-files / lazy-bundling middlewares, asset plugin, error overlay, fallback spinner). A port drifts: what the tests validated was our copy, not what Vite ships. Running the real integration deletes ~870 lines of parity code (net −80 overall) and turns any behavioral gap into a signal about Vite itself — the migration immediately surfaced two real upstream gaps (see below).

How the submodule works

  • The submodule stays byte-pristine — no patches, no tracked-file edits; bumping it is a plain pointer update. Everything environment-specific happens in untracked files.
  • just setup-test-dev-server-vite (idempotent, vp-only): init the submodule — or re-sync it when the checkout isn't on the pinned commit (shallow, SHA-addressed fetch) — then vp install --frozen-lockfile, build packages/vite with its own pinned rolldown, and swap packages/vite/node_modules/rolldown to a symlink at the workspace package (a junction on Windows, so no elevated privileges needed). Any install inside the submodule resets the swap — re-run the script.
  • vite is not a package dependency: the harness dynamic-imports the submodule's built dist by file URL, with minimal structural types for the API slice it touches. Node-only fixtures and every CI job that never runs browser tests work without the submodule; only the dev-server workflow runs the setup step.
  • Repo-wide tools ignore packages/test-dev-server/vite/** (.gitignore for gitignore-respecting walkers, plus .typos.toml and .ls-lint.json entries).

What the harness still adds on top of Vite

src/vite-server.ts owns only the test surface Vite doesn't provide: the /_dev/status endpoint (buildSeq / moduleRegistrationSeq / live engine bundle state, used by waitForBuildStable and friends), a LoggercustomLogger adapter so serverLogs capture keeps working, port pre-reservation (Vite treats port: 0 as "use the default port"), and the old contract that a resolved createDevServer() means the initial build has settled.

It also carries two WORKAROUND blocks for genuine upstream gaps, each commented in the code and deletable once fixed in vitejs/vite:

  1. Recovery reload — after an errored build, a successful rebuild never notifies clients stuck on the error overlay or the fallback page that the build is healthy again.
  2. Stale-error replaylastBuildError is only cleared in onOutput, so a client reconnecting after an HMR-delivered recovery gets a stale error replayed.

These will be filed/upstreamed against vitejs/vite separately.

Test changes

Specs now assert on Vite's own signals: <vite-error-overlay> (shadow DOM — new errorOverlay() / errorOverlayText() helpers in ~utils), [vite] connected. / [vite] hot updated: browser logs, and Vite's server logs. On the browser platform the build entry comes from the playground's index.html module script (the fixture input field applies to the node platform only), and lazy compilation is always on — full bundle mode forces devMode.lazy: true.

Reviewer notes

  • The status endpoint and both workarounds reach Vite's private bundled-dev internals (environments.client.bundledDev, its devEngine / lastBuildError) via casts. The submodule pin keeps this stable; each bump should re-check those fields.
  • Docs updated to describe the new architecture: docs/development-guide/testing.md, internal-docs/dev-server-test-harness/implementation.md, internal-docs/lazy-compilation/implementation.md, packages/test-dev-server/tests/AGENTS.md.

🤖 Generated with Claude Code

@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 9a7799f
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a4781a26efcd9000824501c
😎 Deploy Preview https://deploy-preview-10081--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@h-a-n-a
h-a-n-a force-pushed the 07-02-test_dev_server_use_vite_submodule branch from abfa8ac to 79f6107 Compare July 2, 2026 08:56
@h-a-n-a
h-a-n-a marked this pull request as ready for review July 3, 2026 08:38
@shulaoda shulaoda self-assigned this Jul 3, 2026

shulaoda commented Jul 3, 2026

Copy link
Copy Markdown
Member

Merge activity

  • Jul 3, 8:53 AM UTC: The merge label 'graphite: merge-when-ready' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Jul 3, 8:53 AM UTC: shulaoda added this pull request to the Graphite merge queue.
  • Jul 3, 8:54 AM UTC: h-a-n-a removed this pull request from the Graphite merge queue.
  • Jul 3, 9:31 AM UTC: The merge label 'graphite: merge-when-ready' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Jul 3, 9:31 AM UTC: h-a-n-a added this pull request to the Graphite merge queue.
  • Jul 3, 9:36 AM UTC: Merged by the Graphite merge queue.

@sapphi-red

Copy link
Copy Markdown
Member

What would you do when you need to change the Vite side code?

@h-a-n-a

h-a-n-a commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

What would you do when you need to change the Vite side code?

It requires us to pin the ref to the changes in Vite.

@sapphi-red

Copy link
Copy Markdown
Member

How does the work flow happen e2e? Is it like:

  1. create a PR to vite in vitejs/vite.
  2. use that ref in the PR in rolldown
  3. merge the PR in rolldown
  4. merge the PR in vite
  5. create a PR in rolldown to update the ref to the main commit
  6. merge the PR in rolldown

@shulaoda shulaoda assigned h-a-n-a and unassigned shulaoda Jul 3, 2026
@h-a-n-a

h-a-n-a commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

How does the work flow happen e2e? Is it like:

  1. create a PR to vite in vitejs/vite.
  2. use that ref in the PR in rolldown
  3. merge the PR in rolldown
  4. merge the PR in vite
  5. create a PR in rolldown to update the ref to the main commit
  6. merge the PR in rolldown

This is correct 👍. It's a bit cumbersome in someway, but it helps us do the integration tests much easier.

## What

The `@rolldown/test-dev-server` **browser suite** now runs on Vite's real full bundle mode (`experimental.bundledDev`) instead of the harness's own TS re-implementation of it. Vite is loaded at runtime from a vendored submodule at `packages/test-dev-server/vite` (pinned to vitejs/vite `578ffb8`, v8.1.3) with its `rolldown` resolution symlink-swapped to the workspace's `packages/rolldown` — so every browser test exercises the local rolldown binding through the actual Vite integration.

The **node fixtures suite is unchanged in behavior**: Vite's bundled dev is client-environment-only, so the custom disk-serving transport (`DevServer` + `FullBundleDevEnvironment`) stays for the node platform. `createDevServer()` / `serve()` dispatch on `build.platform`.

## Why

The browser harness was a port of Vite's full-bundle serving (in-memory output store, index-html / memory-files / lazy-bundling middlewares, asset plugin, error overlay, fallback spinner). A port drifts: what the tests validated was our copy, not what Vite ships. Running the real integration deletes ~870 lines of parity code (net −80 overall) and turns any behavioral gap into a signal about Vite itself — the migration immediately surfaced two real upstream gaps (see below).

## How the submodule works

- The submodule stays **byte-pristine** — no patches, no tracked-file edits; bumping it is a plain pointer update. Everything environment-specific happens in untracked files.
- `just setup-test-dev-server-vite` (idempotent, `vp`-only): init the submodule — or re-sync it when the checkout isn't on the pinned commit (shallow, SHA-addressed fetch) — then `vp install --frozen-lockfile`, build `packages/vite` with its own pinned rolldown, and swap `packages/vite/node_modules/rolldown` to a symlink at the workspace package (a junction on Windows, so no elevated privileges needed). Any install inside the submodule resets the swap — re-run the script.
- `vite` is **not** a package dependency: the harness dynamic-imports the submodule's built dist by file URL, with minimal structural types for the API slice it touches. Node-only fixtures and every CI job that never runs browser tests work without the submodule; only the dev-server workflow runs the setup step.
- Repo-wide tools ignore `packages/test-dev-server/vite/**` (`.gitignore` for gitignore-respecting walkers, plus `.typos.toml` and `.ls-lint.json` entries).

## What the harness still adds on top of Vite

`src/vite-server.ts` owns only the test surface Vite doesn't provide: the `/_dev/status` endpoint (`buildSeq` / `moduleRegistrationSeq` / live engine bundle state, used by `waitForBuildStable` and friends), a `Logger` → `customLogger` adapter so `serverLogs` capture keeps working, port pre-reservation (Vite treats `port: 0` as "use the default port"), and the old contract that a resolved `createDevServer()` means the initial build has settled.

It also carries two `WORKAROUND` blocks for genuine upstream gaps, each commented in the code and deletable once fixed in vitejs/vite:

1. **Recovery reload** — after an errored build, a successful rebuild never notifies clients stuck on the error overlay or the fallback page that the build is healthy again.
2. **Stale-error replay** — `lastBuildError` is only cleared in `onOutput`, so a client reconnecting after an HMR-delivered recovery gets a stale error replayed.

These will be filed/upstreamed against vitejs/vite separately.

## Test changes

Specs now assert on Vite's own signals: `<vite-error-overlay>` (shadow DOM — new `errorOverlay()` / `errorOverlayText()` helpers in `~utils`), `[vite] connected.` / `[vite] hot updated:` browser logs, and Vite's server logs. On the browser platform the build entry comes from the playground's `index.html` module script (the fixture `input` field applies to the node platform only), and lazy compilation is always on — full bundle mode forces `devMode.lazy: true`.

## Reviewer notes

- The status endpoint and both workarounds reach Vite's `private` bundled-dev internals (`environments.client.bundledDev`, its `devEngine` / `lastBuildError`) via casts. The submodule pin keeps this stable; each bump should re-check those fields.
- Docs updated to describe the new architecture: `docs/development-guide/testing.md`, `internal-docs/dev-server-test-harness/implementation.md`, `internal-docs/lazy-compilation/implementation.md`, `packages/test-dev-server/tests/AGENTS.md`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@graphite-app
graphite-app Bot force-pushed the 07-02-test_dev_server_use_vite_submodule branch from 21c59d2 to 9a7799f Compare July 3, 2026 09:32
@graphite-app
graphite-app Bot merged commit 9a7799f into main Jul 3, 2026
33 checks passed
@graphite-app
graphite-app Bot deleted the 07-02-test_dev_server_use_vite_submodule branch July 3, 2026 09:36
@rolldown-guard rolldown-guard Bot mentioned this pull request Jul 8, 2026
shulaoda added a commit that referenced this pull request Jul 8, 2026
## [1.1.5] - 2026-07-08

### 🚀 Features

- detect top-level import-binding reads as execution-order sensitive (#10180) by @hyf0
- sourcemap_filenames: add a sourcemapFileNames option (#9271) by @V1OL3TF0X
- binding: record plugin hook result kind in tracing spans (#10154) by @IWANABETHATGUY
- linking: skip side-effect-free modules in per-entry reachability (#10111) by @IWANABETHATGUY
- improve error message for unresolved virtual imports (#10156) by @sapphi-red
- add descriptive metadata to plugin API (#10106) by @sapphi-red
- add `--configLoader=native` option (#10118) by @sapphi-red

### 🐛 Bug Fixes

- improve invalid annotation warnings (#10185) by @hyf0
- keep deduplicated asset filenames stable once they can be observed (#10191) by @shulaoda
- sourcemap_filenames: use public option name in pattern errors (#10188) by @IWANABETHATGUY
- sourcemap_filenames: hash prepared sourcemap content (#10178) by @hyf0
- tree-shake unused circular declarators exported via export list (#10166) by @IWANABETHATGUY
- dev: don't panic when an HMR rebuild hits an unresolved import (#10162) by @shulaoda
- propagate errors from output.globals function (#9880) by @shulaoda
- dev: revert cache mutations when a partial scan fails (#10110) by @shulaoda
- dev: update importer relationships of cached modules in incremental build (#10107) by @shulaoda
- hmr: fall back to full reload when a changed module is not registered as executed (#10132) by @shulaoda
- chunk-optimizer: follow entry facade edges in runtime placement cycle check (#10101) by @hyf0
- dev: ignore watcher events after close (#10113) by @hyf0
- emit async wrapper for TLA modules under onDemandWrapping (#10086) by @IWANABETHATGUY
- gate sideEffects:false modules' side effects on body demand (#10080) by @IWANABETHATGUY
- rolldown_plugin_vite_resolve: return empty object for `browser: false` mapped modules (#10082) by @sapphi-red
- reset the word-boundary state on newline in Hires::Boundary sourcemaps (#10025) by @shulaoda
- trim an emptied chunk's outro/intro instead of skipping past it (#10029) by @shulaoda
- test each edited chunk's own start against indent exclude ranges (#10026) by @shulaoda
- preserve sourcemap mappings for indented lines when a CJS module shares the chunk (#10074) by @hyf0

### 🚜 Refactor

- separate tree-shaking side effects from execution order sensitivity (#10168) by @hyf0
- type construct_vite_preload_call to take an ObjectPattern (#10135) by @shulaoda
- treeshake: single-source the own-export classification shared with the lazy-barrel loader (#10098) by @IWANABETHATGUY
- dev: reuse Vite's bundledDev server (#10081) by @h-a-n-a
- clippy: ban std HashMap/HashSet in favour of FxHashMap/FxHashSet (#10108) by @Boshen
- treeshake: make body demand a second module bit instead of a stmt multimap (#10097) by @IWANABETHATGUY
- seal used_symbol_refs by construction after its last writer (#10091) by @hyf0
- treeshake: replace inclusion mutual recursion with a worklist engine (#10096) by @IWANABETHATGUY
- treeshake: split include_statements.rs into focused modules (#10095) by @IWANABETHATGUY
- drop redundant is_user_defined filter on partitioned entries (#10050) by @shulaoda
- project the retained export interface out of used_symbol_refs (#10089) by @hyf0
- track used external symbols separately from used_symbol_refs (#10088) by @hyf0
- make module namespace inclusion an explicit linking metadata field (#10087) by @hyf0
- rename statement evaluation metadata (#10078) by @hyf0

### 📚 Documentation

- virtual modules user-facing id convention (#10155) by @sapphi-red
- cli: clarify disabling boolean/object flags like codeSplitting (#10153) by @IWANABETHATGUY
- chore: remove Vite+ alpha banner (#10105) by @mdong1909
- write down the used_symbol_refs contract (#10090) by @hyf0
- dev/lazy: update design and implementation (#10079) by @h-a-n-a

### ⚡ Performance

- ast_scanner: stop order-sensitivity checks once a module is flagged (#10190) by @IWANABETHATGUY
- return impl ExactSizeIterator from slice-backed accessors (#10133) by @Boshen
- binding: box dev and watcher napi futures (#10103) by @Boshen

### 🧪 Testing

- move string_wizard replace unit tests to the JS magic-string suite (#10176) by @IWANABETHATGUY
- dev: assert incremental scan state matches a fresh full build after each HMR step (#10115) by @shulaoda
- dev: restore runtime assertions of delete_file_not_used_anymore (#10112) by @shulaoda
- dev: fix flaky dev server tests in CI (#10152) by @h-a-n-a
- add regression test for #10099 (lazyBarrel drops default-import binding but keeps its property reads) (#10109) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- deploy website to Void via GitHub OIDC (#10192) by @Boshen
- deps: update oxc to 0.139.0 (#10161) by @shulaoda
- deps: update test262 submodule for tests (#10160) by @rolldown-guard[bot]
- rolldown_plugin_utils: remove dead asset-url and css scaffolding (#10131) by @shulaoda
- deps: revert vite-plus to v0.2.1 (#10148) by @shulaoda
- deps: update github actions (#10141) by @renovate[bot]
- deps: update dependency rust to v1.96.1 (#10145) by @renovate[bot]
- deps: update npm packages (#10142) by @renovate[bot]
- deps: update rust crates (#10143) by @renovate[bot]
- deps: update napi to v3.10.3 (#10121) by @renovate[bot]
- rolldown_utils: remove unused time module (#10138) by @shulaoda
- remove dead CopyModulePlugin::is_active method (#10129) by @shulaoda
- remove dead LazyCompilationContext::is_lazy_module method (#10128) by @shulaoda
- remove dead BuildDiagnostic::downcast_ref method (#10127) by @shulaoda
- deps: update dependency vite-plus to v0.2.2 (#10084) by @renovate[bot]
- deps: update rust crate oxc_sourcemap to v8.1.0 (#10122) by @renovate[bot]
- deps: update crate-ci/typos action to v1.48.0 (#10124) by @renovate[bot]
- enable more clippy restriction lints (#10114) by @Boshen
- deps: update rust dependencies (#10100) by @Boshen
- deps: update oxc resolver to v11.23.0 (#10083) by @renovate[bot]

### ◀️ Revert

- Revert "chore(deps): revert vite-plus to v0.2.1" (#10157) by @h-a-n-a
- "fix(hmr): fall back to full reload when a changed module is not registered as executed (#10132)" (#10151) by @shulaoda

### ❤️ New Contributors

* @V1OL3TF0X made their first contribution in [#9271](#9271)

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.

3 participants