wip(ssr): initial scaffold for compiler-ssr

This commit is contained in:
Evan You 2020-02-02 00:05:27 -05:00
parent 34e61197c7
commit efbbd19b3d
26 changed files with 496 additions and 36 deletions

View File

@ -45,7 +45,12 @@ export const enum NodeTypes {
JS_FUNCTION_EXPRESSION, JS_FUNCTION_EXPRESSION,
JS_SEQUENCE_EXPRESSION, JS_SEQUENCE_EXPRESSION,
JS_CONDITIONAL_EXPRESSION, JS_CONDITIONAL_EXPRESSION,
JS_CACHE_EXPRESSION JS_CACHE_EXPRESSION,
// ssr codegen
JS_BLOCK_STATEMENT,
JS_TEMPLATE_LITERAL,
JS_IF_STATEMENT
} }
export const enum ElementTypes { export const enum ElementTypes {
@ -97,7 +102,7 @@ export interface RootNode extends Node {
hoists: JSChildNode[] hoists: JSChildNode[]
imports: ImportItem[] imports: ImportItem[]
cached: number cached: number
codegenNode: TemplateChildNode | JSChildNode | undefined codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined
} }
export type ElementNode = export type ElementNode =
@ -130,6 +135,7 @@ export interface PlainElementNode extends BaseElementNode {
| CacheExpression // when cached by v-once | CacheExpression // when cached by v-once
| SequenceExpression // when turned into a block | SequenceExpression // when turned into a block
| undefined | undefined
ssrCodegenNode?: TemplateLiteral
} }
export interface ComponentNode extends BaseElementNode { export interface ComponentNode extends BaseElementNode {
@ -147,7 +153,7 @@ export interface SlotOutletNode extends BaseElementNode {
export interface TemplateNode extends BaseElementNode { export interface TemplateNode extends BaseElementNode {
tagType: ElementTypes.TEMPLATE tagType: ElementTypes.TEMPLATE
codegenNode: ElementCodegenNode | undefined | CacheExpression // TemplateNode is a container type that always gets compiled away
} }
export interface TextNode extends Node { export interface TextNode extends Node {
@ -232,9 +238,12 @@ export interface TextCallNode extends Node {
codegenNode: CallExpression codegenNode: CallExpression
} }
// JS Node Types ---------------------------------------------------------------
// We also include a number of JavaScript AST nodes for code generation. // We also include a number of JavaScript AST nodes for code generation.
// The AST is an intentionally minimal subset just to meet the exact needs of // The AST is an intentionally minimal subset just to meet the exact needs of
// Vue render function generation. // Vue render function generation.
export type JSChildNode = export type JSChildNode =
| CallExpression | CallExpression
| ObjectExpression | ObjectExpression
@ -252,6 +261,7 @@ export interface CallExpression extends Node {
| string | string
| symbol | symbol
| JSChildNode | JSChildNode
| SSRCodegenNode
| TemplateChildNode | TemplateChildNode
| TemplateChildNode[])[] | TemplateChildNode[])[]
} }
@ -275,8 +285,10 @@ export interface ArrayExpression extends Node {
export interface FunctionExpression extends Node { export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION type: NodeTypes.JS_FUNCTION_EXPRESSION
params: ExpressionNode | ExpressionNode[] | undefined params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
body?: BlockStatement
newline: boolean newline: boolean
// so that codegen knows it needs to generate ScopeId wrapper
isSlot: boolean isSlot: boolean
} }
@ -299,6 +311,27 @@ export interface CacheExpression extends Node {
isVNode: boolean isVNode: boolean
} }
// SSR-specific Node Types -----------------------------------------------------
export type SSRCodegenNode = BlockStatement | TemplateLiteral | IfStatement
export interface BlockStatement extends Node {
type: NodeTypes.JS_BLOCK_STATEMENT
body: (JSChildNode | IfStatement)[]
}
export interface TemplateLiteral extends Node {
type: NodeTypes.JS_TEMPLATE_LITERAL
elements: (string | JSChildNode)[]
}
export interface IfStatement extends Node {
type: NodeTypes.JS_IF_STATEMENT
test: ExpressionNode
consequent: BlockStatement
alternate: IfStatement | BlockStatement
}
// Codegen Node Types ---------------------------------------------------------- // Codegen Node Types ----------------------------------------------------------
// createVNode(...) // createVNode(...)
@ -637,3 +670,13 @@ export function createCacheExpression(
loc: locStub loc: locStub
} }
} }
export function createTemplateLiteral(
elements: TemplateLiteral['elements']
): TemplateLiteral {
return {
type: NodeTypes.JS_TEMPLATE_LITERAL,
elements,
loc: locStub
}
}

View File

@ -18,7 +18,9 @@ import {
SequenceExpression, SequenceExpression,
ConditionalExpression, ConditionalExpression,
CacheExpression, CacheExpression,
locStub locStub,
SSRCodegenNode,
TemplateLiteral
} from './ast' } from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map' import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { import {
@ -44,7 +46,7 @@ import {
} from './runtimeHelpers' } from './runtimeHelpers'
import { ImportItem } from './transform' import { ImportItem } from './transform'
type CodegenNode = TemplateChildNode | JSChildNode type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
export interface CodegenResult { export interface CodegenResult {
code: string code: string
@ -74,7 +76,8 @@ function createCodegenContext(
prefixIdentifiers = mode === 'module' || mode === 'cjs', prefixIdentifiers = mode === 'module' || mode === 'cjs',
sourceMap = false, sourceMap = false,
filename = `template.vue.html`, filename = `template.vue.html`,
scopeId = null scopeId = null,
ssr = false
}: CodegenOptions }: CodegenOptions
): CodegenContext { ): CodegenContext {
const context: CodegenContext = { const context: CodegenContext = {
@ -83,6 +86,7 @@ function createCodegenContext(
sourceMap, sourceMap,
filename, filename,
scopeId, scopeId,
ssr,
source: ast.loc.source, source: ast.loc.source,
code: ``, code: ``,
column: 1, column: 1,
@ -169,7 +173,8 @@ export function generate(
indent, indent,
deindent, deindent,
newline, newline,
scopeId scopeId,
ssr
} = context } = context
const hasHelpers = ast.helpers.length > 0 const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module' const useWithBlock = !prefixIdentifiers && mode !== 'module'
@ -231,10 +236,14 @@ export function generate(
} }
// enter render function // enter render function
if (genScopeId) { if (genScopeId && !ssr) {
push(`const render = withId(`) push(`const render = withId(`)
} }
push(`function render() {`) if (!ssr) {
push(`function render() {`)
} else {
push(`function ssrRender(_ctx, _push, _parent) {`)
}
indent() indent()
if (useWithBlock) { if (useWithBlock) {
@ -255,7 +264,7 @@ export function generate(
} }
newline() newline()
} }
} else { } else if (!ssr) {
push(`const _ctx = this`) push(`const _ctx = this`)
if (ast.cached > 0) { if (ast.cached > 0) {
newline() newline()
@ -276,7 +285,9 @@ export function generate(
} }
// generate the VNode tree expression // generate the VNode tree expression
push(`return `) if (!ssr) {
push(`return `)
}
if (ast.codegenNode) { if (ast.codegenNode) {
genNode(ast.codegenNode, context) genNode(ast.codegenNode, context)
} else { } else {
@ -291,7 +302,7 @@ export function generate(
deindent() deindent()
push(`}`) push(`}`)
if (genScopeId) { if (genScopeId && !ssr) {
push(`)`) push(`)`)
} }
@ -325,7 +336,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
return return
} }
const { push, newline, helper, scopeId, mode } = context const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module' const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
newline() newline()
// push scope Id before initilaizing hoisted vnodes so that these vnodes // push scope Id before initilaizing hoisted vnodes so that these vnodes
@ -469,6 +480,18 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.JS_CACHE_EXPRESSION: case NodeTypes.JS_CACHE_EXPRESSION:
genCacheExpression(node, context) genCacheExpression(node, context)
break break
// SSR only types
case NodeTypes.JS_BLOCK_STATEMENT:
!__BROWSER__ && genNodeList(node.body, context, true)
break
case NodeTypes.JS_TEMPLATE_LITERAL:
!__BROWSER__ && genTemplateLiteral(node, context)
break
case NodeTypes.JS_IF_STATEMENT:
// TODO
break
/* istanbul ignore next */ /* istanbul ignore next */
default: default:
if (__DEV__) { if (__DEV__) {
@ -589,10 +612,10 @@ function genFunctionExpression(
context: CodegenContext context: CodegenContext
) { ) {
const { push, indent, deindent, scopeId, mode } = context const { push, indent, deindent, scopeId, mode } = context
const { params, returns, newline, isSlot } = node const { params, returns, body, newline, isSlot } = node
// slot functions also need to push scopeId before rendering its content // slot functions also need to push scopeId before rendering its content
const genScopeId = const genScopeId =
!__BROWSER__ && isSlot && scopeId != null && mode === 'module' !__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
if (genScopeId) { if (genScopeId) {
push(`withId(`) push(`withId(`)
@ -604,17 +627,23 @@ function genFunctionExpression(
genNode(params, context) genNode(params, context)
} }
push(`) => `) push(`) => `)
if (newline) { if (newline || body) {
push(`{`) push(`{`)
indent() indent()
push(`return `)
} }
if (isArray(returns)) { if (returns) {
genNodeListAsArray(returns, context) if (newline) {
} else { push(`return `)
genNode(returns, context) }
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
} else if (body) {
genNode(body, context)
} }
if (newline) { if (newline || body) {
deindent() deindent()
push(`}`) push(`}`)
} }
@ -686,3 +715,19 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
} }
push(`)`) push(`)`)
} }
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
const { push } = context
push('`')
for (let i = 0; i < node.elements.length; i++) {
const e = node.elements[i]
if (isString(e)) {
push(e.replace(/`/g, '\\`'))
} else {
push('${')
genNode(e, context)
push('}')
}
}
push('`')
}

View File

@ -31,6 +31,11 @@ export { registerRuntimeHelpers } from './runtimeHelpers'
export { transformModel } from './transforms/vModel' export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn' export { transformOn } from './transforms/vOn'
// exported for compiler-ssr
export { transformExpression } from './transforms/transformExpression'
export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
export { buildProps } from './transforms/transformElement'
// utility, but need to rewrite typing to avoid dts relying on @vue/shared // utility, but need to rewrite typing to avoid dts relying on @vue/shared
import { generateCodeFrame as _genCodeFrame } from '@vue/shared' import { generateCodeFrame as _genCodeFrame } from '@vue/shared'
const generateCodeFrame = _genCodeFrame as ( const generateCodeFrame = _genCodeFrame as (

View File

@ -29,7 +29,7 @@ export interface ParserOptions {
export interface TransformOptions { export interface TransformOptions {
nodeTransforms?: NodeTransform[] nodeTransforms?: NodeTransform[]
directiveTransforms?: { [name: string]: DirectiveTransform } directiveTransforms?: { [name: string]: DirectiveTransform | undefined }
isBuiltInComponent?: (tag: string) => symbol | void isBuiltInComponent?: (tag: string) => symbol | void
// Transform expressions like {{ foo }} to `_ctx.foo`. // Transform expressions like {{ foo }} to `_ctx.foo`.
// - This is force-enabled in module mode, since modules are by default strict // - This is force-enabled in module mode, since modules are by default strict
@ -76,6 +76,8 @@ export interface CodegenOptions {
filename?: string filename?: string
// SFC scoped styles ID // SFC scoped styles ID
scopeId?: string | null scopeId?: string | null
// generate SSR specific code?
ssr?: boolean
} }
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions

View File

@ -18,7 +18,9 @@ import { transformOn } from './transforms/vOn'
import { transformShow } from './transforms/vShow' import { transformShow } from './transforms/vShow'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers' import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
const parserOptions = __BROWSER__ ? parserOptionsMinimal : parserOptionsStandard export const parserOptions = __BROWSER__
? parserOptionsMinimal
: parserOptionsStandard
export function compile( export function compile(
template: string, template: string,

View File

@ -27,6 +27,6 @@
}, },
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-ssr#readme", "homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-ssr#readme",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.0.0-alpha.4" "@vue/compiler-dom": "3.0.0-alpha.4"
} }
} }

View File

@ -0,0 +1,94 @@
import {
CodegenResult,
RootNode,
generate as baseGenerate,
CodegenOptions,
NodeTypes,
locStub,
BlockStatement,
ElementTypes,
createCallExpression,
TemplateLiteral,
createTemplateLiteral,
CallExpression,
TemplateChildNode
} from '@vue/compiler-dom'
import { isString } from '@vue/shared'
export function generate(
ast: RootNode,
options: CodegenOptions
): CodegenResult {
// construct a SSR-specific codegen tree to pass to core codegen
const body: BlockStatement['body'] = []
let currentCall: CallExpression | null = null
let currentString: TemplateLiteral | null = null
function ensureCurrentString() {
if (!currentCall) {
currentCall = createCallExpression(`_push`)
body.push(currentCall)
}
if (!currentString) {
currentString = createTemplateLiteral([])
currentCall.arguments.push(currentString)
}
return currentString.elements
}
function pushStringPart(part: TemplateLiteral['elements'][0]) {
const bufferedElements = ensureCurrentString()
const lastItem = bufferedElements[bufferedElements.length - 1]
if (isString(part) && isString(lastItem)) {
bufferedElements[bufferedElements.length - 1] += part
} else {
bufferedElements.push(part)
}
}
function processChildren(children: TemplateChildNode[]) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.type === NodeTypes.ELEMENT) {
if (child.tagType === ElementTypes.ELEMENT) {
const elementsToAdd = child.ssrCodegenNode!.elements
for (let j = 0; j < elementsToAdd.length; j++) {
pushStringPart(elementsToAdd[j])
}
if (child.children.length) {
processChildren(child.children)
}
// push closing tag
pushStringPart(`</${child.tag}>`)
} else if (child.tagType === ElementTypes.COMPONENT) {
// TODO
} else if (child.tagType === ElementTypes.SLOT) {
// TODO
}
} else if (child.type === NodeTypes.TEXT) {
// TODO
} else if (child.type === NodeTypes.IF) {
// TODO
} else if (child.type === NodeTypes.FOR) {
// TODO
}
}
}
const isFragment = ast.children.length > 1
if (isFragment) {
pushStringPart(`<!---->`)
}
processChildren(ast.children)
if (isFragment) {
pushStringPart(`<!---->`)
}
ast.codegenNode = {
type: NodeTypes.JS_BLOCK_STATEMENT,
loc: locStub,
body
}
return baseGenerate(ast, options)
}

View File

@ -1,3 +1,63 @@
export function hello(): string { import {
return 'TODO' CodegenResult,
baseParse,
parserOptions,
transform,
generate,
CompilerOptions,
transformExpression,
trackVForSlotScopes,
trackSlotScopes
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformIf } from './transforms/ssrVIf'
import { ssrTransformFor } from './transforms/ssrVFor'
import { ssrTransformElement } from './transforms/ssrTransformElement'
import { ssrTransformComponent } from './transforms/ssrTransformComponent'
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
export interface SSRCompilerOptions extends CompilerOptions {}
export function compile(
template: string,
options: SSRCompilerOptions = {}
): CodegenResult {
const ast = baseParse(template, {
...parserOptions,
...options
})
transform(ast, {
...options,
prefixIdentifiers: true,
// disalbe optimizations that are unnecessary for ssr
cacheHandlers: false,
hoistStatic: false,
nodeTransforms: [
ssrTransformIf,
ssrTransformFor,
trackVForSlotScopes,
transformExpression,
ssrTransformSlotOutlet,
ssrTransformElement,
ssrTransformComponent,
trackSlotScopes,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: {
// TODO server-side directive transforms
...(options.directiveTransforms || {}) // user transforms
}
})
// traverse the template AST and convert into SSR codegen AST
// by replacing ast.codegenNode.
ssrCodegenTransform(ast)
return generate(ast, {
mode: 'cjs',
...options,
ssr: true,
prefixIdentifiers: true
})
} }

View File

@ -0,0 +1 @@
//

View File

@ -0,0 +1,99 @@
import {
RootNode,
BlockStatement,
CallExpression,
TemplateLiteral,
createCallExpression,
createTemplateLiteral,
locStub,
NodeTypes,
TemplateChildNode,
ElementTypes
} from '@vue/compiler-dom'
import { isString } from '@vue/shared'
// Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal
// instead of each getting a corresponding call), we need to apply an extra
// transform pass to convert the template AST into a fresh JS AST before
// passing it to codegen.
export function ssrCodegenTransform(ast: RootNode) {
const context = createSSRTransformContext()
const isFragment = ast.children.length > 1
if (isFragment) {
context.pushStringPart(`<!---->`)
}
processChildren(ast.children, context)
if (isFragment) {
context.pushStringPart(`<!---->`)
}
ast.codegenNode = {
type: NodeTypes.JS_BLOCK_STATEMENT,
loc: locStub,
body: context.body
}
}
type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
function createSSRTransformContext() {
const body: BlockStatement['body'] = []
let currentCall: CallExpression | null = null
let currentString: TemplateLiteral | null = null
return {
body,
pushStringPart(part: TemplateLiteral['elements'][0]) {
if (!currentCall) {
currentCall = createCallExpression(`_push`)
body.push(currentCall)
}
if (!currentString) {
currentString = createTemplateLiteral([])
currentCall.arguments.push(currentString)
}
const bufferedElements = currentString.elements
const lastItem = bufferedElements[bufferedElements.length - 1]
if (isString(part) && isString(lastItem)) {
bufferedElements[bufferedElements.length - 1] += part
} else {
bufferedElements.push(part)
}
}
}
}
function processChildren(
children: TemplateChildNode[],
context: SSRTransformContext
) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.type === NodeTypes.ELEMENT) {
if (child.tagType === ElementTypes.ELEMENT) {
const elementsToAdd = child.ssrCodegenNode!.elements
for (let j = 0; j < elementsToAdd.length; j++) {
context.pushStringPart(elementsToAdd[j])
}
if (child.children.length) {
processChildren(child.children, context)
}
// push closing tag
context.pushStringPart(`</${child.tag}>`)
} else if (child.tagType === ElementTypes.COMPONENT) {
// TODO
} else if (child.tagType === ElementTypes.SLOT) {
// TODO
}
} else if (child.type === NodeTypes.TEXT) {
// TODO
} else if (child.type === NodeTypes.IF) {
// TODO
} else if (child.type === NodeTypes.FOR) {
// TODO
}
}
}

View File

@ -0,0 +1,15 @@
import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-dom'
export const ssrTransformComponent: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.COMPONENT
) {
return function ssrPostTransformComponent() {
// generate a _push(_renderComponent) call
// dynamic component as well
// !check if we need to bail out for slots
// TODO also handle scopeID here
}
}
}

View File

@ -0,0 +1,70 @@
import {
NodeTransform,
NodeTypes,
ElementTypes,
TemplateLiteral,
createTemplateLiteral
} from '@vue/compiler-dom'
import { escapeHtml } from '@vue/server-renderer/src'
/*
## Simple Element
``` html
<div></div>
```
``` js
return function render(_ctx, _push, _parent) {
_push(`<div></div>`)
}
```
## Consecutive Elements
``` html
<div>
<span></span>
</div>
<div></div>
```
``` js
return function render(_ctx, _push, _parent) {
_push(`<div><span></span></div><div></div>`)
}
```
*/
export const ssrTransformElement: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT
) {
return function ssrPostTransformElement() {
// element
// generate the template literal representing the open tag.
const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
for (let i = 0; i < node.props.length; i++) {
const prop = node.props[i]
if (prop.type === NodeTypes.DIRECTIVE) {
const directiveTransform = context.directiveTransforms[prop.name]
if (directiveTransform) {
// TODO directive transforms
} else {
// no corresponding ssr directive transform found.
// TODO emit error
}
} else {
// static prop
openTag.push(
` ${prop.name}` +
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
)
}
}
openTag.push(`>`)
node.ssrCodegenNode = createTemplateLiteral(openTag)
}
}
}

View File

@ -0,0 +1,3 @@
import { NodeTransform } from '@vue/compiler-dom'
export const ssrTransformSlotOutlet: NodeTransform = () => {}

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1,3 @@
import { NodeTransform } from '@vue/compiler-dom'
export const ssrTransformFor: NodeTransform = () => {}

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1,3 @@
import { NodeTransform } from '@vue/compiler-dom'
export const ssrTransformIf: NodeTransform = () => {}

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1 @@
// TODO

View File

@ -0,0 +1 @@
// TODO

View File

@ -146,10 +146,11 @@ describe('ssr: renderToString', () => {
{ msg: 'hello' }, { msg: 'hello' },
{ {
// optimized slot using string push // optimized slot using string push
default: ({ msg }: any, push: any) => { default: ({ msg }: any, push: any, p: any) => {
push(`<span>${msg}</span>`) push(`<span>${msg}</span>`)
}, },
_compiled: true // important to avoid slots being normalized // important to avoid slots being normalized
_compiled: true as any
}, },
parent parent
) )

View File

@ -1,3 +1,7 @@
export { renderToString, renderComponent, renderSlot } from './renderToString' // public
export { renderToString } from './renderToString'
// internal
export { renderComponent, renderSlot } from './renderToString'
export { renderClass, renderStyle, renderProps } from './renderProps' export { renderClass, renderStyle, renderProps } from './renderProps'
export { escapeHtml, interpolate } from './ssrUtils' export { escapeHtml, interpolate } from './ssrUtils'

View File

@ -4,7 +4,6 @@ import {
ComponentInternalInstance, ComponentInternalInstance,
VNode, VNode,
VNodeArrayChildren, VNodeArrayChildren,
VNodeNormalizedChildren,
createVNode, createVNode,
Text, Text,
Comment, Comment,
@ -12,7 +11,8 @@ import {
Portal, Portal,
ShapeFlags, ShapeFlags,
ssrUtils, ssrUtils,
Slot Slot,
Slots
} from 'vue' } from 'vue'
import { import {
isString, isString,
@ -99,7 +99,7 @@ export async function renderToString(input: App | VNode): Promise<string> {
export function renderComponent( export function renderComponent(
comp: Component, comp: Component,
props: Props | null = null, props: Props | null = null,
children: VNodeNormalizedChildren | null = null, children: Slots | SSRSlots | null = null,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> { ): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
return renderComponentVNode( return renderComponentVNode(
@ -256,14 +256,16 @@ function renderElement(
} }
} }
type OptimizedSlotFn = ( export type SSRSlots = Record<string, SSRSlot>
export type SSRSlot = (
props: Props, props: Props,
push: PushFn, push: PushFn,
parentComponent: ComponentInternalInstance | null parentComponent: ComponentInternalInstance | null
) => void ) => void
export function renderSlot( export function renderSlot(
slotFn: Slot | OptimizedSlotFn, slotFn: Slot | SSRSlot,
slotProps: Props, slotProps: Props,
push: PushFn, push: PushFn,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null