Describe the bug
In dev mode (full-bundle / experimental.devMode), an asset referenced via new URL('./asset', import.meta.url) is not resolved when the referencing module is delivered through an HMR patch or a lazy-compilation chunk. The patch ships the raw specifier, so the browser resolves it against the patch's own URL and 404s. It only works after a full page reload (which serves the fully-generated bundle).
This is a sibling of #9812. That issue is the __ROLLDOWN_ASSET__#<refId> placeholder produced by import x from './asset' (resolved in the asset plugin's renderChunk, a generate-phase hook). This issue is the other asset-reference mechanism — new URL(..., import.meta.url) (and, by the same code path, import.meta.ROLLUP_FILE_URL_<refId>) — which is resolved in the module finalizer (link phase, crates/rolldown/src/module_finalizers/mod.rs — handle_new_url_with_string_literal_and_import_meta_url ~L1057, rewrite_rollup_file_url ~L1009).
Root cause is the same divergence: the HMR/lazy codegen (crates/rolldown/src/hmr/hmr_stage.rs) reuses the scan phase (so the asset is emitted by the load hook) but reimplements finalize+codegen with HmrAstFinalizer + EcmaCompiler::print_with, and never runs link or generate. HmrAstFinalizer does no asset-reference resolution at all (grep it for new_url / rollup_file_url / file_ref — empty), so every finalizer/renderChunk asset rewrite is silently absent from patches:
| Reference form |
Resolved in (full pipeline) |
In HMR/lazy patch? |
import x from './x.png' → __ROLLDOWN_ASSET__#ref |
generate (renderChunk) |
fixed for #9812 |
new URL('./x', import.meta.url) |
link (module_finalizers AST rewrite) |
broken (this issue) |
import.meta.ROLLUP_FILE_URL_x |
link (module_finalizers AST rewrite) |
broken (same path, untested) |
Reproduction
Reproducible branch: 06-17-hmr-new-url-asset (built on the #9812 placeholder fix).
Test: packages/test-dev-server/tests/playground/lazy-compilation/__tests__/new-url.spec.ts (scenario fixture in playground/lazy-compilation/new-url/). The playground config sets experimental.resolveNewUrlToAsset: true (the feature is opt-in; without it new URL is left raw everywhere, not just in HMR).
just build-rolldown
cd packages/test-dev-server/tests
pnpm test:browser playground/lazy-compilation # `new-url` fails; the other scenarios pass
The scenario lazily imports a module that does img.src = new URL('./new-url-image.png', import.meta.url).href.
Observed (captured)
First lazy load (HMR codegen):
img.src = http://localhost:PORT/@vite/new-url-image.png ← raw specifier, unresolved
404 http://localhost:PORT/@vite/new-url-image.png
Full build (e.g. the existing crates/rolldown/tests/rolldown/topics/new_url/binary_asset snapshot) resolves correctly:
const url = new URL("assets/foo-u1ylrPOy.bin", import.meta.url);
So: the asset is emitted, but the new URL(...) first argument is never rewritten in the patch → wrong URL → 404 until a full reload.
Expected behavior
A new URL('./asset', import.meta.url) (and import.meta.ROLLUP_FILE_URL_x) reference in a module added by an HMR patch or compiled lazily should resolve to the same hashed asset URL the full build produces, and the asset should be served on the first request — not only after a refresh.
System Info
rolldown main (1.1.1 dev), reproduced on macOS. Related: #9812, vitejs/vite#22596.
Validations
Describe the bug
In dev mode (full-bundle /
experimental.devMode), an asset referenced vianew URL('./asset', import.meta.url)is not resolved when the referencing module is delivered through an HMR patch or a lazy-compilation chunk. The patch ships the raw specifier, so the browser resolves it against the patch's own URL and 404s. It only works after a full page reload (which serves the fully-generated bundle).This is a sibling of #9812. That issue is the
__ROLLDOWN_ASSET__#<refId>placeholder produced byimport x from './asset'(resolved in the asset plugin'srenderChunk, a generate-phase hook). This issue is the other asset-reference mechanism —new URL(..., import.meta.url)(and, by the same code path,import.meta.ROLLUP_FILE_URL_<refId>) — which is resolved in the module finalizer (link phase,crates/rolldown/src/module_finalizers/mod.rs—handle_new_url_with_string_literal_and_import_meta_url~L1057,rewrite_rollup_file_url~L1009).Root cause is the same divergence: the HMR/lazy codegen (
crates/rolldown/src/hmr/hmr_stage.rs) reuses the scan phase (so the asset is emitted by theloadhook) but reimplements finalize+codegen withHmrAstFinalizer+EcmaCompiler::print_with, and never runs link or generate.HmrAstFinalizerdoes no asset-reference resolution at all (grep it fornew_url/rollup_file_url/file_ref— empty), so every finalizer/renderChunkasset rewrite is silently absent from patches:import x from './x.png'→__ROLLDOWN_ASSET__#refrenderChunk)new URL('./x', import.meta.url)module_finalizersAST rewrite)import.meta.ROLLUP_FILE_URL_xmodule_finalizersAST rewrite)Reproduction
Reproducible branch:
06-17-hmr-new-url-asset(built on the #9812 placeholder fix).Test:
packages/test-dev-server/tests/playground/lazy-compilation/__tests__/new-url.spec.ts(scenario fixture inplayground/lazy-compilation/new-url/). The playground config setsexperimental.resolveNewUrlToAsset: true(the feature is opt-in; without itnew URLis left raw everywhere, not just in HMR).The scenario lazily imports a module that does
img.src = new URL('./new-url-image.png', import.meta.url).href.Observed (captured)
First lazy load (HMR codegen):
Full build (e.g. the existing
crates/rolldown/tests/rolldown/topics/new_url/binary_assetsnapshot) resolves correctly:So: the asset is emitted, but the
new URL(...)first argument is never rewritten in the patch → wrong URL → 404 until a full reload.Expected behavior
A
new URL('./asset', import.meta.url)(andimport.meta.ROLLUP_FILE_URL_x) reference in a module added by an HMR patch or compiled lazily should resolve to the same hashed asset URL the full build produces, and the asset should be served on the first request — not only after a refresh.System Info
rolldown
main(1.1.1 dev), reproduced on macOS. Related: #9812, vitejs/vite#22596.Validations