Skip to content

Commit 36bd9b0

Browse files
authored
fix(hmr): avoid infinite recursion when reloading hmr components (#6936)
close #6930
1 parent f1cc478 commit 36bd9b0

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

packages/runtime-core/__tests__/hmr.spec.ts

+48
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
h,
77
nextTick,
88
nodeOps,
9+
ref,
910
render,
1011
serializeInner,
1112
triggerEvent,
@@ -415,6 +416,53 @@ describe('hot module replacement', () => {
415416
expect(mountSpy).toHaveBeenCalledTimes(1)
416417
})
417418

419+
// #6930
420+
test('reload: avoid infinite recursion', async () => {
421+
const root = nodeOps.createElement('div')
422+
const childId = 'test-child-6930'
423+
const unmountSpy = vi.fn()
424+
const mountSpy = vi.fn()
425+
426+
const Child: ComponentOptions = {
427+
__hmrId: childId,
428+
data() {
429+
return { count: 0 }
430+
},
431+
expose: ['count'],
432+
unmounted: unmountSpy,
433+
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
434+
}
435+
createRecord(childId, Child)
436+
437+
const Parent: ComponentOptions = {
438+
setup() {
439+
const com = ref()
440+
const changeRef = (value: any) => {
441+
com.value = value
442+
}
443+
444+
return () => [h(Child, { ref: changeRef }), com.value?.count]
445+
},
446+
}
447+
448+
render(h(Parent), root)
449+
await nextTick()
450+
expect(serializeInner(root)).toBe(`<div>0</div>0`)
451+
452+
reload(childId, {
453+
__hmrId: childId,
454+
data() {
455+
return { count: 1 }
456+
},
457+
mounted: mountSpy,
458+
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
459+
})
460+
await nextTick()
461+
expect(serializeInner(root)).toBe(`<div>1</div>1`)
462+
expect(unmountSpy).toHaveBeenCalledTimes(1)
463+
expect(mountSpy).toHaveBeenCalledTimes(1)
464+
})
465+
418466
// #1156 - static nodes should retain DOM element reference across updates
419467
// when HMR is active
420468
test('static el reference', async () => {

packages/runtime-core/src/hmr.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ function reload(id: string, newComp: HMRComponent) {
139139
// components to be unmounted and re-mounted. Queue the update so that we
140140
// don't end up forcing the same parent to re-render multiple times.
141141
instance.parent.effect.dirty = true
142-
queueJob(instance.parent.update)
142+
queueJob(() => {
143+
instance.parent!.update()
144+
// #6930 avoid infinite recursion
145+
hmrDirtyComponents.delete(oldComp)
146+
})
143147
} else if (instance.appContext.reload) {
144148
// root instance mounted via createApp() has a reload method
145149
instance.appContext.reload()

0 commit comments

Comments
 (0)