Przeglądaj źródła

fix(hmr): child reload in dynamic branch should not break subsequent parent reload (#14527)

edison 1 miesiąc temu
rodzic
commit
abefa0c460

+ 55 - 0
packages/runtime-vapor/__tests__/hmr.spec.ts

@@ -1104,6 +1104,61 @@ describe('hot module replacement', () => {
     )
   })
 
+  test('child reload in dynamic branch should not break subsequent parent reload', async () => {
+    const root = document.createElement('div')
+    const childId = 'test-dynamic-child-reload'
+    const parentId = 'test-dynamic-parent-reload'
+
+    const Child = defineVaporComponent({
+      __hmrId: childId,
+      setup() {
+        const msg = ref('child')
+        return { msg }
+      },
+      render: compileToFunction(`<div>{{ msg }}</div>`),
+    })
+    createRecord(childId, Child as any)
+
+    const { mount, component: Parent } = define({
+      __hmrId: parentId,
+      components: { Child },
+      setup() {
+        const ok = ref(true)
+        return { ok }
+      },
+      render: compileToFunction(`<Child v-if="ok" />`),
+    }).create()
+    createRecord(parentId, Parent as any)
+
+    mount(root)
+    expect(root.innerHTML).toBe(`<div>child</div><!--if-->`)
+
+    reload(childId, {
+      __vapor: true,
+      __hmrId: childId,
+      setup() {
+        const msg = ref('child changed')
+        return { msg }
+      },
+      render: compileToFunction(`<div>{{ msg }}</div>`),
+    })
+    expect(root.innerHTML).toBe(`<div>child changed</div><!--if-->`)
+
+    reload(parentId, {
+      __vapor: true,
+      __hmrId: parentId,
+      components: { Child },
+      setup() {
+        const ok = ref(true)
+        return { ok }
+      },
+      render: compileToFunction(`<Child v-if="ok" />`),
+    })
+
+    await nextTick()
+    expect(root.innerHTML).toBe(`<div>child changed</div><!--if-->`)
+  })
+
   // Vapor router-view has no render function (setup-only).
   // When HMR rerender is triggered, the setup function is re-executed.
   // Ensure provide() warning is suppressed.

+ 35 - 20
packages/runtime-vapor/src/hmr.ts

@@ -4,16 +4,18 @@ import {
   pushWarningContext,
   setCurrentInstance,
 } from '@vue/runtime-dom'
-import { insert, normalizeBlock, remove } from './block'
+import { type Block, insert, normalizeBlock, remove } from './block'
 import {
   type VaporComponent,
   type VaporComponentInstance,
   createComponent,
   devRender,
+  isVaporComponent,
   mountComponent,
   unmountComponent,
 } from './component'
 import { isArray } from '@vue/shared'
+import { isFragment } from './fragment'
 
 export function hmrRerender(instance: VaporComponentInstance): void {
   const normalized = normalizeBlock(instance.block)
@@ -73,16 +75,11 @@ function updateParentBlockOnHmrReload(
   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
-        }
-      }
-    }
+    parentInstance.block = replaceBlockInstance(
+      parentInstance.block,
+      instance,
+      newInstance,
+    )
   }
 }
 
@@ -100,15 +97,33 @@ export function updateParentTeleportOnHmrReload(
   const teleport = instance.parentTeleport
   if (teleport) {
     newInstance.parentTeleport = teleport
-    if (teleport.nodes === instance) {
-      teleport.nodes = newInstance
-    } else if (isArray(teleport.nodes)) {
-      for (let i = 0; i < teleport.nodes.length; i++) {
-        if (teleport.nodes[i] === instance) {
-          teleport.nodes[i] = newInstance
-          break
-        }
-      }
+    teleport.nodes = replaceBlockInstance(teleport.nodes, instance, newInstance)
+  }
+}
+
+function replaceBlockInstance(
+  block: Block,
+  instance: VaporComponentInstance,
+  newInstance: VaporComponentInstance,
+): Block {
+  if (block === instance) return newInstance
+
+  if (isArray(block)) {
+    for (let i = 0; i < block.length; i++) {
+      block[i] = replaceBlockInstance(block[i], instance, newInstance)
     }
+    return block
+  }
+
+  if (isVaporComponent(block)) {
+    block.block = replaceBlockInstance(block.block, instance, newInstance)
+    return block
   }
+
+  if (isFragment(block)) {
+    block.nodes = replaceBlockInstance(block.nodes, instance, newInstance)
+    return block
+  }
+
+  return block
 }