diff --git a/packages/runtime-vapor/__tests__/hmr.spec.ts b/packages/runtime-vapor/__tests__/hmr.spec.ts new file mode 100644 index 00000000000..21ca611263e --- /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 c96c1afa130..1a88ec7dc22 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 + } + } + } + } }