From 6e5143d9635dac3f20fb394a827109df30e232ae Mon Sep 17 00:00:00 2001 From: edison Date: Wed, 20 Aug 2025 20:49:59 +0800 Subject: [PATCH] fix(hmr): prevent updating unmounting component during HMR rerender (#13775) close #13771 close #13772 --- packages/runtime-core/__tests__/hmr.spec.ts | 43 +++++++++++++++++++++ packages/runtime-core/src/hmr.ts | 7 +++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index 3f157d009a..01feb91ace 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -894,4 +894,47 @@ describe('hot module replacement', () => { await timeout() expect(serializeInner(root)).toBe('
bar
') }) + + test('rerender for nested component', () => { + const id = 'child-nested-rerender' + const Foo: ComponentOptions = { + __hmrId: id, + render() { + return this.$slots.default() + }, + } + createRecord(id, Foo) + + const parentId = 'parent-nested-rerender' + const Parent: ComponentOptions = { + __hmrId: parentId, + render() { + return h(Foo, null, { + default: () => this.$slots.default(), + _: 3 /* FORWARDED */, + }) + }, + } + + const appId = 'app-nested-rerender' + const App: ComponentOptions = { + __hmrId: appId, + render: () => + h(Parent, null, { + default: () => [ + h(Foo, null, { + default: () => ['foo'], + }), + ], + }), + } + createRecord(parentId, App) + + const root = nodeOps.createElement('div') + render(h(App), root) + expect(serializeInner(root)).toBe('foo') + + rerender(id, () => 'bar') + expect(serializeInner(root)).toBe('bar') + }) }) diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 7aedf52dd3..2c46ea73b5 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -7,7 +7,7 @@ import { type InternalRenderFunction, isClassComponent, } from './component' -import { queueJob, queuePostFlushCb } from './scheduler' +import { SchedulerJobFlags, queueJob, queuePostFlushCb } from './scheduler' import { extend, getGlobalThis } from '@vue/shared' type HMRComponent = ComponentOptions | ClassComponent @@ -96,7 +96,10 @@ function rerender(id: string, newRender?: Function): void { instance.renderCache = [] // this flag forces child components with slot content to update isHmrUpdating = true - instance.update() + // #13771 don't update if the job is already disposed + if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) { + instance.update() + } isHmrUpdating = false }) } -- 2.47.3