fix(hmr): prevent update unmounting component during HMR reload (#13815)

close vitejs/vite-plugin-vue#599
This commit is contained in:
edison 2025-09-02 17:07:36 +08:00 committed by GitHub
parent 35da3c6dcb
commit ef20b86b36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 5 deletions

View File

@ -937,4 +937,55 @@ describe('hot module replacement', () => {
rerender(id, () => 'bar') rerender(id, () => 'bar')
expect(serializeInner(root)).toBe('bar') expect(serializeInner(root)).toBe('bar')
}) })
// https://github.com/vitejs/vite-plugin-vue/issues/599
// Both Outer and Inner are reloaded when './server.js' changes
test('reload nested components from single update', async () => {
const innerId = 'nested-reload-inner'
const outerId = 'nested-reload-outer'
let Inner = {
__hmrId: innerId,
render() {
return h('div', 'foo')
},
}
let Outer = {
__hmrId: outerId,
render() {
return h(Inner)
},
}
createRecord(innerId, Inner)
createRecord(outerId, Outer)
const App = {
render: () => h(Outer),
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe('<div>foo</div>')
Inner = {
__hmrId: innerId,
render() {
return h('div', 'bar')
},
}
Outer = {
__hmrId: outerId,
render() {
return h(Inner)
},
}
// trigger reload for both Outer and Inner
reload(outerId, Outer)
reload(innerId, Inner)
await nextTick()
expect(serializeInner(root)).toBe('<div>bar</div>')
})
}) })

View File

@ -147,11 +147,15 @@ function reload(id: string, newComp: HMRComponent): void {
// components to be unmounted and re-mounted. Queue the update so that we // 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. // don't end up forcing the same parent to re-render multiple times.
queueJob(() => { queueJob(() => {
isHmrUpdating = true // vite-plugin-vue/issues/599
instance.parent!.update() // don't update if the job is already disposed
isHmrUpdating = false if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
// #6930, #11248 avoid infinite recursion isHmrUpdating = true
dirtyInstances.delete(instance) instance.parent!.update()
isHmrUpdating = false
// #6930, #11248 avoid infinite recursion
dirtyInstances.delete(instance)
}
}) })
} else if (instance.appContext.reload) { } else if (instance.appContext.reload) {
// root instance mounted via createApp() has a reload method // root instance mounted via createApp() has a reload method