Summary
Dev mode currently replaces import.meta.hot.data with a fresh object whenever a module cache entry is removed.
This differs from Vite’s documented HMR behavior:
The import.meta.hot.data object is persisted across different instances of the same updated module.
https://vite.dev/guide/api-hmr#hot-data
refs #10164, #22883 (comment)
@h-a-n-a Sorry I misunderstood the behavior. I think the behavior isn't correct.
Current behavior
FbmHMRClient.handleModuleCacheRemoval() creates a new object, invokes the disposer with it, and replaces the entry in dataMap:
handleModuleCacheRemoval(id: string): void {
const data = {}
const disposer = this.disposeMap.get(id)
if (disposer) {
disposer(data)
}
this.dataMap.set(id, data)
}
https://github.com/vitejs/vite/blob/rolldown-canary/packages/vite/src/client/fbmHmrClient.ts#L160-L167
Consequently, mutations made directly to import.meta.hot.data during module execution are lost on every update. Only values written by the dispose callback survive into the next instance.
The existing hmr-whole-chain-dispose test explicitly expects this fresh-object behavior, which appears to be the inverse of Vite’s contract.
Reproduction
const previous = import.meta.hot?.data.count ?? 0
document.querySelector('.count').textContent = previous
if (import.meta.hot) {
import.meta.hot.data.count = previous + 1
import.meta.hot.accept()
}
Expected after successive edits:
- Initial execution:
0
- First HMR update:
1
- Second HMR update:
2
Current full-bundle behavior:
- Initial execution:
0
- First HMR update:
0
- Second HMR update:
0
Expected behavior
A module should retain the same hot.data object across its HMR instances:
- Direct property mutations must remain visible after an update.
dispose() must receive the existing object, not a newly allocated one.
- The same data must be available to
prune() when pruning is implemented.
Vite’s shared HMR client creates the object only when no entry exists:
https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L31-L33
Its update path passes the existing object to the disposer:
https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L282-L284
Related prune gap
Full-bundle mode does not currently implement hot.prune(), and the corresponding Rolldown test is skipped. This prevents testing the complete data lifecycle.
Note that Vite’s current prunePaths() passes the existing data object to dispose/prune callbacks but does not itself delete the dataMap entry:
https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L216-L231
Summary
Dev mode currently replaces
import.meta.hot.datawith a fresh object whenever a module cache entry is removed.This differs from Vite’s documented HMR behavior:
https://vite.dev/guide/api-hmr#hot-data
refs #10164, #22883 (comment)
@h-a-n-a Sorry I misunderstood the behavior. I think the behavior isn't correct.
Current behavior
FbmHMRClient.handleModuleCacheRemoval()creates a new object, invokes the disposer with it, and replaces the entry indataMap:https://github.com/vitejs/vite/blob/rolldown-canary/packages/vite/src/client/fbmHmrClient.ts#L160-L167
Consequently, mutations made directly to
import.meta.hot.dataduring module execution are lost on every update. Only values written by the dispose callback survive into the next instance.The existing
hmr-whole-chain-disposetest explicitly expects this fresh-object behavior, which appears to be the inverse of Vite’s contract.Reproduction
Expected after successive edits:
012Current full-bundle behavior:
000Expected behavior
A module should retain the same
hot.dataobject across its HMR instances:dispose()must receive the existing object, not a newly allocated one.prune()when pruning is implemented.Vite’s shared HMR client creates the object only when no entry exists:
https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L31-L33
Its update path passes the existing object to the disposer:
https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L282-L284
Related prune gap
Full-bundle mode does not currently implement
hot.prune(), and the corresponding Rolldown test is skipped. This prevents testing the complete data lifecycle.Note that Vite’s current
prunePaths()passes the existing data object to dispose/prune callbacks but does not itself delete thedataMapentry:https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/hmr.ts#L216-L231