]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: fix teleport root component hmr reload
authordaiwei <daiwei521@126.com>
Wed, 26 Mar 2025 09:30:32 +0000 (17:30 +0800)
committerdaiwei <daiwei521@126.com>
Wed, 26 Mar 2025 09:30:32 +0000 (17:30 +0800)
packages/runtime-vapor/__tests__/components/Teleport.spec.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/components/Teleport.ts
packages/runtime-vapor/src/hmr.ts

index 20f5105472b772956cc460b77bab8e9b6afd5dbe..d5ff414224937f7dcb8ce9bb56dd852b22535af5 100644 (file)
@@ -317,7 +317,7 @@ describe('renderer: VaporTeleport', () => {
       expect(target.innerHTML).toBe('')
     })
 
-    test.todo('reload child + toggle disabled', async () => {
+    test('reload child + toggle disabled', async () => {
       const target = document.createElement('div')
       const root = document.createElement('div')
       const childId = 'test3-child'
@@ -410,8 +410,6 @@ describe('renderer: VaporTeleport', () => {
       )
       expect(target.innerHTML).toBe('')
 
-      //bug: child reload not update teleport fragment's nodes
-
       // toggle disabled
       disabled.value = false
       await nextTick()
index 17ae7190923bb8315a56f1034cd722c804ed026f..332595aac487f292518d1d362dd094e64b2ea5cd 100644 (file)
@@ -25,6 +25,7 @@ export class VaporFragment {
   anchor?: Node
   insert?: (parent: ParentNode, anchor: Node | null) => void
   remove?: (parent?: ParentNode) => void
+  getNodes?: () => Block
 
   constructor(nodes: Block) {
     this.nodes = nodes
@@ -184,8 +185,8 @@ export function normalizeBlock(block: Block): Node[] {
   } else if (isVaporComponent(block)) {
     nodes.push(...normalizeBlock(block.block!))
   } else {
-    if ((block as any).getNodes) {
-      nodes.push(...normalizeBlock((block as any).getNodes()))
+    if (block.getNodes) {
+      nodes.push(...normalizeBlock(block.getNodes()))
     } else {
       nodes.push(...normalizeBlock(block.nodes))
     }
index 4d5c6d0b78232ae4ea54591ceebe2db01b94bd30..208b2d778726b344f552bb147d0ce373955353c0 100644 (file)
@@ -60,7 +60,11 @@ import {
 import { hmrReload, hmrRerender } from './hmr'
 import { isHydrating, locateHydrationNode } from './dom/hydration'
 import { insertionAnchor, insertionParent } from './insertionState'
-import type { VaporTeleportImpl } from './components/Teleport'
+import {
+  type VaporTeleportImpl,
+  instanceToTeleportMap,
+  teleportStack,
+} from './components/Teleport'
 
 export { currentInstance } from '@vue/runtime-dom'
 
@@ -209,6 +213,11 @@ export function createComponent(
   )
 
   if (__DEV__) {
+    let teleport = teleportStack[teleportStack.length - 1]
+    if (teleport) {
+      instanceToTeleportMap.set(instance, teleport)
+    }
+
     pushWarningContext(instance)
     startMeasure(instance, `init`)
 
@@ -296,7 +305,7 @@ export function createComponent(
   onScopeDispose(() => unmountComponent(instance), true)
 
   if (!isHydrating && _insertionParent) {
-    insert(instance.block, _insertionParent, _insertionAnchor)
+    mountComponent(instance, _insertionParent, _insertionAnchor)
   }
 
   return instance
index 6c0b089bf0048755a12185035132f902bb35d012..ab900951da6e7e44549e8869314f0fa9fcbe973d 100644 (file)
@@ -15,12 +15,45 @@ import {
   remove,
 } from '../block'
 import { createComment, createTextNode, querySelector } from '../dom/node'
-import type { LooseRawProps, LooseRawSlots } from '../component'
+import type {
+  LooseRawProps,
+  LooseRawSlots,
+  VaporComponentInstance,
+} from '../component'
 import { rawPropsProxyHandlers } from '../componentProps'
 import { renderEffect } from '../renderEffect'
-import { extend } from '@vue/shared'
+import { extend, isArray } from '@vue/shared'
 import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
 
+export const teleportStack: TeleportFragment[] = []
+export const instanceToTeleportMap: WeakMap<
+  VaporComponentInstance,
+  TeleportFragment
+> = __DEV__ ? new WeakMap() : (null as any)
+
+/**
+ * dev only.
+ * when the **root** child component updates, synchronously update
+ * the TeleportFragment's children and nodes.
+ */
+export function handleTeleportChildrenHmrReload(
+  instance: VaporComponentInstance,
+  newInstance: VaporComponentInstance,
+): void {
+  const teleport = instanceToTeleportMap.get(instance)
+  if (teleport) {
+    instanceToTeleportMap.set(newInstance, teleport)
+    if (teleport.nodes === instance) {
+      teleport.children = teleport.nodes = newInstance
+    } else if (isArray(teleport.nodes)) {
+      const i = teleport.nodes.indexOf(instance)
+      if (i > -1) {
+        ;(teleport.children as Block[])[i] = teleport.nodes[i] = newInstance
+      }
+    }
+  }
+}
+
 export const VaporTeleportImpl = {
   name: 'VaporTeleport',
   __isTeleport: true,
@@ -34,11 +67,12 @@ export const VaporTeleportImpl = {
     pauseTracking()
     const scope = (frag.scope = new EffectScope())
     scope!.run(() => {
-      let children: Block
       renderEffect(() => {
+        teleportStack.push(frag)
         frag.updateChildren(
-          (children = slots.default && (slots.default as BlockFn)()),
+          (frag.children = slots.default && (slots.default as BlockFn)()),
         )
+        teleportStack.pop()
       })
 
       renderEffect(() => {
@@ -48,15 +82,17 @@ export const VaporTeleportImpl = {
             {},
             new Proxy(props, rawPropsProxyHandlers) as any as TeleportProps,
           ),
-          children!,
+          frag.children!,
         )
       })
     })
     resetTracking()
 
     if (__DEV__) {
-      // TODO
-      ;(frag as any).getNodes = () => {
+      // used in normalizeBlock to get the nodes of a TeleportFragment
+      // during hmr update. return empty array if the teleport content
+      // is mounted into the target container.
+      frag.getNodes = () => {
         return frag.parent !== frag.currentParent ? [] : frag.nodes
       }
     }
@@ -68,6 +104,7 @@ export const VaporTeleportImpl = {
 class TeleportFragment extends VaporFragment {
   anchor: Node
   scope: EffectScope | undefined
+  children: Block | undefined
 
   private targetStart?: Node
   private mainAnchor?: Node
@@ -178,7 +215,7 @@ class TeleportFragment extends VaporFragment {
     // remove nodes
     if (this.nodes) {
       remove(this.nodes, this.currentParent)
-      this.nodes = []
+      this.children = this.nodes = []
     }
 
     // remove anchors
index 5df5d2a469d549b16b04c485453c21d01bf9ce2e..ba669360491c2e71775a6e4fbc9fc1f8f4aa1dc2 100644 (file)
@@ -13,6 +13,7 @@ import {
   mountComponent,
   unmountComponent,
 } from './component'
+import { handleTeleportChildrenHmrReload } from './components/Teleport'
 
 export function hmrRerender(instance: VaporComponentInstance): void {
   const normalized = normalizeBlock(instance.block)
@@ -54,5 +55,5 @@ export function hmrReload(
   )
   simpleSetCurrentInstance(prev, instance.parent)
   mountComponent(newInstance, parent, anchor)
-  instance.block = newInstance.block
+  handleTeleportChildrenHmrReload(instance, newInstance)
 }