From 08d21e10ffb0328ce9e953380ae301c1e9024fdb Mon Sep 17 00:00:00 2001 From: daiwei Date: Mon, 18 Aug 2025 10:30:49 +0800 Subject: [PATCH 1/2] fix(hmr): prevent updating unmounting instance during rerender --- packages/runtime-core/src/hmr.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 7aedf52dd3e..2c46ea73b50 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 }) } From f6344214a17f68b4f93ee31f6e1bacbc9f413881 Mon Sep 17 00:00:00 2001 From: daiwei Date: Mon, 18 Aug 2025 11:00:06 +0800 Subject: [PATCH 2/2] test: add test --- packages/runtime-core/__tests__/hmr.spec.ts | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index 3f157d009a9..01feb91acee 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') + }) })