diff --git a/packages/runtime-core/__tests__/rendererPortal.spec.ts b/packages/runtime-core/__tests__/rendererPortal.spec.ts index e5146c49..c7d600bf 100644 --- a/packages/runtime-core/__tests__/rendererPortal.spec.ts +++ b/packages/runtime-core/__tests__/rendererPortal.spec.ts @@ -12,7 +12,7 @@ import { TestElement, TestNode } from '@vue/runtime-test' -import { VNodeChildren } from '../src/vnode' +import { VNodeArrayChildren } from '../src/vnode' describe('renderer: portal', () => { test('should work', () => { @@ -60,7 +60,7 @@ describe('renderer: portal', () => { test('should update children', async () => { const target = nodeOps.createElement('div') const root = nodeOps.createElement('div') - const children = ref>([ + const children = ref>([ h('div', 'teleported') ]) diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 583e12db..ccac9f93 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -1,5 +1,10 @@ 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' @@ -41,7 +46,7 @@ const normalizeSlot = (key: string, rawSlot: Function): Slot => ( export function resolveSlots( instance: ComponentInternalInstance, - children: NormalizedChildren + children: VNodeNormalizedChildren ) { let slots: InternalSlots | void if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 7fa736ae..bcc2a4bd 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -9,7 +9,7 @@ import { Comment, isSameVNodeType, VNode, - VNodeChildren + VNodeArrayChildren } from '../vnode' import { warn } from '../warning' import { isKeepAlive } from './KeepAlive' @@ -370,7 +370,7 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined { 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 } diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index 2137be2d..fae16b85 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -2,7 +2,7 @@ import { VNode, VNodeProps, createVNode, - VNodeChildren, + VNodeArrayChildren, Fragment, Portal, isVNode @@ -62,7 +62,7 @@ type RawChildren = | number | boolean | VNode - | VNodeChildren + | VNodeArrayChildren | (() => any) // fake constructor type returned from `defineComponent` @@ -85,11 +85,11 @@ export function h( ): 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) diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 7c1635d2..3200dd3d 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -1,7 +1,7 @@ import { Data } from '../component' import { Slot } from '../componentSlots' import { - VNodeChildren, + VNodeArrayChildren, openBlock, createBlock, Fragment, @@ -15,7 +15,7 @@ export function renderSlot( 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 ( diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index df3d04bd..9d821893 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -125,7 +125,13 @@ export { Plugin, CreateAppFunction } from './apiCreateApp' -export { VNode, VNodeTypes, VNodeProps, VNodeChildren } from './vnode' +export { + VNode, + VNodeTypes, + VNodeProps, + VNodeArrayChildren, + VNodeNormalizedChildren +} from './vnode' export { Component, FunctionalComponent, diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index d545d75e..f4c68387 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -6,7 +6,7 @@ import { cloneIfMounted, normalizeVNode, VNode, - VNodeChildren, + VNodeArrayChildren, createVNode, isSameVNodeType } from './vnode' @@ -177,7 +177,7 @@ export function createRenderer< createApp: CreateAppFunction } { type HostVNode = VNode - type HostVNodeChildren = VNodeChildren + type HostVNodeChildren = VNodeArrayChildren type HostSuspenseBoundary = SuspenseBoundary const { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index c23ddd80..2552f6c0 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -71,19 +71,19 @@ type VNodeChildAtom = | null | void -export interface VNodeChildren +export interface VNodeArrayChildren extends Array< - | VNodeChildren + | VNodeArrayChildren | VNodeChildAtom > {} export type VNodeChild = | VNodeChildAtom - | VNodeChildren + | VNodeArrayChildren -export type NormalizedChildren = +export type VNodeNormalizedChildren = | string - | VNodeChildren + | VNodeArrayChildren | RawSlots | null @@ -94,7 +94,7 @@ export interface VNode { key: string | number | null ref: string | Ref | ((ref: object | null) => void) | null scopeId: string | null // SFC only - children: NormalizedChildren + children: VNodeNormalizedChildren component: ComponentInternalInstance | null suspense: SuspenseBoundary | null dirs: DirectiveBinding[] | null @@ -376,7 +376,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) { children = String(children) type = ShapeFlags.TEXT_CHILDREN } - vnode.children = children as NormalizedChildren + vnode.children = children as VNodeNormalizedChildren vnode.shapeFlag |= type } diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 412fdb27..ff3e2749 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -3,21 +3,24 @@ import { 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' @@ -39,6 +42,7 @@ type SSRBuffer = SSRBufferItem[] type SSRBufferItem = string | ResolvedSSRBuffer | Promise type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] type PushFn = (item: SSRBufferItem) => void +type Props = Record function createBuffer() { let appendable = false @@ -79,26 +83,36 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string { } export async function renderToString(app: App): Promise { - 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 { + return renderComponentVNode( + createVNode(comp, props, children), + parentComponent + ) +} + +function renderComponentVNode( vnode: VNode, parentComponent: ComponentInternalInstance | null = null ): ResolvedSSRBuffer | Promise { 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 { const comp = instance.type as Component @@ -141,7 +155,7 @@ export function renderVNode( break case Fragment: push(``) - renderVNodeChildren(push, children as VNodeChildren, parentComponent) + renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent) push(``) break case Portal: @@ -151,7 +165,7 @@ export function renderVNode( 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 { @@ -166,7 +180,7 @@ export function renderVNode( function renderVNodeChildren( push: PushFn, - children: VNodeChildren, + children: VNodeArrayChildren, parentComponent: ComponentInternalInstance | null = null ) { for (let i = 0; i < children.length; i++) { @@ -218,13 +232,37 @@ function renderElement( 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(``) } } -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(``) }