From 4428e9e39d29614dbcf8e0d7305580e55f19afc7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 11 Sep 2025 11:25:11 +0800 Subject: [PATCH 1/5] fix(hmr): add cleanup call in hmrRerender and update parentInstance.block during reload --- packages/reactivity/src/effectScope.ts | 6 ++++++ packages/runtime-vapor/__tests__/hmr.spec.ts | 4 ++++ packages/runtime-vapor/src/hmr.ts | 22 ++++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/runtime-vapor/__tests__/hmr.spec.ts diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index 36c9b85e8d7..f59272dc889 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -87,6 +87,12 @@ export class EffectScope implements ReactiveNode { if (sub !== undefined) { unlink(sub) } + this.cleanup() + } + /** + * @internal + */ + cleanup(): void { cleanup(this) } } diff --git a/packages/runtime-vapor/__tests__/hmr.spec.ts b/packages/runtime-vapor/__tests__/hmr.spec.ts new file mode 100644 index 00000000000..7df8919b01a --- /dev/null +++ b/packages/runtime-vapor/__tests__/hmr.spec.ts @@ -0,0 +1,4 @@ +// TODO: port tests from packages/runtime-core/__tests__/hmr.spec.ts +describe.todo('hot module replacement', () => { + test('child reload + parent rerender', () => {}) +}) diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index c96c1afa130..52c0b2351a3 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -12,12 +12,13 @@ import { mountComponent, unmountComponent, } from './component' - +import { isArray } from '@vue/shared' export function hmrRerender(instance: VaporComponentInstance): void { const normalized = normalizeBlock(instance.block) const parent = normalized[0].parentNode! const anchor = normalized[normalized.length - 1].nextSibling remove(instance.block, parent) + instance.scope!.cleanup() const prev = setCurrentInstance(instance) pushWarningContext(instance) devRender(instance) @@ -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,20 @@ export function hmrReload( ) setCurrentInstance(...prev) mountComponent(newInstance, parent, anchor) + + // 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 + 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 + } + } + } + } } From a99b1ef21bf104acfd78d4fe189e8a80f4c5645d Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 11 Sep 2025 11:42:16 +0800 Subject: [PATCH 2/5] test: add tests --- packages/runtime-vapor/__tests__/hmr.spec.ts | 99 +++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/packages/runtime-vapor/__tests__/hmr.spec.ts b/packages/runtime-vapor/__tests__/hmr.spec.ts index 7df8919b01a..946cf8de4e2 100644 --- a/packages/runtime-vapor/__tests__/hmr.spec.ts +++ b/packages/runtime-vapor/__tests__/hmr.spec.ts @@ -1,4 +1,99 @@ // TODO: port tests from packages/runtime-core/__tests__/hmr.spec.ts -describe.todo('hot module replacement', () => { - test('child reload + parent rerender', () => {}) + +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 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 changed
root changed
"`, + ) + }) }) From 5fb1d0a581f7ad2b85a76b2131297a7bf8b517ad Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 11 Sep 2025 14:25:49 +0800 Subject: [PATCH 3/5] chore: tweaks --- packages/runtime-vapor/src/hmr.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index 52c0b2351a3..1b33affa302 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -13,12 +13,13 @@ import { unmountComponent, } from './component' import { isArray } from '@vue/shared' + export function hmrRerender(instance: VaporComponentInstance): void { const normalized = normalizeBlock(instance.block) const parent = normalized[0].parentNode! const anchor = normalized[normalized.length - 1].nextSibling remove(instance.block, parent) - instance.scope!.cleanup() + instance.scope.cleanup() const prev = setCurrentInstance(instance) pushWarningContext(instance) devRender(instance) @@ -46,9 +47,20 @@ export function hmrReload( setCurrentInstance(...prev) mountComponent(newInstance, parent, anchor) - // 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 + 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 From 12acb7669544628040e6655d9ee6ea6b9ad022ea Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 11 Sep 2025 15:23:23 +0800 Subject: [PATCH 4/5] chore: update --- packages/reactivity/src/effectScope.ts | 6 ------ packages/runtime-vapor/src/hmr.ts | 1 - 2 files changed, 7 deletions(-) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index f59272dc889..36c9b85e8d7 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -87,12 +87,6 @@ export class EffectScope implements ReactiveNode { if (sub !== undefined) { unlink(sub) } - this.cleanup() - } - /** - * @internal - */ - cleanup(): void { cleanup(this) } } diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index 1b33affa302..1a88ec7dc22 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -19,7 +19,6 @@ export function hmrRerender(instance: VaporComponentInstance): void { const parent = normalized[0].parentNode! const anchor = normalized[normalized.length - 1].nextSibling remove(instance.block, parent) - instance.scope.cleanup() const prev = setCurrentInstance(instance) pushWarningContext(instance) devRender(instance) From 3f2ff2213afe60762c187cf9248f295802afbc56 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 12 Sep 2025 21:04:21 +0800 Subject: [PATCH 5/5] chore: update --- packages/runtime-vapor/__tests__/hmr.spec.ts | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/hmr.spec.ts b/packages/runtime-vapor/__tests__/hmr.spec.ts index 946cf8de4e2..21ca611263e 100644 --- a/packages/runtime-vapor/__tests__/hmr.spec.ts +++ b/packages/runtime-vapor/__tests__/hmr.spec.ts @@ -76,6 +76,25 @@ describe('hot module replacement', () => { `"
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, @@ -93,7 +112,7 @@ describe('hot module replacement', () => { }, }) expect(root.innerHTML).toMatchInlineSnapshot( - `"
child changed
root changed
"`, + `"
child changed2
root changed
"`, ) }) })