Skip to content

Commit

Permalink
fix(hmr): avoid infinite recursion when reloading hmr components (#6936)
Browse files Browse the repository at this point in the history
close #6930
  • Loading branch information
zhangzhonghe authored May 31, 2024
1 parent f1cc478 commit 36bd9b0
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
48 changes: 48 additions & 0 deletions packages/runtime-core/__tests__/hmr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
h,
nextTick,
nodeOps,
ref,
render,
serializeInner,
triggerEvent,
Expand Down Expand Up @@ -415,6 +416,53 @@ describe('hot module replacement', () => {
expect(mountSpy).toHaveBeenCalledTimes(1)
})

// #6930
test('reload: avoid infinite recursion', async () => {
const root = nodeOps.createElement('div')
const childId = 'test-child-6930'
const unmountSpy = vi.fn()
const mountSpy = vi.fn()

const Child: ComponentOptions = {
__hmrId: childId,
data() {
return { count: 0 }
},
expose: ['count'],
unmounted: unmountSpy,
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
}
createRecord(childId, Child)

const Parent: ComponentOptions = {
setup() {
const com = ref()
const changeRef = (value: any) => {
com.value = value
}

return () => [h(Child, { ref: changeRef }), com.value?.count]
},
}

render(h(Parent), root)
await nextTick()
expect(serializeInner(root)).toBe(`<div>0</div>0`)

reload(childId, {
__hmrId: childId,
data() {
return { count: 1 }
},
mounted: mountSpy,
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
})
await nextTick()
expect(serializeInner(root)).toBe(`<div>1</div>1`)
expect(unmountSpy).toHaveBeenCalledTimes(1)
expect(mountSpy).toHaveBeenCalledTimes(1)
})

// #1156 - static nodes should retain DOM element reference across updates
// when HMR is active
test('static el reference', async () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime-core/src/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ function reload(id: string, newComp: HMRComponent) {
// components to be unmounted and re-mounted. Queue the update so that we
// don't end up forcing the same parent to re-render multiple times.
instance.parent.effect.dirty = true
queueJob(instance.parent.update)
queueJob(() => {
instance.parent!.update()
// #6930 avoid infinite recursion
hmrDirtyComponents.delete(oldComp)
})
} else if (instance.appContext.reload) {
// root instance mounted via createApp() has a reload method
instance.appContext.reload()
Expand Down

0 comments on commit 36bd9b0

Please sign in to comment.