Skip to content

preserve import.meta.hot.data across bundled-dev HMR updates #23001

Description

@sapphi-red

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

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions