From: edison Date: Wed, 24 Sep 2025 08:58:49 +0000 (+0800) Subject: fix(runtime-vapor): sync parent component block reference during HMR reload (#13866) X-Git-Tag: v3.6.0-alpha.3~41 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b65a6b869e25846716960eb25b0a6c5b8ae5a429;p=thirdparty%2Fvuejs%2Fcore.git fix(runtime-vapor): sync parent component block reference during HMR reload (#13866) --- diff --git a/packages/runtime-vapor/__tests__/hmr.spec.ts b/packages/runtime-vapor/__tests__/hmr.spec.ts new file mode 100644 index 0000000000..21ca611263 --- /dev/null +++ b/packages/runtime-vapor/__tests__/hmr.spec.ts @@ -0,0 +1,118 @@ +// TODO: port tests from packages/runtime-core/__tests__/hmr.spec.ts + +import { type HMRRuntime, ref } from '@vue/runtime-dom' +import { makeRender } from './_utils' +import { + child, + createComponent, + renderEffect, + setText, + template, +} from '@vue/runtime-vapor' + +declare var __VUE_HMR_RUNTIME__: HMRRuntime +const { createRecord, reload } = __VUE_HMR_RUNTIME__ + +const define = makeRender() + +describe('hot module replacement', () => { + test('child reload + parent reload', async () => { + const root = document.createElement('div') + const childId = 'test1-child-reload' + const parentId = 'test1-parent-reload' + + const { component: Child } = define({ + __hmrId: childId, + setup() { + const msg = ref('child') + return { msg } + }, + render(ctx) { + const n0 = template(`
`)() + const x0 = child(n0 as any) + renderEffect(() => setText(x0 as any, ctx.msg)) + return [n0] + }, + }) + createRecord(childId, Child as any) + + const { mount, component: Parent } = define({ + __hmrId: parentId, + setup() { + const msg = ref('root') + return { msg } + }, + render(ctx) { + const n0 = createComponent(Child) + const n1 = template(`
`)() + const x0 = child(n1 as any) + renderEffect(() => setText(x0 as any, ctx.msg)) + return [n0, n1] + }, + }).create() + createRecord(parentId, Parent as any) + mount(root) + + expect(root.innerHTML).toMatchInlineSnapshot( + `"
child
root
"`, + ) + + // reload child + reload(childId, { + __hmrId: childId, + __vapor: true, + setup() { + const msg = ref('child changed') + return { msg } + }, + render(ctx: any) { + const n0 = template(`
`)() + const x0 = child(n0 as any) + renderEffect(() => setText(x0 as any, ctx.msg)) + return [n0] + }, + }) + expect(root.innerHTML).toMatchInlineSnapshot( + `"
child changed
root
"`, + ) + + // reload child again + reload(childId, { + __hmrId: childId, + __vapor: true, + setup() { + const msg = ref('child changed2') + return { msg } + }, + render(ctx: any) { + const n0 = template(`
`)() + const x0 = child(n0 as any) + renderEffect(() => setText(x0 as any, ctx.msg)) + return [n0] + }, + }) + expect(root.innerHTML).toMatchInlineSnapshot( + `"
child changed2
root
"`, + ) + + // reload parent + reload(parentId, { + __hmrId: parentId, + __vapor: true, + setup() { + const msg = ref('root changed') + return { msg } + }, + render(ctx: any) { + const n0 = createComponent(Child) + const n1 = template(`
`)() + const x0 = child(n1 as any) + renderEffect(() => setText(x0 as any, ctx.msg)) + return [n0, n1] + }, + }) + expect(root.innerHTML).toMatchInlineSnapshot( + `"
child changed2
root changed
"`, + ) + }) +}) diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index c96c1afa13..1a88ec7dc2 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -12,6 +12,7 @@ import { mountComponent, unmountComponent, } from './component' +import { isArray } from '@vue/shared' export function hmrRerender(instance: VaporComponentInstance): void { const normalized = normalizeBlock(instance.block) @@ -34,7 +35,8 @@ export function hmrReload( const parent = normalized[0].parentNode! const anchor = normalized[normalized.length - 1].nextSibling unmountComponent(instance, parent) - const prev = setCurrentInstance(instance.parent) + const parentInstance = instance.parent as VaporComponentInstance | null + const prev = setCurrentInstance(parentInstance) const newInstance = createComponent( newComp, instance.rawProps, @@ -43,4 +45,31 @@ export function hmrReload( ) setCurrentInstance(...prev) mountComponent(newInstance, parent, anchor) + + updateParentBlockOnHmrReload(parentInstance, instance, newInstance) +} + +/** + * dev only + * update parentInstance.block to ensure that the correct parent and + * anchor are found during parentInstance HMR rerender/reload, as + * `normalizeBlock` relies on the current instance.block + */ +function updateParentBlockOnHmrReload( + parentInstance: VaporComponentInstance | null, + instance: VaporComponentInstance, + newInstance: VaporComponentInstance, +): void { + if (parentInstance) { + if (parentInstance.block === instance) { + parentInstance.block = newInstance + } else if (isArray(parentInstance.block)) { + for (let i = 0; i < parentInstance.block.length; i++) { + if (parentInstance.block[i] === instance) { + parentInstance.block[i] = newInstance + break + } + } + } + } }