wip(vapor): vapor slots in vdom

This commit is contained in:
Evan You 2025-02-07 15:47:06 +08:00
parent 700562866b
commit 23939d09c6
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
11 changed files with 150 additions and 27 deletions

View File

@ -192,6 +192,8 @@ export interface VaporInteropInterface {
update(n1: VNode, n2: VNode, shouldUpdate: boolean): void
unmount(vnode: VNode, doRemove?: boolean): void
move(vnode: VNode, container: any, anchor: any): void
slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
vdomUnmount: UnmountComponentFn
vdomSlot: (

View File

@ -837,8 +837,8 @@ export function setupComponent(
vi(instance)
} else {
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children, optimized)
}
initSlots(instance, children, optimized)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)

View File

@ -8,6 +8,7 @@ import {
Fragment,
type VNode,
type VNodeArrayChildren,
VaporSlot,
createBlock,
createVNode,
isVNode,
@ -31,6 +32,15 @@ export function renderSlot(
fallback?: () => VNodeArrayChildren,
noSlotted?: boolean,
): VNode {
let slot = slots[name]
// vapor slots rendered in vdom
if (slot && slots._vapor) {
const ret = (openBlock(), createBlock(VaporSlot, props))
ret.vs = { slot, fallback }
return ret
}
if (
currentRenderingInstance &&
(currentRenderingInstance.ce ||
@ -53,8 +63,6 @@ export function renderSlot(
)
}
let slot = slots[name]
if (__DEV__ && slot && slot.length > 1) {
warn(
`SSR-optimized slot function detected in a non-SSR-optimized render ` +

View File

@ -505,7 +505,7 @@ export { type VaporInteropInterface } from './apiCreateApp'
/**
* @internal
*/
export { type RendererInternals } from './renderer'
export { type RendererInternals, MoveType } from './renderer'
/**
* @internal
*/

View File

@ -7,6 +7,7 @@ import {
type VNodeArrayChildren,
type VNodeHook,
type VNodeProps,
VaporSlot,
cloneIfMounted,
cloneVNode,
createVNode,
@ -445,6 +446,9 @@ function baseCreateRenderer(
optimized,
)
break
case VaporSlot:
getVaporInterface(parentComponent, n2).slot(n1, n2, container, anchor)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
@ -2186,6 +2190,7 @@ function baseCreateRenderer(
if (shapeFlag & ShapeFlags.COMPONENT) {
if ((type as ConcreteComponent).__vapor) {
getVaporInterface(parentComponent, vnode).unmount(vnode, doRemove)
return
} else {
unmountComponent(vnode.component!, parentSuspense, doRemove)
}
@ -2236,6 +2241,11 @@ function baseCreateRenderer(
unmountChildren(children as VNode[], parentComponent, parentSuspense)
}
if (type === VaporSlot) {
getVaporInterface(parentComponent, vnode).unmount(vnode, doRemove)
return
}
if (doRemove) {
remove(vnode)
}

View File

@ -25,6 +25,7 @@ import type { RawSlots } from './componentSlots'
import {
type ReactiveFlags,
type Ref,
type ShallowRef,
isProxy,
isRef,
toRaw,
@ -70,6 +71,7 @@ export const Fragment = Symbol.for('v-fgt') as any as {
export const Text: unique symbol = Symbol.for('v-txt')
export const Comment: unique symbol = Symbol.for('v-cmt')
export const Static: unique symbol = Symbol.for('v-stc')
export const VaporSlot: unique symbol = Symbol.for('v-vps')
export type VNodeTypes =
| string
@ -83,6 +85,7 @@ export type VNodeTypes =
| typeof TeleportImpl
| typeof Suspense
| typeof SuspenseImpl
| typeof VaporSlot
export type VNodeRef =
| string
@ -258,6 +261,18 @@ export interface VNode<
* @internal VDOM in Vapor interop hook
*/
vi?: (instance: ComponentInternalInstance) => void
/**
* @internal Vapor slot in VDOM metadata
*/
vs?: {
slot: (props: any) => any
fallback: (() => VNodeArrayChildren) | undefined
ref?: ShallowRef<any>
}
/**
* @internal Vapor slot Block
*/
vb?: any
}
// Since v-if and v-for are the two possible ways node structure can dynamically

View File

@ -5,7 +5,7 @@ import {
mountComponent,
unmountComponent,
} from './component'
import { createComment } from './dom/node'
import { createComment, createTextNode } from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
export type Block =
@ -37,9 +37,7 @@ export class DynamicFragment extends VaporFragment {
constructor(anchorLabel?: string) {
super([])
this.anchor =
__DEV__ && anchorLabel
? createComment(anchorLabel)
: document.createTextNode('')
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
}
update(render?: BlockFn, key: any = render): void {

View File

@ -1,7 +1,7 @@
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
import { type Block, type BlockFn, DynamicFragment } from './block'
import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance } from '@vue/runtime-core'
import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component'
import { renderEffect } from './renderEffect'
@ -96,7 +96,7 @@ export function createSlot(
? new Proxy(rawProps, rawPropsProxyHandlers)
: EMPTY_OBJ
if (rawSlots._) {
if (isRef(rawSlots._)) {
return instance.appContext.vapor!.vdomSlot(
rawSlots._,
name,

View File

@ -3,7 +3,7 @@ import {
popWarningContext,
pushWarningContext,
simpleSetCurrentInstance,
} from '@vue/runtime-core'
} from '@vue/runtime-dom'
import { insert, normalizeBlock, remove } from './block'
import {
type VaporComponent,

View File

@ -14,8 +14,8 @@ import { invokeArrayFns } from '@vue/shared'
export function renderEffect(fn: () => void, noLifecycle = false): void {
const instance = currentInstance as VaporComponentInstance | null
const scope = getCurrentScope()
if (__DEV__ && !__TEST__ && !isVaporComponent(instance)) {
warn('renderEffect called without active vapor instance.')
if (__DEV__ && !__TEST__ && !scope && !isVaporComponent(instance)) {
warn('renderEffect called without active EffectScope or Vapor instance.')
}
// renderEffect is always called after user has registered all hooks

View File

@ -1,6 +1,7 @@
import {
type ComponentInternalInstance,
type ConcreteComponent,
MoveType,
type Plugin,
type RendererInternals,
type ShallowRef,
@ -10,6 +11,7 @@ import {
createVNode,
currentInstance,
ensureRenderer,
onScopeDispose,
renderSlot,
shallowRef,
simpleSetCurrentInstance,
@ -24,17 +26,19 @@ import {
unmountComponent,
} from './component'
import { type Block, VaporFragment, insert, remove } from './block'
import { extend, isFunction } from '@vue/shared'
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
import type { RawSlots, VaporSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
import { createTextNode } from './dom/node'
// mounting vapor components and slots in vdom
const vaporInteropImpl: Omit<
VaporInteropInterface,
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
> = {
mount(vnode, container, anchor, parentComponent) {
const selfAnchor = (vnode.anchor = document.createComment('vapor'))
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
container.insertBefore(selfAnchor, anchor)
const prev = currentInstance
simpleSetCurrentInstance(parentComponent)
@ -61,6 +65,7 @@ const vaporInteropImpl: Omit<
update(n1, n2, shouldUpdate) {
n2.component = n1.component
n2.el = n2.anchor = n1.anchor
if (shouldUpdate) {
const instance = n2.component as any as VaporComponentInstance
instance.rawPropsRef!.value = n2.props
@ -70,15 +75,71 @@ const vaporInteropImpl: Omit<
unmount(vnode, doRemove) {
const container = doRemove ? vnode.anchor!.parentNode : undefined
unmountComponent(vnode.component as any, container)
if (vnode.component) {
unmountComponent(vnode.component as any, container)
} else if (vnode.vb) {
remove(vnode.vb, container)
}
remove(vnode.anchor as Node, container)
},
/**
* vapor slot in vdom
*/
slot(n1: VNode, n2: VNode, container, anchor) {
if (!n1) {
// mount
const selfAnchor = (n2.el = n2.anchor = createTextNode())
insert(selfAnchor, container, anchor)
const { slot, fallback } = n2.vs!
const propsRef = (n2.vs!.ref = shallowRef(n2.props))
const slotBlock = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler))
// TODO fallback for slot with v-if content
// fallback is a vnode slot function here, and slotBlock, if a DynamicFragment,
// expects a Vapor BlockFn as fallback
fallback
insert((n2.vb = slotBlock), container, selfAnchor)
} else {
// update
n2.el = n2.anchor = n1.anchor
n2.vb = n1.vb
;(n2.vs!.ref = n1.vs!.ref)!.value = n2.props
}
},
move(vnode, container, anchor) {
insert(vnode.component as any, container, anchor)
insert(vnode.vb || (vnode.component as any), container, anchor)
insert(vnode.anchor as any, container, anchor)
},
}
const vaporSlotPropsProxyHandler: ProxyHandler<
ShallowRef<Record<string, any>>
> = {
get(target, key: any) {
return target.value[key]
},
has(target, key: any) {
return target.value[key]
},
ownKeys(target) {
return Object.keys(target.value)
},
}
const vaporSlotsProxyHandler: ProxyHandler<any> = {
get(target, key) {
if (key === '_vapor') {
return target
} else {
return target[key]
}
},
}
/**
* Mount vdom component in vapor
*/
function createVDOMComponent(
internals: RendererInternals,
component: ConcreteComponent,
@ -100,35 +161,51 @@ function createVDOMComponent(
vnode.vi = (instance: ComponentInternalInstance) => {
instance.props = wrapper.props
instance.attrs = wrapper.attrs
// TODO slots
instance.slots =
wrapper.slots === EMPTY_OBJ
? EMPTY_OBJ
: new Proxy(wrapper.slots, vaporSlotsProxyHandler)
}
let isMounted = false
const parentInstance = currentInstance as VaporComponentInstance
frag.insert = (parent, anchor) => {
const unmount = (parentNode?: ParentNode) => {
internals.umt(vnode.component!, null, !!parentNode)
}
frag.insert = (parentNode, anchor) => {
if (!isMounted) {
internals.mt(
vnode,
parent,
parentNode,
anchor,
parentInstance as any,
null,
undefined,
false,
)
// TODO register unmount with onScopeDispose
onScopeDispose(unmount, true)
isMounted = true
} else {
// TODO move
// move
internals.m(
vnode,
parentNode,
anchor,
MoveType.REORDER,
parentInstance as any,
)
}
}
frag.remove = parentNode => {
internals.umt(vnode.component!, null, !!parentNode)
}
frag.remove = unmount
return frag
}
/**
* Mount vdom slot in vapor
*/
function renderVDOMSlot(
internals: RendererInternals,
slotsRef: ShallowRef<Slots>,
@ -156,7 +233,13 @@ function renderVDOMSlot(
remove(fallbackNodes, parentNode)
fallbackNodes = undefined
}
internals.p(oldVNode, vnode, parent, anchor, parentComponent as any)
internals.p(
oldVNode,
vnode,
parentNode,
anchor,
parentComponent as any,
)
oldVNode = vnode
} else {
if (fallback && !fallbackNodes) {
@ -171,7 +254,14 @@ function renderVDOMSlot(
})
isMounted = true
} else {
// TODO move
// move
internals.m(
oldVNode!,
parentNode,
anchor,
MoveType.REORDER,
parentComponent as any,
)
}
frag.remove = parentNode => {