wip(ssr): renderer support for optimized and manual slots

This commit is contained in:
Evan You 2020-01-28 22:58:02 -05:00
parent a7b0954f14
commit 6b1ce00621
9 changed files with 86 additions and 37 deletions

View File

@ -12,7 +12,7 @@ import {
TestElement, TestElement,
TestNode TestNode
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { VNodeChildren } from '../src/vnode' import { VNodeArrayChildren } from '../src/vnode'
describe('renderer: portal', () => { describe('renderer: portal', () => {
test('should work', () => { test('should work', () => {
@ -60,7 +60,7 @@ describe('renderer: portal', () => {
test('should update children', async () => { test('should update children', async () => {
const target = nodeOps.createElement('div') const target = nodeOps.createElement('div')
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
const children = ref<VNodeChildren<TestNode, TestElement>>([ const children = ref<VNodeArrayChildren<TestNode, TestElement>>([
h('div', 'teleported') h('div', 'teleported')
]) ])

View File

@ -1,5 +1,10 @@
import { ComponentInternalInstance, currentInstance } from './component' 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 { isArray, isFunction, EMPTY_OBJ } from '@vue/shared'
import { ShapeFlags } from './shapeFlags' import { ShapeFlags } from './shapeFlags'
import { warn } from './warning' import { warn } from './warning'
@ -41,7 +46,7 @@ const normalizeSlot = (key: string, rawSlot: Function): Slot => (
export function resolveSlots( export function resolveSlots(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
children: NormalizedChildren children: VNodeNormalizedChildren
) { ) {
let slots: InternalSlots | void let slots: InternalSlots | void
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {

View File

@ -9,7 +9,7 @@ import {
Comment, Comment,
isSameVNodeType, isSameVNodeType,
VNode, VNode,
VNodeChildren VNodeArrayChildren
} from '../vnode' } from '../vnode'
import { warn } from '../warning' import { warn } from '../warning'
import { isKeepAlive } from './KeepAlive' import { isKeepAlive } from './KeepAlive'
@ -370,7 +370,7 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
function getKeepAliveChild(vnode: VNode): VNode | undefined { function getKeepAliveChild(vnode: VNode): VNode | undefined {
return isKeepAlive(vnode) return isKeepAlive(vnode)
? vnode.children ? vnode.children
? ((vnode.children as VNodeChildren)[0] as VNode) ? ((vnode.children as VNodeArrayChildren)[0] as VNode)
: undefined : undefined
: vnode : vnode
} }

View File

@ -2,7 +2,7 @@ import {
VNode, VNode,
VNodeProps, VNodeProps,
createVNode, createVNode,
VNodeChildren, VNodeArrayChildren,
Fragment, Fragment,
Portal, Portal,
isVNode isVNode
@ -62,7 +62,7 @@ type RawChildren =
| number | number
| boolean | boolean
| VNode | VNode
| VNodeChildren | VNodeArrayChildren
| (() => any) | (() => any)
// fake constructor type returned from `defineComponent` // fake constructor type returned from `defineComponent`
@ -85,11 +85,11 @@ export function h(
): VNode ): VNode
// fragment // fragment
export function h(type: typeof Fragment, children?: VNodeChildren): VNode export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
export function h( export function h(
type: typeof Fragment, type: typeof Fragment,
props?: RawProps | null, props?: RawProps | null,
children?: VNodeChildren children?: VNodeArrayChildren
): VNode ): VNode
// portal (target prop is required) // portal (target prop is required)

View File

@ -1,7 +1,7 @@
import { Data } from '../component' import { Data } from '../component'
import { Slot } from '../componentSlots' import { Slot } from '../componentSlots'
import { import {
VNodeChildren, VNodeArrayChildren,
openBlock, openBlock,
createBlock, createBlock,
Fragment, Fragment,
@ -15,7 +15,7 @@ export function renderSlot(
props: Data = {}, props: Data = {},
// this is not a user-facing function, so the fallback is always generated by // this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be an array // the compiler and guaranteed to be an array
fallback?: VNodeChildren fallback?: VNodeArrayChildren
): VNode { ): VNode {
const slot = slots[name] const slot = slots[name]
return ( return (

View File

@ -125,7 +125,13 @@ export {
Plugin, Plugin,
CreateAppFunction CreateAppFunction
} from './apiCreateApp' } from './apiCreateApp'
export { VNode, VNodeTypes, VNodeProps, VNodeChildren } from './vnode' export {
VNode,
VNodeTypes,
VNodeProps,
VNodeArrayChildren,
VNodeNormalizedChildren
} from './vnode'
export { export {
Component, Component,
FunctionalComponent, FunctionalComponent,

View File

@ -6,7 +6,7 @@ import {
cloneIfMounted, cloneIfMounted,
normalizeVNode, normalizeVNode,
VNode, VNode,
VNodeChildren, VNodeArrayChildren,
createVNode, createVNode,
isSameVNodeType isSameVNodeType
} from './vnode' } from './vnode'
@ -177,7 +177,7 @@ export function createRenderer<
createApp: CreateAppFunction<HostElement> createApp: CreateAppFunction<HostElement>
} { } {
type HostVNode = VNode<HostNode, HostElement> type HostVNode = VNode<HostNode, HostElement>
type HostVNodeChildren = VNodeChildren<HostNode, HostElement> type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement> type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
const { const {

View File

@ -71,19 +71,19 @@ type VNodeChildAtom<HostNode, HostElement> =
| null | null
| void | void
export interface VNodeChildren<HostNode = any, HostElement = any> export interface VNodeArrayChildren<HostNode = any, HostElement = any>
extends Array< extends Array<
| VNodeChildren<HostNode, HostElement> | VNodeArrayChildren<HostNode, HostElement>
| VNodeChildAtom<HostNode, HostElement> | VNodeChildAtom<HostNode, HostElement>
> {} > {}
export type VNodeChild<HostNode = any, HostElement = any> = export type VNodeChild<HostNode = any, HostElement = any> =
| VNodeChildAtom<HostNode, HostElement> | VNodeChildAtom<HostNode, HostElement>
| VNodeChildren<HostNode, HostElement> | VNodeArrayChildren<HostNode, HostElement>
export type NormalizedChildren<HostNode = any, HostElement = any> = export type VNodeNormalizedChildren<HostNode = any, HostElement = any> =
| string | string
| VNodeChildren<HostNode, HostElement> | VNodeArrayChildren<HostNode, HostElement>
| RawSlots | RawSlots
| null | null
@ -94,7 +94,7 @@ export interface VNode<HostNode = any, HostElement = any> {
key: string | number | null key: string | number | null
ref: string | Ref | ((ref: object | null) => void) | null ref: string | Ref | ((ref: object | null) => void) | null
scopeId: string | null // SFC only scopeId: string | null // SFC only
children: NormalizedChildren<HostNode, HostElement> children: VNodeNormalizedChildren<HostNode, HostElement>
component: ComponentInternalInstance | null component: ComponentInternalInstance | null
suspense: SuspenseBoundary<HostNode, HostElement> | null suspense: SuspenseBoundary<HostNode, HostElement> | null
dirs: DirectiveBinding[] | null dirs: DirectiveBinding[] | null
@ -376,7 +376,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
children = String(children) children = String(children)
type = ShapeFlags.TEXT_CHILDREN type = ShapeFlags.TEXT_CHILDREN
} }
vnode.children = children as NormalizedChildren vnode.children = children as VNodeNormalizedChildren
vnode.shapeFlag |= type vnode.shapeFlag |= type
} }

View File

@ -3,21 +3,24 @@ import {
Component, Component,
ComponentInternalInstance, ComponentInternalInstance,
VNode, VNode,
VNodeChildren, VNodeArrayChildren,
VNodeNormalizedChildren,
createVNode, createVNode,
Text, Text,
Comment, Comment,
Fragment, Fragment,
Portal, Portal,
ShapeFlags, ShapeFlags,
ssrUtils ssrUtils,
Slot
} from 'vue' } from 'vue'
import { import {
isString, isString,
isPromise, isPromise,
isArray, isArray,
isFunction, isFunction,
isVoidTag isVoidTag,
EMPTY_OBJ
} from '@vue/shared' } from '@vue/shared'
import { renderProps } from './renderProps' import { renderProps } from './renderProps'
import { escape } from './escape' import { escape } from './escape'
@ -39,6 +42,7 @@ type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
type PushFn = (item: SSRBufferItem) => void type PushFn = (item: SSRBufferItem) => void
type Props = Record<string, unknown>
function createBuffer() { function createBuffer() {
let appendable = false let appendable = false
@ -79,26 +83,36 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
} }
export async function renderToString(app: App): Promise<string> { export async function renderToString(app: App): Promise<string> {
const resolvedBuffer = await renderComponent( const resolvedBuffer = await renderComponent(app._component, app._props, null)
createVNode(app._component, app._props)
)
return unrollBuffer(resolvedBuffer) return unrollBuffer(resolvedBuffer)
} }
export function renderComponent( 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, vnode: VNode,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> { ): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const instance = createComponentInstance(vnode, parentComponent) const instance = createComponentInstance(vnode, parentComponent)
const res = setupComponent(instance, null) const res = setupComponent(instance, null)
if (isPromise(res)) { if (isPromise(res)) {
return res.then(() => innerRenderComponent(instance)) return res.then(() => renderComponentSubTree(instance))
} else { } else {
return innerRenderComponent(instance) return renderComponentSubTree(instance)
} }
} }
function innerRenderComponent( function renderComponentSubTree(
instance: ComponentInternalInstance instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> { ): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const comp = instance.type as Component const comp = instance.type as Component
@ -141,7 +155,7 @@ export function renderVNode(
break break
case Fragment: case Fragment:
push(`<!---->`) push(`<!---->`)
renderVNodeChildren(push, children as VNodeChildren, parentComponent) renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent)
push(`<!---->`) push(`<!---->`)
break break
case Portal: case Portal:
@ -151,7 +165,7 @@ export function renderVNode(
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
renderElement(push, vnode, parentComponent) renderElement(push, vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.COMPONENT) { } else if (shapeFlag & ShapeFlags.COMPONENT) {
push(renderComponent(vnode, parentComponent)) push(renderComponentVNode(vnode, parentComponent))
} else if (shapeFlag & ShapeFlags.SUSPENSE) { } else if (shapeFlag & ShapeFlags.SUSPENSE) {
// TODO // TODO
} else { } else {
@ -166,7 +180,7 @@ export function renderVNode(
function renderVNodeChildren( function renderVNodeChildren(
push: PushFn, push: PushFn,
children: VNodeChildren, children: VNodeArrayChildren,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null
) { ) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
@ -218,13 +232,37 @@ function renderElement(
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
push(escape(children as string)) push(escape(children as string))
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
renderVNodeChildren(push, children as VNodeChildren, parentComponent) renderVNodeChildren(
push,
children as VNodeArrayChildren,
parentComponent
)
} }
} }
push(`</${tag}>`) push(`</${tag}>`)
} }
} }
export function renderSlot() { type OptimizedSlotFn = (
// TODO 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(`<!---->`)
} }