Skip to content

Commit 99d70dd

Browse files
committed
wip(vapor): vdom slots in vapor component
1 parent a770a83 commit 99d70dd

File tree

7 files changed

+131
-32
lines changed

7 files changed

+131
-32
lines changed

packages/runtime-core/src/apiCreateApp.ts

+7
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ export interface VaporInteropInterface {
194194
move(vnode: VNode, container: any, anchor: any): void
195195
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
196196
vdomUnmount: UnmountComponentFn
197+
vdomSlot: (
198+
slots: any,
199+
name: string | (() => string),
200+
props: Record<string, any>,
201+
parentComponent: any, // VaporComponentInstance
202+
fallback?: any, // VaporSlot
203+
) => any
197204
}
198205

199206
/**

packages/runtime-core/src/helpers/renderSlot.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ export function renderSlot(
3232
noSlotted?: boolean,
3333
): VNode {
3434
if (
35-
currentRenderingInstance!.ce ||
36-
(currentRenderingInstance!.parent &&
37-
isAsyncWrapper(currentRenderingInstance!.parent) &&
38-
currentRenderingInstance!.parent.ce)
35+
currentRenderingInstance &&
36+
(currentRenderingInstance.ce ||
37+
(currentRenderingInstance.parent &&
38+
isAsyncWrapper(currentRenderingInstance.parent) &&
39+
currentRenderingInstance.parent.ce))
3940
) {
4041
// in custom element mode, render <slot/> as actual slot outlets
4142
// wrap it with a fragment because in shadowRoot: false mode the slot

packages/runtime-core/src/hmr.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type GenericComponentInstance,
88
isClassComponent,
99
} from './component'
10-
import { queueJob, queuePostFlushCb } from './scheduler'
10+
import { nextTick, queueJob, queuePostFlushCb } from './scheduler'
1111
import { extend, getGlobalThis } from '@vue/shared'
1212

1313
type HMRComponent = ComponentOptions | ClassComponent
@@ -102,7 +102,9 @@ function rerender(id: string, newRender?: Function): void {
102102
i.renderCache = []
103103
i.update()
104104
}
105-
isHmrUpdating = false
105+
nextTick(() => {
106+
isHmrUpdating = false
107+
})
106108
})
107109
}
108110

@@ -160,7 +162,9 @@ function reload(id: string, newComp: HMRComponent): void {
160162
} else {
161163
;(parent as ComponentInternalInstance).update()
162164
}
163-
isHmrUpdating = false
165+
nextTick(() => {
166+
isHmrUpdating = false
167+
})
164168
// #6930, #11248 avoid infinite recursion
165169
dirtyInstances.delete(instance)
166170
})

packages/runtime-core/src/renderer.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -2530,15 +2530,17 @@ function resolveChildrenNamespace(
25302530
}
25312531

25322532
function toggleRecurse(
2533-
{ effect, job }: ComponentInternalInstance,
2533+
{ effect, job, vapor }: ComponentInternalInstance,
25342534
allowed: boolean,
25352535
) {
2536-
if (allowed) {
2537-
effect.flags |= EffectFlags.ALLOW_RECURSE
2538-
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
2539-
} else {
2540-
effect.flags &= ~EffectFlags.ALLOW_RECURSE
2541-
job.flags! &= ~SchedulerJobFlags.ALLOW_RECURSE
2536+
if (!vapor) {
2537+
if (allowed) {
2538+
effect.flags |= EffectFlags.ALLOW_RECURSE
2539+
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
2540+
} else {
2541+
effect.flags &= ~EffectFlags.ALLOW_RECURSE
2542+
job.flags! &= ~SchedulerJobFlags.ALLOW_RECURSE
2543+
}
25422544
}
25432545
}
25442546

packages/runtime-vapor/src/component.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,13 @@ export class VaporComponentInstance implements GenericComponentInstance {
313313
props: Record<string, any>
314314
attrs: Record<string, any>
315315
propsDefaults: Record<string, any> | null
316-
rawPropsRef?: ShallowRef<any> // to hold vnode props in vdom interop mode
317316

318317
slots: StaticSlots
319318

319+
// to hold vnode props / slots in vdom interop mode
320+
rawPropsRef?: ShallowRef<any>
321+
rawSlotsRef?: ShallowRef<any>
322+
320323
emit: EmitFn
321324
emitted: Record<string, boolean> | null
322325

packages/runtime-vapor/src/componentSlots.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,29 @@ export function getSlot(
8585
}
8686
}
8787

88-
// TODO how to handle empty slot return blocks?
89-
// e.g. a slot renders a v-if node that may toggle inside.
90-
// we may need special handling by passing the fallback into the slot
91-
// and make the v-if use it as fallback
9288
export function createSlot(
9389
name: string | (() => string),
9490
rawProps?: LooseRawProps | null,
9591
fallback?: VaporSlot,
9692
): Block {
9793
const instance = currentInstance as VaporComponentInstance
9894
const rawSlots = instance.rawSlots
99-
const isDynamicName = isFunction(name)
100-
const fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
10195
const slotProps = rawProps
10296
? new Proxy(rawProps, rawPropsProxyHandlers)
10397
: EMPTY_OBJ
10498

99+
if (rawSlots._) {
100+
return instance.appContext.vapor!.vdomSlot(
101+
rawSlots._,
102+
name,
103+
slotProps,
104+
instance,
105+
fallback,
106+
)
107+
}
108+
109+
const isDynamicName = isFunction(name)
110+
const fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
105111
const renderSlot = () => {
106112
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
107113
if (slot) {

packages/runtime-vapor/src/vdomInterop.ts

+87-11
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,57 @@ import {
33
type ConcreteComponent,
44
type Plugin,
55
type RendererInternals,
6+
type ShallowRef,
7+
type Slots,
8+
type VNode,
69
type VaporInteropInterface,
710
createVNode,
811
currentInstance,
912
ensureRenderer,
13+
renderSlot,
1014
shallowRef,
1115
simpleSetCurrentInstance,
1216
} from '@vue/runtime-dom'
1317
import {
1418
type LooseRawProps,
1519
type LooseRawSlots,
20+
type VaporComponent,
1621
VaporComponentInstance,
1722
createComponent,
1823
mountComponent,
1924
unmountComponent,
2025
} from './component'
21-
import { VaporFragment, insert } from './block'
22-
import { extend, remove } from '@vue/shared'
26+
import { type Block, VaporFragment, insert, remove } from './block'
27+
import { extend, isFunction, remove as removeItem } from '@vue/shared'
2328
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
24-
import type { RawSlots } from './componentSlots'
29+
import type { RawSlots, VaporSlot } from './componentSlots'
30+
import { renderEffect } from './renderEffect'
2531

2632
const vaporInteropImpl: Omit<
2733
VaporInteropInterface,
28-
'vdomMount' | 'vdomUnmount'
34+
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
2935
> = {
3036
mount(vnode, container, anchor, parentComponent) {
3137
const selfAnchor = (vnode.anchor = document.createComment('vapor'))
3238
container.insertBefore(selfAnchor, anchor)
3339
const prev = currentInstance
3440
simpleSetCurrentInstance(parentComponent)
41+
3542
const propsRef = shallowRef(vnode.props)
43+
const slotsRef = shallowRef(vnode.children)
44+
3645
// @ts-expect-error
37-
const instance = (vnode.component = createComponent(vnode.type, {
38-
$: [() => propsRef.value],
39-
}))
46+
const instance = (vnode.component = createComponent(
47+
vnode.type as any as VaporComponent,
48+
{
49+
$: [() => propsRef.value],
50+
} as RawProps,
51+
{
52+
_: slotsRef, // pass the slots ref
53+
} as any as RawSlots,
54+
))
4055
instance.rawPropsRef = propsRef
56+
instance.rawSlotsRef = slotsRef
4157
mountComponent(instance, container, selfAnchor)
4258
simpleSetCurrentInstance(prev)
4359
return instance
@@ -46,8 +62,9 @@ const vaporInteropImpl: Omit<
4662
update(n1, n2, shouldUpdate) {
4763
n2.component = n1.component
4864
if (shouldUpdate) {
49-
;(n2.component as any as VaporComponentInstance).rawPropsRef!.value =
50-
n2.props
65+
const instance = n2.component as any as VaporComponentInstance
66+
instance.rawPropsRef!.value = n2.props
67+
instance.rawSlotsRef!.value = n2.children
5168
}
5269
},
5370

@@ -109,8 +126,66 @@ function createVDOMComponent(
109126
}
110127
frag.remove = () => {
111128
internals.umt(vnode.component!, null, true)
112-
remove(parentInstance.vdomChildren!, vnode.component)
113-
isMounted = false
129+
removeItem(parentInstance.vdomChildren!, vnode.component)
130+
}
131+
132+
return frag
133+
}
134+
135+
function renderVDOMSlot(
136+
internals: RendererInternals,
137+
slotsRef: ShallowRef<Slots>,
138+
name: string | (() => string),
139+
props: Record<string, any>,
140+
parentComponent: VaporComponentInstance,
141+
fallback?: VaporSlot,
142+
): VaporFragment {
143+
const frag = new VaporFragment([])
144+
145+
let isMounted = false
146+
let fallbackNodes: Block | undefined
147+
let parentNode: ParentNode
148+
let oldVNode: VNode | null = null
149+
150+
frag.insert = (parent, anchor) => {
151+
parentNode = parent
152+
if (!isMounted) {
153+
renderEffect(() => {
154+
const vnode = renderSlot(
155+
slotsRef.value,
156+
isFunction(name) ? name() : name,
157+
props,
158+
)
159+
if ((vnode.children as any[]).length) {
160+
if (fallbackNodes) {
161+
remove(fallbackNodes, parentNode)
162+
fallbackNodes = undefined
163+
}
164+
internals.p(oldVNode, vnode, parent, anchor, parentComponent as any)
165+
oldVNode = vnode
166+
} else {
167+
if (fallback && !fallbackNodes) {
168+
// mount fallback
169+
if (oldVNode) {
170+
internals.um(oldVNode, parentComponent as any, null, true)
171+
}
172+
insert((fallbackNodes = fallback(props)), parent, anchor)
173+
}
174+
oldVNode = null
175+
}
176+
})
177+
isMounted = true
178+
} else {
179+
// TODO move
180+
}
181+
182+
frag.remove = () => {
183+
if (fallbackNodes) {
184+
remove(fallbackNodes, parentNode)
185+
} else if (oldVNode) {
186+
internals.um(oldVNode, parentComponent as any, null)
187+
}
188+
}
114189
}
115190

116191
return frag
@@ -121,5 +196,6 @@ export const vaporInteropPlugin: Plugin = app => {
121196
app._context.vapor = extend(vaporInteropImpl, {
122197
vdomMount: createVDOMComponent.bind(null, internals),
123198
vdomUnmount: internals.umt,
199+
vdomSlot: renderVDOMSlot.bind(null, internals),
124200
})
125201
}

0 commit comments

Comments
 (0)