TestElement,
TestNode
} from '@vue/runtime-test'
-import { VNodeChildren } from '../src/vnode'
+import { VNodeArrayChildren } from '../src/vnode'
describe('renderer: portal', () => {
test('should work', () => {
test('should update children', async () => {
const target = nodeOps.createElement('div')
const root = nodeOps.createElement('div')
- const children = ref<VNodeChildren<TestNode, TestElement>>([
+ const children = ref<VNodeArrayChildren<TestNode, TestElement>>([
h('div', 'teleported')
])
import { ComponentInternalInstance, currentInstance } from './component'
-import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
+import {
+ VNode,
+ VNodeNormalizedChildren,
+ normalizeVNode,
+ VNodeChild
+} from './vnode'
import { isArray, isFunction, EMPTY_OBJ } from '@vue/shared'
import { ShapeFlags } from './shapeFlags'
import { warn } from './warning'
export function resolveSlots(
instance: ComponentInternalInstance,
- children: NormalizedChildren
+ children: VNodeNormalizedChildren
) {
let slots: InternalSlots | void
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
Comment,
isSameVNodeType,
VNode,
- VNodeChildren
+ VNodeArrayChildren
} from '../vnode'
import { warn } from '../warning'
import { isKeepAlive } from './KeepAlive'
function getKeepAliveChild(vnode: VNode): VNode | undefined {
return isKeepAlive(vnode)
? vnode.children
- ? ((vnode.children as VNodeChildren)[0] as VNode)
+ ? ((vnode.children as VNodeArrayChildren)[0] as VNode)
: undefined
: vnode
}
VNode,
VNodeProps,
createVNode,
- VNodeChildren,
+ VNodeArrayChildren,
Fragment,
Portal,
isVNode
| number
| boolean
| VNode
- | VNodeChildren
+ | VNodeArrayChildren
| (() => any)
// fake constructor type returned from `defineComponent`
): VNode
// fragment
-export function h(type: typeof Fragment, children?: VNodeChildren): VNode
+export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
export function h(
type: typeof Fragment,
props?: RawProps | null,
- children?: VNodeChildren
+ children?: VNodeArrayChildren
): VNode
// portal (target prop is required)
import { Data } from '../component'
import { Slot } from '../componentSlots'
import {
- VNodeChildren,
+ VNodeArrayChildren,
openBlock,
createBlock,
Fragment,
props: Data = {},
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be an array
- fallback?: VNodeChildren
+ fallback?: VNodeArrayChildren
): VNode {
const slot = slots[name]
return (
Plugin,
CreateAppFunction
} from './apiCreateApp'
-export { VNode, VNodeTypes, VNodeProps, VNodeChildren } from './vnode'
+export {
+ VNode,
+ VNodeTypes,
+ VNodeProps,
+ VNodeArrayChildren,
+ VNodeNormalizedChildren
+} from './vnode'
export {
Component,
FunctionalComponent,
cloneIfMounted,
normalizeVNode,
VNode,
- VNodeChildren,
+ VNodeArrayChildren,
createVNode,
isSameVNodeType
} from './vnode'
createApp: CreateAppFunction<HostElement>
} {
type HostVNode = VNode<HostNode, HostElement>
- type HostVNodeChildren = VNodeChildren<HostNode, HostElement>
+ type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
const {
| null
| void
-export interface VNodeChildren<HostNode = any, HostElement = any>
+export interface VNodeArrayChildren<HostNode = any, HostElement = any>
extends Array<
- | VNodeChildren<HostNode, HostElement>
+ | VNodeArrayChildren<HostNode, HostElement>
| VNodeChildAtom<HostNode, HostElement>
> {}
export type VNodeChild<HostNode = any, HostElement = any> =
| VNodeChildAtom<HostNode, HostElement>
- | VNodeChildren<HostNode, HostElement>
+ | VNodeArrayChildren<HostNode, HostElement>
-export type NormalizedChildren<HostNode = any, HostElement = any> =
+export type VNodeNormalizedChildren<HostNode = any, HostElement = any> =
| string
- | VNodeChildren<HostNode, HostElement>
+ | VNodeArrayChildren<HostNode, HostElement>
| RawSlots
| null
key: string | number | null
ref: string | Ref | ((ref: object | null) => void) | null
scopeId: string | null // SFC only
- children: NormalizedChildren<HostNode, HostElement>
+ children: VNodeNormalizedChildren<HostNode, HostElement>
component: ComponentInternalInstance | null
suspense: SuspenseBoundary<HostNode, HostElement> | null
dirs: DirectiveBinding[] | null
children = String(children)
type = ShapeFlags.TEXT_CHILDREN
}
- vnode.children = children as NormalizedChildren
+ vnode.children = children as VNodeNormalizedChildren
vnode.shapeFlag |= type
}
Component,
ComponentInternalInstance,
VNode,
- VNodeChildren,
+ VNodeArrayChildren,
+ VNodeNormalizedChildren,
createVNode,
Text,
Comment,
Fragment,
Portal,
ShapeFlags,
- ssrUtils
+ ssrUtils,
+ Slot
} from 'vue'
import {
isString,
isPromise,
isArray,
isFunction,
- isVoidTag
+ isVoidTag,
+ EMPTY_OBJ
} from '@vue/shared'
import { renderProps } from './renderProps'
import { escape } from './escape'
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
type PushFn = (item: SSRBufferItem) => void
+type Props = Record<string, unknown>
function createBuffer() {
let appendable = false
}
export async function renderToString(app: App): Promise<string> {
- const resolvedBuffer = await renderComponent(
- createVNode(app._component, app._props)
- )
+ const resolvedBuffer = await renderComponent(app._component, app._props, null)
return unrollBuffer(resolvedBuffer)
}
export function renderComponent(
+ comp: Component,
+ props: Props | null,
+ children: VNodeNormalizedChildren | null,
+ parentComponent: ComponentInternalInstance | null = null
+): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
+ return renderComponentVNode(
+ createVNode(comp, props, children),
+ parentComponent
+ )
+}
+
+function renderComponentVNode(
vnode: VNode,
parentComponent: ComponentInternalInstance | null = null
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const instance = createComponentInstance(vnode, parentComponent)
const res = setupComponent(instance, null)
if (isPromise(res)) {
- return res.then(() => innerRenderComponent(instance))
+ return res.then(() => renderComponentSubTree(instance))
} else {
- return innerRenderComponent(instance)
+ return renderComponentSubTree(instance)
}
}
-function innerRenderComponent(
+function renderComponentSubTree(
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const comp = instance.type as Component
break
case Fragment:
push(`<!---->`)
- renderVNodeChildren(push, children as VNodeChildren, parentComponent)
+ renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent)
push(`<!---->`)
break
case Portal:
if (shapeFlag & ShapeFlags.ELEMENT) {
renderElement(push, vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
- push(renderComponent(vnode, parentComponent))
+ push(renderComponentVNode(vnode, parentComponent))
} else if (shapeFlag & ShapeFlags.SUSPENSE) {
// TODO
} else {
function renderVNodeChildren(
push: PushFn,
- children: VNodeChildren,
+ children: VNodeArrayChildren,
parentComponent: ComponentInternalInstance | null = null
) {
for (let i = 0; i < children.length; i++) {
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
push(escape(children as string))
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- renderVNodeChildren(push, children as VNodeChildren, parentComponent)
+ renderVNodeChildren(
+ push,
+ children as VNodeArrayChildren,
+ parentComponent
+ )
}
}
push(`</${tag}>`)
}
}
-export function renderSlot() {
- // TODO
+type OptimizedSlotFn = (
+ props: Props,
+ push: PushFn,
+ parentComponent: ComponentInternalInstance | null
+) => void
+
+export function renderSlot(
+ slotFn: Slot | OptimizedSlotFn,
+ slotProps: Props,
+ push: PushFn,
+ parentComponent: ComponentInternalInstance | null = null
+) {
+ // template-compiled slots are always rendered as fragments
+ push(`<!---->`)
+ if (slotFn.length > 2) {
+ // only ssr-optimized slot fns accept 3 arguments
+ slotFn(slotProps, push, parentComponent)
+ } else {
+ // normal slot
+ renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
+ }
+ push(`<!---->`)
}