Browse Source

fix(runtime-vapor): sync interop vnode el after root changes

daiwei 3 weeks ago
parent
commit
798f842dcf

+ 99 - 0
packages/runtime-vapor/__tests__/vdomInterop.spec.ts

@@ -716,6 +716,47 @@ describe('vdomInterop', () => {
       expect(calls.length).toBe(0)
       app.unmount()
     })
+
+    test('should expose the latest vapor root element in updated hooks', async () => {
+      const useAltRoot = ref(false)
+      const updatedSpy = vi.fn((vnode: any) => {
+        expect((vnode.el as Element).tagName).toBe('P')
+      })
+
+      const VaporChild = defineVaporComponent({
+        props: {
+          alt: Boolean,
+        },
+        setup(props: any) {
+          return createIf(
+            () => props.alt,
+            () => template('<p>alt</p>')(),
+            () => template('<div>base</div>')(),
+          )
+        },
+      })
+
+      const App = defineComponent({
+        setup() {
+          return () =>
+            h(VaporChild as any, {
+              alt: useAltRoot.value,
+              onVnodeUpdated: updatedSpy,
+            })
+        },
+      })
+
+      const root = document.createElement('div')
+      const app = createApp(App)
+      app.use(vaporInteropPlugin)
+      app.mount(root)
+
+      useAltRoot.value = true
+      await nextTick()
+
+      expect(root.querySelector('p')).not.toBeNull()
+      expect(updatedSpy).toHaveBeenCalledTimes(1)
+    })
   })
 
   describe('slots', () => {
@@ -2326,6 +2367,64 @@ describe('vdomInterop', () => {
       ])
     })
 
+    test('should expose the latest vapor root element in updated hooks on reactivation', async () => {
+      const VaporChild = defineVaporComponent({
+        props: {
+          alt: Boolean,
+        },
+        setup(props: any) {
+          return createIf(
+            () => props.alt,
+            () => template('<p>alt</p>')(),
+            () => template('<div>base</div>')(),
+          )
+        },
+      })
+
+      const VdomChild = defineComponent({
+        setup() {
+          return () => h('span', 'vdom')
+        },
+      })
+
+      const current = shallowRef<any>(VaporChild)
+      const useAltRoot = ref(false)
+      const updatedSpy = vi.fn((vnode: any) => {
+        expect((vnode.el as Element).tagName).toBe('P')
+      })
+
+      const App = defineComponent({
+        setup() {
+          return () =>
+            h(KeepAlive, null, {
+              default: () =>
+                current.value === VaporChild
+                  ? h(VaporChild as any, {
+                      alt: useAltRoot.value,
+                      onVnodeUpdated: updatedSpy,
+                    })
+                  : h(VdomChild),
+            })
+        },
+      })
+
+      const root = document.createElement('div')
+      const app = createApp(App)
+      app.use(vaporInteropPlugin)
+      app.mount(root)
+      await nextTick()
+
+      current.value = VdomChild
+      await nextTick()
+
+      useAltRoot.value = true
+      current.value = VaporChild
+      await nextTick()
+
+      expect(root.querySelector('p')).not.toBeNull()
+      expect(updatedSpy).toHaveBeenCalledTimes(1)
+    })
+
     test('should bail out directive beforeUpdate/updated on reactivation for non-element root vapor child', async () => {
       const beforeUpdateSpy = vi.fn()
       const updatedSpy = vi.fn()

+ 14 - 1
packages/runtime-vapor/src/vdomInterop.ts

@@ -249,7 +249,8 @@ const vaporInteropImpl: Omit<
 
   update(n1, n2, shouldUpdate, onBeforeUpdate, onVnodeBeforeUpdate) {
     n2.component = n1.component
-    n2.el = n2.anchor = n1.anchor
+    n2.el = n1.el
+    n2.anchor = n1.anchor
 
     const instance = n2.component as any as VaporComponentInstance
 
@@ -270,6 +271,7 @@ const vaporInteropImpl: Omit<
       }
       instance.rawPropsRef!.value = filterReservedProps(n2.props)
       instance.rawSlotsRef!.value = n2.children
+      queuePostFlushCb(() => syncVNodeRootEl(n2, instance))
     }
   },
 
@@ -441,6 +443,7 @@ const vaporInteropImpl: Omit<
     }
     queuePostFlushCb(() => {
       if (shouldUpdate) {
+        syncVNodeRootEl(vnode, instance)
         if (vnode.dirs) {
           invokeDirectiveHook(vnode, cached, parentComponent, 'updated')
         }
@@ -1281,3 +1284,13 @@ function invokeVaporSlot(vnode: VNode): Block {
     throw e
   }
 }
+
+function syncVNodeRootEl(vnode: VNode, instance: VaporComponentInstance): void {
+  const rootEl = getRootElement(instance)
+  if (rootEl) {
+    vnode.el = rootEl
+  } else {
+    vnode.el = vnode.anchor as any
+    vnode.dirs = null
+  }
+}