From 9be697b38cbe85807c39c41dd5e0d5563df02589 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 20 Mar 2025 22:17:57 +0800 Subject: [PATCH] wip: save --- .../runtime-core/src/components/Teleport.ts | 6 +- packages/runtime-core/src/index.ts | 9 + packages/runtime-vapor/src/block.ts | 4 +- packages/runtime-vapor/src/component.ts | 18 ++ .../runtime-vapor/src/components/Teleport.ts | 162 ++++++++++++++++++ packages/runtime-vapor/src/index.ts | 1 + 6 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 packages/runtime-vapor/src/components/Teleport.ts diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index a6445df7b0..c365ad0a21 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -27,10 +27,10 @@ export const TeleportEndKey: unique symbol = Symbol('_vte') export const isTeleport = (type: any): boolean => type.__isTeleport -const isTeleportDisabled = (props: VNode['props']): boolean => +export const isTeleportDisabled = (props: VNode['props']): boolean => props && (props.disabled || props.disabled === '') -const isTeleportDeferred = (props: VNode['props']): boolean => +export const isTeleportDeferred = (props: VNode['props']): boolean => props && (props.defer || props.defer === '') const isTargetSVG = (target: RendererElement): boolean => @@ -39,7 +39,7 @@ const isTargetSVG = (target: RendererElement): boolean => const isTargetMathML = (target: RendererElement): boolean => typeof MathMLElement === 'function' && target instanceof MathMLElement -const resolveTarget = ( +export const resolveTarget = ( props: TeleportProps | null, select: RendererOptions['querySelector'], ): T | null => { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index c7150e38e8..2d721b058f 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -557,3 +557,12 @@ export { startMeasure, endMeasure } from './profiling' * @internal */ export { initFeatureFlags } from './featureFlags' +/** + * @internal + */ +export { + resolveTarget, + isTeleportDisabled, + isTeleportDeferred, + TeleportEndKey, +} from './components/Teleport' diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index b782afd38d..6ec62da20d 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -20,6 +20,8 @@ export type BlockFn = (...args: any[]) => Block export class VaporFragment { nodes: Block + target?: ParentNode | null + targetAnchor?: Node | null anchor?: Node insert?: (parent: ParentNode, anchor: Node | null) => void remove?: (parent?: ParentNode) => void @@ -129,7 +131,7 @@ export function insert( // TODO handle hydration for vdom interop block.insert(parent, anchor) } else { - insert(block.nodes, parent, anchor) + insert(block.nodes, block.target || parent, block.targetAnchor || anchor) } if (block.anchor) insert(block.anchor, parent, anchor) } diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 548babebf8..7989b67a8b 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -60,6 +60,7 @@ import { import { hmrReload, hmrRerender } from './hmr' import { isHydrating, locateHydrationNode } from './dom/hydration' import { insertionAnchor, insertionParent } from './insertionState' +import type { VaporTeleportImpl } from './components/Teleport' export { currentInstance } from '@vue/runtime-dom' @@ -92,6 +93,8 @@ export interface ObjectVaporComponent name?: string vapor?: boolean + + __isTeleport?: boolean } interface SharedInternalOptions { @@ -157,6 +160,21 @@ export function createComponent( return frag } + // teleport + if (component.__isTeleport) { + const frag = (component as typeof VaporTeleportImpl).process( + rawProps!, + rawSlots!, + ) + if (!isHydrating && _insertionParent) { + insert(frag, _insertionParent, _insertionAnchor) + } else { + frag.hydrate() + } + + return frag as any + } + if ( isSingleRoot && component.inheritAttrs !== false && diff --git a/packages/runtime-vapor/src/components/Teleport.ts b/packages/runtime-vapor/src/components/Teleport.ts new file mode 100644 index 0000000000..762d69ce81 --- /dev/null +++ b/packages/runtime-vapor/src/components/Teleport.ts @@ -0,0 +1,162 @@ +import { + TeleportEndKey, + type TeleportProps, + isTeleportDeferred, + isTeleportDisabled, + queuePostFlushCb, + resolveTarget, + warn, +} from '@vue/runtime-dom' +import { + type Block, + type BlockFn, + VaporFragment, + insert, + remove, +} from '../block' +import { createComment, createTextNode, querySelector } from '../dom/node' +import type { LooseRawProps, LooseRawSlots } from '../component' +import { rawPropsProxyHandlers } from '../componentProps' +import { renderEffect } from '../renderEffect' + +export const VaporTeleportImpl = { + name: 'VaporTeleport', + __isTeleport: true, + __vapor: true, + + process(props: LooseRawProps, slots: LooseRawSlots): TeleportFragment { + const children = slots.default && (slots.default as BlockFn)() + const frag = __DEV__ + ? new TeleportFragment('teleport') + : new TeleportFragment() + + const resolvedProps = new Proxy( + props, + rawPropsProxyHandlers, + ) as any as TeleportProps + + renderEffect(() => frag.update(resolvedProps, children)) + + frag.remove = parent => { + const { + nodes, + target, + cachedTargetAnchor, + targetStart, + placeholder, + mainAnchor, + } = frag + + remove(nodes, target || parent) + + // remove anchors + if (targetStart) { + let parentNode = targetStart.parentNode! + remove(targetStart!, parentNode) + remove(cachedTargetAnchor!, parentNode) + } + if (placeholder && placeholder.isConnected) { + remove(placeholder!, parent) + remove(mainAnchor!, parent) + } + } + + return frag + }, +} + +export class TeleportFragment extends VaporFragment { + anchor: Node + target?: ParentNode | null + targetStart?: Node | null + targetAnchor?: Node | null + cachedTargetAnchor?: Node + mainAnchor?: Node + placeholder?: Node + + constructor(anchorLabel?: string) { + super([]) + this.anchor = + __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode() + } + + update(props: TeleportProps, children: Block): void { + this.nodes = children + const parent = this.anchor.parentNode + + if (!this.mainAnchor) { + this.mainAnchor = __DEV__ + ? createComment('teleport end') + : createTextNode() + } + if (!this.placeholder) { + this.placeholder = __DEV__ + ? createComment('teleport start') + : createTextNode() + } + if (parent) { + insert(this.placeholder, parent, this.anchor) + insert(this.mainAnchor, parent, this.anchor) + } + + const disabled = isTeleportDisabled(props) + if (disabled) { + this.target = this.anchor.parentNode + this.targetAnchor = parent ? this.mainAnchor : null + } else { + const target = (this.target = resolveTarget( + props, + querySelector, + ) as ParentNode) + if (target) { + if ( + // initial mount + !this.targetStart || + // target changed + this.targetStart.parentNode !== target + ) { + ;[this.targetAnchor, this.targetStart] = prepareAnchor(target) + this.cachedTargetAnchor = this.targetAnchor + } else { + // re-mount or target not changed, use cached target anchor + this.targetAnchor = this.cachedTargetAnchor + } + } else if (__DEV__) { + warn('Invalid Teleport target on mount:', target, `(${typeof target})`) + } + } + + const mountToTarget = () => { + insert(this.nodes, this.target!, this.targetAnchor) + } + + if (parent) { + if (isTeleportDeferred(props)) { + queuePostFlushCb(mountToTarget) + } else { + mountToTarget() + } + } + } + + hydrate(): void { + // TODO + } +} + +function prepareAnchor(target: ParentNode | null) { + const targetStart = createTextNode('targetStart') + const targetAnchor = createTextNode('targetAnchor') + + // attach a special property, so we can skip teleported content in + // renderer's nextSibling search + // @ts-expect-error + targetStart[TeleportEndKey] = targetAnchor + + if (target) { + insert(targetStart, target) + insert(targetAnchor, target) + } + + return [targetAnchor, targetStart] +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 682532fa4d..4b55949a63 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -3,6 +3,7 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp' export { defineVaporComponent } from './apiDefineComponent' export { vaporInteropPlugin } from './vdomInterop' export type { VaporDirective } from './directives/custom' +export { VaporTeleportImpl as VaporTeleport } from './components/Teleport' // compiler-use only export { insert, prepend, remove, isFragment, VaporFragment } from './block' -- 2.47.3