wip: fix teleport root component hmr reload

This commit is contained in:
daiwei 2025-03-26 17:30:32 +08:00
parent b6468562e6
commit b474ce0a1e
5 changed files with 62 additions and 16 deletions

View 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()

View 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))
}

View 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

View 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

View 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)
}