Skip to content

Commit f9ec81f

Browse files
authored
fix(bundled-dev): preserve import.meta.hot.data across HMR updates (#23005)
1 parent c73d914 commit f9ec81f

5 files changed

Lines changed: 46 additions & 3 deletions

File tree

packages/vite/src/client/bundledDevHmrClient.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,10 @@ export class BundledDevHMRClient extends HMRClient {
162162
}
163163

164164
handleModuleCacheRemoval(id: string): void {
165-
const data = {}
166165
const disposer = this.disposeMap.get(id)
167166
if (disposer) {
168-
disposer(data)
167+
disposer(this.dataMap.get(id))
169168
}
170-
this.dataMap.set(id, data)
171169
}
172170

173171
private async applyPush({

playground/hmr-full-bundle-mode/__tests__/hmr-full-bundle-mode.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,25 @@ if (isBuild) {
224224
await expect.poll(() => page.textContent('.dynamic')).toBe('loaded')
225225
})
226226

227+
// placed before `invalidate` on purpose: that test's cleanup restores
228+
// invalidation-child.js, whose `hot.invalidate()` propagates to a parent
229+
// without a shipped factory and forces a full page reload — which would
230+
// wipe the `hot.data` this test relies on.
231+
test('preserves import.meta.hot.data across updates', async () => {
232+
await expect.poll(() => page.textContent('.data-count')).toBe('1')
233+
await expect.poll(() => page.textContent('.data-disposed')).toBe('0')
234+
235+
editFile('data.js', (code) => code.replace('// @hmr-bump', '// @hmr-bump1'))
236+
await expect.poll(() => page.textContent('.data-count')).toBe('2')
237+
await expect.poll(() => page.textContent('.data-disposed')).toBe('1')
238+
239+
editFile('data.js', (code) =>
240+
code.replace('// @hmr-bump1', '// @hmr-bump12'),
241+
)
242+
await expect.poll(() => page.textContent('.data-count')).toBe('3')
243+
await expect.poll(() => page.textContent('.data-disposed')).toBe('2')
244+
})
245+
227246
test('invalidate', async () => {
228247
const original = readFile('invalidation-child.js')
229248
onTestFinished(() => addFile('invalidation-child.js', original))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// verifies `import.meta.hot.data` persists across HMR updates (#23001)
2+
const data = import.meta.hot?.data ?? {}
3+
4+
// direct mutation: must survive each update, so `count` keeps climbing
5+
data.count = (data.count ?? 0) + 1
6+
text('.data-count', data.count)
7+
8+
// written by the dispose callback below: proves the disposer is handed the
9+
// *existing* data object rather than a freshly allocated one
10+
text('.data-disposed', data.disposed ?? 0)
11+
12+
if (import.meta.hot) {
13+
import.meta.hot.dispose((prev) => {
14+
prev.disposed = (prev.disposed ?? 0) + 1
15+
})
16+
import.meta.hot.accept()
17+
}
18+
19+
// @hmr-bump
20+
21+
function text(el, value) {
22+
document.querySelector(el).textContent = String(value)
23+
}

playground/hmr-full-bundle-mode/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ <h1>HMR Full Bundle Mode</h1>
1010
<div class="worker-plain"></div>
1111
<div class="dead-accept"></div>
1212
<div class="cycle"></div>
13+
<div class="data-count"></div>
14+
<div class="data-disposed"></div>
1315
<div>
1416
<button id="load-dynamic">Load dynamic</button>
1517
<div class="dynamic"></div>

playground/hmr-full-bundle-mode/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import './hmr-asset.js'
33
import './invalidation-parent.js'
44
import './dead-accept.js'
55
import './cycle-a.js'
6+
import './data.js'
67
import assetUrl from './asset.png'
78
import WorkerQuery from './worker-query.js?worker'
89

0 commit comments

Comments
 (0)