UnoCSS version
66.9.1
Describe the bug
In Vite development mode, utilities that are first discovered in a
dynamically imported module are generated by UnoCSS, but the browser does not
reload the global UnoCSS virtual module. The newly rendered content therefore
remains unstyled until the page is manually reloaded.
This is a regression from 66.8.1:
| Version |
Five fresh dev-server runs |
66.8.1 |
display: grid in 5/5 runs |
66.9.1 |
display: block in 5/5 runs |
Production builds include the expected utilities. The problem is specific to
development-mode incremental extraction/HMR.
Reproduction
https://github.com/ntnyq/unocss-vite-hmr-lazy-repro
pnpm install --frozen-lockfile
pnpm exec playwright install chromium
pnpm reproduce
The reproduction uses vanilla Vite and one dynamic import; Vue and routing are
not involved. The script starts five fresh Vite dev servers so that UnoCSS's
in-memory token cache cannot hide the issue.
Actual output with 66.9.1:
run 1: display=block, grid-template-columns=none
run 2: display=block, grid-template-columns=none
run 3: display=block, grid-template-columns=none
run 4: display=block, grid-template-columns=none
run 5: display=block, grid-template-columns=none
Bug reproduced 5/5 times.
Expected: after src/lazy.tsx is imported and renders an element with
grid grid-cols-3 gap-4, its computed display should become grid without a
full-page reload.
Suspected cause
This appears to be a follow-up regression from #5284 / commit 75103d5.
Global mode now stores the UnoCSS entry using the internal virtual-module ID:
const virtualId = toViteVirtualId(entry)
entries.add(virtualId)
return virtualId
However, sendUpdate() still sends mod.url directly for both HMR paths:
return {
acceptedPath: mod.url,
path: mod.url,
timestamp: lastServedTime,
type: 'js-update',
}
The observed WebSocket update uses the internal ID:
The Vite browser client registered the URL-safe public path instead:
hotModulesMap.get(path) therefore misses and silently ignores the update. No
timestamped CSS request is made. Rewriting path and acceptedPath to
/@id/__x00__/__uno.css makes the reproduction update immediately without a
reload.
The per-module implementation in the same commit already keeps the internal
ID for moduleGraph.getModuleById() while using a public path in the HMR
payload. Global mode likely needs the same internal-ID/public-URL separation.
Relevant source:
System Info
- macOS
- Node.js
24.19.0
- pnpm
11.24.0
- Vite
8.2.2
- Chromium via Playwright
1.62.1
Validations
UnoCSS version
66.9.1Describe the bug
In Vite development mode, utilities that are first discovered in a
dynamically imported module are generated by UnoCSS, but the browser does not
reload the global UnoCSS virtual module. The newly rendered content therefore
remains unstyled until the page is manually reloaded.
This is a regression from
66.8.1:66.8.1display: gridin 5/5 runs66.9.1display: blockin 5/5 runsProduction builds include the expected utilities. The problem is specific to
development-mode incremental extraction/HMR.
Reproduction
https://github.com/ntnyq/unocss-vite-hmr-lazy-repro
pnpm install --frozen-lockfile pnpm exec playwright install chromium pnpm reproduceThe reproduction uses vanilla Vite and one dynamic import; Vue and routing are
not involved. The script starts five fresh Vite dev servers so that UnoCSS's
in-memory token cache cannot hide the issue.
Actual output with
66.9.1:Expected: after
src/lazy.tsxis imported and renders an element withgrid grid-cols-3 gap-4, its computeddisplayshould becomegridwithout afull-page reload.
Suspected cause
This appears to be a follow-up regression from #5284 / commit
75103d5.Global mode now stores the UnoCSS entry using the internal virtual-module ID:
However,
sendUpdate()still sendsmod.urldirectly for both HMR paths:The observed WebSocket update uses the internal ID:
The Vite browser client registered the URL-safe public path instead:
hotModulesMap.get(path)therefore misses and silently ignores the update. Notimestamped CSS request is made. Rewriting
pathandacceptedPathto/@id/__x00__/__uno.cssmakes the reproduction update immediately without areload.
The per-module implementation in the same commit already keeps the internal
ID for
moduleGraph.getModuleById()while using a public path in the HMRpayload. Global mode likely needs the same internal-ID/public-URL separation.
Relevant source:
https://github.com/unocss/unocss/blob/v66.9.1/packages-integrations/vite/src/modes/global/dev.ts#L161-L168
https://github.com/unocss/unocss/blob/v66.9.1/packages-integrations/vite/src/modes/global/dev.ts#L69-L82
https://github.com/unocss/unocss/blob/v66.9.1/packages-integrations/vite/src/modes/per-module.ts#L17-L32
System Info
24.19.011.24.08.2.21.62.1Validations