Skip to content

fix(dev): serve assets emitted during lazy bundling on first load#22599

Open
semimikoh wants to merge 1 commit into
vitejs:mainfrom
semimikoh:fix/22596-lazy-bundling-asset-first-load
Open

fix(dev): serve assets emitted during lazy bundling on first load#22599
semimikoh wants to merge 1 commit into
vitejs:mainfrom
semimikoh:fix/22596-lazy-bundling-asset-first-load

Conversation

@semimikoh

Copy link
Copy Markdown
Contributor

Description

Fixes #22596.

In bundled dev mode (experimental.bundledDev), static asset imports such as a JS-imported image/SVG or a CSS background: url(...) failed to load the first time a lazily-loaded route/module was opened, and only loaded after a page refresh.

Root cause

When a dynamically imported module is first requested, triggerLazyBundling calls devEngine.compileEntry() and returns the resulting lazy chunk patch code straight to the browser. The assets emitted while compiling that chunk are only registered into memoryFiles by the onOutput callback, which can fire after compileEntry resolves.

So the sequence on first load is:

  1. compileEntry resolves → patch code (referencing hashed asset URLs like /assets/foo-[hash].png) is sent to the browser.
  2. The browser parses it and requests those asset URLs.
  3. onOutput registers the assets into memoryFiles — but only now, after the request already missed.

Because the hashed asset files exist only in memoryFiles (not on disk), the request returns 404 on the first load. A refresh works because by then onOutput has registered the assets.

Fix

Await ensureLatestBuildOutput() after compileEntry so the emitted assets are flushed into memoryFiles before the patch code is returned to the browser.

Tests

Added a regression test in playground/hmr-full-bundle-mode that lazily imports a module referencing both a JS-imported image and a CSS url() asset, then asserts the image actually decodes (naturalWidth > 0) and that no asset request 404s on the first click.

The fixture images use unique content so they are emitted as distinct hashed assets only when the lazy module is compiled (otherwise they would be deduplicated with an eagerly-bundled asset and never exercise the bug). Combined with the playground's existing generateBundle delay plugin, the test fails deterministically without this change and passes with it.

In bundled dev mode (`experimental.bundledDev`), `triggerLazyBundling`
returned the lazy chunk's patch code as soon as `compileEntry` resolved.
However, the assets emitted while compiling that chunk (e.g. images
imported from JS or referenced by CSS `url()`) are only registered into
`memoryFiles` by the `onOutput` callback, which can run after
`compileEntry` resolves. The browser could therefore request those assets
before they were available and receive a 404 on the first load, while a
refresh succeeded.

Await `ensureLatestBuildOutput()` after `compileEntry` so the emitted
assets are flushed into `memoryFiles` before the patch code is sent.

Closes vitejs#22596
@sapphi-red

Copy link
Copy Markdown
Member

triggerLazyBundling calls devEngine.compileEntry() and returns the resulting lazy chunk patch code straight to the browser. The assets emitted while compiling that chunk are only registered into memoryFiles by the onOutput callback, which can fire after compileEntry resolves.

@h-a-n-a I think we need a way to tell Vite about the additional assets generated by devEngine.compileEntry() from the Rolldown side. Would you take a look?

@h-a-n-a

h-a-n-a commented Jun 4, 2026

Copy link
Copy Markdown
Member

triggerLazyBundling calls devEngine.compileEntry() and returns the resulting lazy chunk patch code straight to the browser. The assets emitted while compiling that chunk are only registered into memoryFiles by the onOutput callback, which can fire after compileEntry resolves.

@h-a-n-a I think we need a way to tell Vite about the additional assets generated by devEngine.compileEntry() from the Rolldown side. Would you take a look?

Sounds good. How would you like it to be used/consumed over the Vite side? Do you have an idea of how they interact in mind?

@sapphi-red

Copy link
Copy Markdown
Member

@h-a-n-a I think we need a way to tell Vite about the additional assets generated by devEngine.compileEntry() from the Rolldown side. Would you take a look?

Sounds good. How would you like it to be used/consumed over the Vite side? Do you have an idea of how they interact in mind?

I don't have an idea now. Maybe adding onLazyCompileFinished like onOutput 🤔

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.

Static asset imports fail to load on first page load in dev mode with bundledDev: true

3 participants