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
+ }
+ }
+ }
+ }
}