Skip to content

fix(dev): serve assets emitted during HMR/lazy compile (vite#22596)#9815

Merged
graphite-app[bot] merged 1 commit into
mainfrom
06-17-fix_dev_vite_22596
Jun 19, 2026
Merged

fix(dev): serve assets emitted during HMR/lazy compile (vite#22596)#9815
graphite-app[bot] merged 1 commit into
mainfrom
06-17-fix_dev_vite_22596

Conversation

@h-a-n-a

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

Copy link
Copy Markdown
Member

Summary

In Full Bundle Mode, assets emitted while generating an HMR patch or compiling a lazy entry (e.g. an image newly imported by the changed/lazy module) never reached the dev consumer — those paths skip the generate stage, so they never fire onOutput, the only asset-delivery channel. The browser then 404s the asset until a full reload.

This adds an onAdditionalAssets dev callback that delivers those assets before the patch/lazy code reaches the client, so a consumer that serves built files (Vite) can serve them in time.

Fixes vitejs/vite#22596 and its HMR analog. Also addresses #9812 and #9817 in the Vite / bundled-dev path (there the asset URLs are resolved eagerly at load/transform, and this PR delivers the bytes). Purely additive — no breaking changes.

Changes

  • New onAdditionalAssets callback through every layer (rolldown_dev → napi → TS). Fires before the patch/lazy code reaches the client; the shared emitted_files gate keeps each asset delivered exactly once.
  • Carries a BundleOutput at the Rust boundary so add_additional_files warnings get the same treatment as onOutput; the napi layer delivers BindingOutputs directly (no error path — the bytes are already emitted).
  • test-dev-server reference consumer: ports the bundled-dev branch of Vite's vite:asset plugin (eager URL resolution at load; no __ROLLDOWN_ASSET__ placeholder / renderChunk) and registers onAdditionalAssets into memoryFiles.
  • Regression tests: an HMR edit that adds an asset import, and a lazily-compiled module that pulls in an asset (both assert naturalWidth > 0 on first patch/load, no 404).
  • Docs: internal-docs/dev-engine/implementation.md §17 (asset emit → deliver dataflow).

Verifications

  • cargo check + clippy clean; tsc clean for packages/rolldown and packages/test-dev-server.
  • New test-dev-server asset specs pass.
  • Vite: bundledDev registers onAdditionalAssets; the hmr-full-bundle-mode playground suite is 13/13 green — the three #22596 scenarios (lazy, HMR patch, HMR full-reload) each serve image/png (naturalWidth=128) on the first request.

🤖 Generated with Claude Code

h-a-n-a commented Jun 17, 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.

This was referenced Jun 17, 2026
@h-a-n-a h-a-n-a changed the base branch from 06-17-fix_dev_issue-9812 to graphite-base/9815 June 18, 2026 06:51
@h-a-n-a h-a-n-a force-pushed the graphite-base/9815 branch from e81c6de to 00b7b6c Compare June 18, 2026 06:51
@h-a-n-a h-a-n-a force-pushed the 06-17-fix_dev_vite_22596 branch from 601c6a0 to 22d2307 Compare June 18, 2026 06:51
@h-a-n-a h-a-n-a changed the base branch from graphite-base/9815 to main June 18, 2026 06:51
@h-a-n-a h-a-n-a force-pushed the 06-17-fix_dev_vite_22596 branch from 22d2307 to 7dbb03d Compare June 18, 2026 08:07
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 752820d
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a34c2c87f80890008416591
😎 Deploy Preview https://deploy-preview-9815--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 06-17-fix_dev_vite_22596 branch 2 times, most recently from b374da0 to e546ca6 Compare June 18, 2026 09:03
@h-a-n-a h-a-n-a changed the title fix(dev): vite#22596 fix(dev): serve assets emitted during HMR/lazy compile (vite#22596) Jun 18, 2026
@h-a-n-a h-a-n-a marked this pull request as ready for review June 18, 2026 09:16

hyf0 commented Jun 18, 2026

Copy link
Copy Markdown
Member

Merge activity

  • Jun 18, 9:24 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.
  • Jun 18, 9:24 AM UTC: hyf0 added this pull request to the Graphite merge queue.
  • Jun 18, 9:26 AM UTC: h-a-n-a removed this pull request from the Graphite merge queue.
  • Jun 18, 9:26 AM UTC: h-a-n-a removed this pull request from the Graphite merge queue.
  • Jun 19, 4:15 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.
  • Jun 19, 4:15 AM UTC: h-a-n-a added this pull request to the Graphite merge queue.
  • Jun 19, 4:22 AM UTC: Merged by the Graphite merge queue.

graphite-app Bot pushed a commit that referenced this pull request Jun 18, 2026
…9815)

## Summary

In Full Bundle Mode, assets emitted **while generating an HMR patch or compiling a lazy entry** (e.g. an image newly imported by the changed/lazy module) never reached the dev consumer — those paths skip the generate stage, so they never fire `onOutput`, the only asset-delivery channel. The browser then 404s the asset until a full reload.

This adds an `onAdditionalAssets` dev callback that delivers those assets **before** the patch/lazy code reaches the client, so a consumer that serves built files (Vite) can serve them in time.

Fixes vitejs/vite#22596 and its HMR analog. Also addresses #9812 and #9817 in the Vite / bundled-dev path (there the asset URLs are resolved eagerly at `load`/`transform`, and this PR delivers the bytes). Purely additive — no breaking changes.

## Changes

- **New `onAdditionalAssets` callback** through every layer (`rolldown_dev` → napi → TS). Fires before the patch/lazy code reaches the client; the shared `emitted_files` gate keeps each asset delivered exactly once.
- Carries a `BundleOutput` at the Rust boundary so `add_additional_files` warnings get the same treatment as `onOutput`; the napi layer delivers `BindingOutputs` directly (no error path — the bytes are already emitted).
- **test-dev-server** reference consumer: ports the bundled-dev branch of Vite's `vite:asset` plugin (eager URL resolution at `load`; no `__ROLLDOWN_ASSET__` placeholder / `renderChunk`) and registers `onAdditionalAssets` into `memoryFiles`.
- Regression tests: an HMR edit that adds an asset import, and a lazily-compiled module that pulls in an asset (both assert `naturalWidth > 0` on first patch/load, no 404).
- Docs: `internal-docs/dev-engine/implementation.md` §17 (asset emit → deliver dataflow).

## Verifications

- `cargo check` + `clippy` clean; `tsc` clean for `packages/rolldown` and `packages/test-dev-server`.
- New test-dev-server asset specs pass.
- Vite: `bundledDev` registers `onAdditionalAssets`; the `hmr-full-bundle-mode` playground suite is **13/13 green** — the three #22596 scenarios (lazy, HMR patch, HMR full-reload) each serve `image/png` (`naturalWidth=128`) on the first request.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@graphite-app graphite-app Bot force-pushed the 06-17-fix_dev_vite_22596 branch from e546ca6 to 93eeb8d Compare June 18, 2026 09:25
h-a-n-a added a commit that referenced this pull request Jun 18, 2026
…9815)

## Summary

In Full Bundle Mode, assets emitted **while generating an HMR patch or compiling a lazy entry** (e.g. an image newly imported by the changed/lazy module) never reached the dev consumer — those paths skip the generate stage, so they never fire `onOutput`, the only asset-delivery channel. The browser then 404s the asset until a full reload.

This adds an `onAdditionalAssets` dev callback that delivers those assets **before** the patch/lazy code reaches the client, so a consumer that serves built files (Vite) can serve them in time.

Fixes vitejs/vite#22596 and its HMR analog. Also addresses #9812 and #9817 in the Vite / bundled-dev path (there the asset URLs are resolved eagerly at `load`/`transform`, and this PR delivers the bytes). Purely additive — no breaking changes.

## Changes

- **New `onAdditionalAssets` callback** through every layer (`rolldown_dev` → napi → TS). Fires before the patch/lazy code reaches the client; the shared `emitted_files` gate keeps each asset delivered exactly once.
- Carries a `BundleOutput` at the Rust boundary so `add_additional_files` warnings get the same treatment as `onOutput`; the napi layer delivers `BindingOutputs` directly (no error path — the bytes are already emitted).
- **test-dev-server** reference consumer: ports the bundled-dev branch of Vite's `vite:asset` plugin (eager URL resolution at `load`; no `__ROLLDOWN_ASSET__` placeholder / `renderChunk`) and registers `onAdditionalAssets` into `memoryFiles`.
- Regression tests: an HMR edit that adds an asset import, and a lazily-compiled module that pulls in an asset (both assert `naturalWidth > 0` on first patch/load, no 404).
- Docs: `internal-docs/dev-engine/implementation.md` §17 (asset emit → deliver dataflow).

## Verifications

- `cargo check` + `clippy` clean; `tsc` clean for `packages/rolldown` and `packages/test-dev-server`.
- New test-dev-server asset specs pass.
- Vite: `bundledDev` registers `onAdditionalAssets`; the `hmr-full-bundle-mode` playground suite is **13/13 green** — the three #22596 scenarios (lazy, HMR patch, HMR full-reload) each serve `image/png` (`naturalWidth=128`) on the first request.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@h-a-n-a h-a-n-a force-pushed the 06-17-fix_dev_vite_22596 branch from 93eeb8d to 15d6b31 Compare June 18, 2026 11:28
@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing 06-17-fix_dev_vite_22596 (15d6b31) with main (455fb60)

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 commented Jun 18, 2026

Copy link
Copy Markdown
Member Author

@sapphi-red Would you please also take a look at your convenience? Thanks!

@sapphi-red sapphi-red 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.

LGTM!

…9815)

## Summary

In Full Bundle Mode, assets emitted **while generating an HMR patch or compiling a lazy entry** (e.g. an image newly imported by the changed/lazy module) never reached the dev consumer — those paths skip the generate stage, so they never fire `onOutput`, the only asset-delivery channel. The browser then 404s the asset until a full reload.

This adds an `onAdditionalAssets` dev callback that delivers those assets **before** the patch/lazy code reaches the client, so a consumer that serves built files (Vite) can serve them in time.

Fixes vitejs/vite#22596 and its HMR analog. Also addresses #9812 and #9817 in the Vite / bundled-dev path (there the asset URLs are resolved eagerly at `load`/`transform`, and this PR delivers the bytes). Purely additive — no breaking changes.

## Changes

- **New `onAdditionalAssets` callback** through every layer (`rolldown_dev` → napi → TS). Fires before the patch/lazy code reaches the client; the shared `emitted_files` gate keeps each asset delivered exactly once.
- Carries a `BundleOutput` at the Rust boundary so `add_additional_files` warnings get the same treatment as `onOutput`; the napi layer delivers `BindingOutputs` directly (no error path — the bytes are already emitted).
- **test-dev-server** reference consumer: ports the bundled-dev branch of Vite's `vite:asset` plugin (eager URL resolution at `load`; no `__ROLLDOWN_ASSET__` placeholder / `renderChunk`) and registers `onAdditionalAssets` into `memoryFiles`.
- Regression tests: an HMR edit that adds an asset import, and a lazily-compiled module that pulls in an asset (both assert `naturalWidth > 0` on first patch/load, no 404).
- Docs: `internal-docs/dev-engine/implementation.md` §17 (asset emit → deliver dataflow).

## Verifications

- `cargo check` + `clippy` clean; `tsc` clean for `packages/rolldown` and `packages/test-dev-server`.
- New test-dev-server asset specs pass.
- Vite: `bundledDev` registers `onAdditionalAssets`; the `hmr-full-bundle-mode` playground suite is **13/13 green** — the three #22596 scenarios (lazy, HMR patch, HMR full-reload) each serve `image/png` (`naturalWidth=128`) on the first request.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@graphite-app graphite-app Bot force-pushed the 06-17-fix_dev_vite_22596 branch from 15d6b31 to 752820d Compare June 19, 2026 04:17
@graphite-app graphite-app Bot merged commit 752820d into main Jun 19, 2026
34 checks passed
@graphite-app graphite-app Bot deleted the 06-17-fix_dev_vite_22596 branch June 19, 2026 04:22
@rolldown-guard rolldown-guard Bot mentioned this pull request Jun 24, 2026
shulaoda added a commit that referenced this pull request Jun 24, 2026
## [1.1.3] - 2026-06-24

### 🐛 Bug Fixes

- `defer_drop` crashes the browser main thread (#9942) by @shulaoda
- camel-case: correct camel case for nested values (#9933) by @kb019
- cli: display --help options in camelCase (#9941) by @IWANABETHATGUY
- preserve used re-exports under preserveModules (#9122) (#9934) by @IWANABETHATGUY
- watch: make close reentrant in event callbacks (#9904) by @hyf0
- git for windows treats symlink files as regular files (#9915) by @AliceLanniste
- dev: cancel pending full reload on build error (#9903) by @h-a-n-a
- chunking: pass plugin meta to codeSplitting groups name function (#9267) by @Kyujenius
- dev: serve assets emitted during HMR/lazy compile (vite#22596) (#9815) by @h-a-n-a
- release: dry-run step no longer publishes binding packages (#9866) by @Boshen

### 🚜 Refactor

- rolldown_common: model ModuleId as a classified Path/Virtual/Bare enum (#9927) by @Boshen
- remove unused LegacyModuleIdx (#9872) by @shulaoda
- remove unused StmtInfos::get_namespace_stmt_info (#9870) by @shulaoda
- remove unused Module::as_external_mut (#9871) by @shulaoda
- remove unused EcmaAst::is_body_empty (#9869) by @shulaoda
- drop dead is_css_module handling in resolve_dependencies (#9867) by @shulaoda
- drop redundant with_commonjs on cjs source type (#9868) by @shulaoda

### 📚 Documentation

- clarify on drafting PRs (#9952) by @h-a-n-a
- update contribution guidelines (#9944) by @fubhy
- note Rust crates don't follow semver in AGENTS.md (#9905) by @IWANABETHATGUY
- add feedback form (#9159) by @TheAlexLichter

### ⚡ Performance

- utils: avoid allocation in default_sanitize_file_name for clean names (#9928) by @Boshen
- binding: box once-per-build futures before spawn_future (#9864) by @Boshen
- utils: avoid wasted allocation in legitimize_identifier_name (#9926) by @Boshen
- rolldown: fuse the canonical-name dedup and insert in the renamer (#9900) by @Boshen
- rolldown: probe the name map once in ConflictResolver::resolve (#9899) by @Boshen
- cut two heap allocations from wrapped ESM init finalize (#9901) by @Boshen
- rolldown_plugin_vite_reporter: hoist invariant out_dir prefix out of reporter loop (#9873) by @shulaoda
- drop throwaway Vec in wrapped esm init stmt (#9878) by @shulaoda
- borrow owner_filename in build-import-analysis AddDeps (#9874) by @shulaoda

### 🧪 Testing

- cover preserveModules named export via namespace re-export (#6010) (#9937) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- deps: update napi to v3.9.4 (#9954) by @shulaoda
- reduce noise from CODEOWNERS for trival changes (#9953) by @h-a-n-a
- deps: update mimalloc-safe to 0.1.64 (#9950) by @shulaoda
- deps: update rollup submodule for tests to v4.62.2 (#9931) by @rolldown-guard[bot]
- deps: test mimalloc-safe upstream-mimalloc switch in CI (#9930) by @shulaoda
- rolldown_plugin_vite_build_import_analysis: remove unused v2 code path (#9917) by @shulaoda
- rolldown_plugin_vite_manifest: remove unused is_enable_v2 code path (#9916) by @shulaoda
- rolldown_plugin_vite_asset_import_meta_url: remove unexposed native vite plugin (#9896) by @shulaoda
- rolldown_plugin_vite_asset: remove unexposed native vite plugin (#9895) by @shulaoda
- rolldown_plugin_vite_css_post: remove unexposed native vite plugin (#9894) by @shulaoda
- rolldown_plugin_vite_css: remove unexposed native vite plugin (#9893) by @shulaoda
- rolldown_plugin_vite_html_inline_proxy: remove unexposed native vite plugin (#9892) by @shulaoda
- rolldown_plugin_vite_html: remove unexposed native vite plugin (#9891) by @shulaoda
- deps: update github actions (#9909) by @renovate[bot]
- deps: update rust crate oxc_sourcemap to v8.0.2 (#9910) by @renovate[bot]
- deps: update npm packages (#9912) by @renovate[bot]
- deps: update github actions to v7 (#9913) by @renovate[bot]
- deps: update rolldown-plugin-dts to ^0.26.0 (#9897) by @renovate[bot]
- remove rolldown_filter_analyzer crate (#9865) by @Boshen

### ❤️ New Contributors

* @fubhy made their first contribution in [#9944](#9944)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
graphite-app Bot pushed a commit that referenced this pull request Jul 2, 2026
### Description

Rewrites `internal-docs/lazy-compilation/{design,implementation}.md` to match the current implementation. The docs dated back to the initial POC (split out in #9826) and had drifted from the code in several load-bearing places. Every claim was re-verified against current `main` (file/line spot-checks, cross-checked with the playground specs).

#### Corrections to stale / wrong content

- **Proxy templates**: both inline code blocks are now the real `proxy-module-template{,-fetched}.js` verbatim. The stub evicts its runtime cache entry and ends with `return await loadExports($STABLE_PROXY_MODULE_ID)['rolldown:exports']` — the two-level promise chain from #9981 that makes init errors catchable at the consumer's `await import()`. The fetched template returns `loadExports($STABLE_MODULE_ID)` instead of the import namespace (export-name preservation in shared chunks, #9132).
- **Module IDs**: design.md's TL;DR claimed absolute paths are used "consistently throughout the runtime" — runtime lookups actually use stable (cwd-relative) ids; absolute paths survive only in the `/@vite/lazy?id=` param and the fetched template's `import($MODULE_ID)`. Also documents the four template placeholders and their JSON-quoted rendering (#9102).
- **Error handling**: replaced "`Err` or panic is fine for POC" with the actual contract — unknown ids are rejected as a cache-key-only security gate (#9969), napi surfaces `Failed to compile lazy entry:` → HTTP 500, and init errors are catchable on both cold and warm paths (#9975/#9981).
- **Dedup**: the race-condition section's "potential future enhancement" runtime guard is long implemented — lazy chunks render `createEsm/CjsInitializer(stableId, factory, 1)` with a dedup flag (HMR patches deliberately omit it so updates re-execute). Rewrote the section around the two-layer dedup: per-client `executed_modules` pruning + the runtime flag.
- **Rebuild id normalization**: the old note said the `ModuleChanged` rebuild "should resolve or normalize" the proxy id to a real module id — the raw proxy id is deliberately correct (it is the incremental-cache invalidation key for the proxy whose content changed; normalizing would leave the stale stub cached). Note removed and the actual mechanism documented.
- Misc: `lazyMagic` helper (never existed), `__export` → `__exportAll`, `snippet` → `ast_factory` in the Lessons Learned snippets, `registerModule(stableId, { exports })` shape.

#### New coverage

- **Enabling / wiring**: `experimental.devMode.lazy: true`, inner-plugin registration, the shared `LazyCompilationContext` handed to the `DevEngine`
- **Lazy chunk rendering**: initializer wrappers + `__rolldown_module_id__` param, the nested-lazy `import()` → `/@vite/lazy` rewrite, entry init call, `lazy_compile_{n}.js` naming
- **Emitted assets**: delivered via `onAdditionalAssets` before the code returns (#9815)
- **Build output refresh**: `update_watch_paths()` runs first (what makes post-fetch edits watchable at all), silent output swap for connected clients, failure paths (#9903)
- **Editing a fetched lazy module**: per-client HMR outcomes; a non-accepting proxy escalates to FullReload → HmrRebuild with a deferred reload
- **Client sessions**: implicit creation on `hmr:module-registered`, removal on ws disconnect, the `"rolldown-tests"` escape hatch; clientId prunes the patch — nothing is "routed"
- **Known limitations**: link-stage-synthesized exports (JSON/text/base64/dataurl register `{}` inside lazy chunks until rebuild + refresh), CSS error deferral, assets require load-hook plugins, sourcemaps (inline-only for lazy chunks — the map asset is discarded on the lazy path, unlike `HmrPatch`)
- **Test coverage table**: the 7 playground specs + `dev-lazy-compile.test.ts` and what each pins

Docs only — no runtime change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants