From efbbd19b3d067a3a4981a361d58574adbe972ddd Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 2 Feb 2020 00:05:27 -0500 Subject: [PATCH] wip(ssr): initial scaffold for compiler-ssr --- packages/compiler-core/src/ast.ts | 51 +++++++++- packages/compiler-core/src/codegen.ts | 83 ++++++++++++---- packages/compiler-core/src/index.ts | 5 + packages/compiler-core/src/options.ts | 4 +- packages/compiler-dom/src/index.ts | 4 +- packages/compiler-ssr/package.json | 2 +- packages/compiler-ssr/src/codegen.ts | 94 ++++++++++++++++++ packages/compiler-ssr/src/index.ts | 64 +++++++++++- packages/compiler-ssr/src/runtimeHelpers.ts | 1 + .../compiler-ssr/src/ssrCodegenTransform.ts | 99 +++++++++++++++++++ .../src/transforms/ssrTransformComponent.ts | 15 +++ .../src/transforms/ssrTransformElement.ts | 70 +++++++++++++ .../src/transforms/ssrTransformSlotOutlet.ts | 3 + .../compiler-ssr/src/transforms/ssrVBind.ts | 1 + .../compiler-ssr/src/transforms/ssrVCloak.ts | 1 + .../compiler-ssr/src/transforms/ssrVFor.ts | 3 + .../compiler-ssr/src/transforms/ssrVHtml.ts | 1 + .../compiler-ssr/src/transforms/ssrVIf.ts | 3 + .../compiler-ssr/src/transforms/ssrVModel.ts | 1 + .../compiler-ssr/src/transforms/ssrVOn.ts | 1 + .../compiler-ssr/src/transforms/ssrVShow.ts | 1 + .../compiler-ssr/src/transforms/ssrVSlot.ts | 1 + .../compiler-ssr/src/transforms/ssrVText.ts | 1 + .../__tests__/renderToString.spec.ts | 5 +- packages/server-renderer/src/index.ts | 6 +- .../server-renderer/src/renderToString.ts | 12 ++- 26 files changed, 496 insertions(+), 36 deletions(-) create mode 100644 packages/compiler-ssr/src/codegen.ts create mode 100644 packages/compiler-ssr/src/runtimeHelpers.ts create mode 100644 packages/compiler-ssr/src/ssrCodegenTransform.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrTransformComponent.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrTransformElement.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVBind.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVCloak.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVFor.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVHtml.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVIf.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVModel.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVOn.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVShow.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVSlot.ts create mode 100644 packages/compiler-ssr/src/transforms/ssrVText.ts diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index edd7918e7..058d78b12 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -45,7 +45,12 @@ export const enum NodeTypes { JS_FUNCTION_EXPRESSION, JS_SEQUENCE_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 { @@ -97,7 +102,7 @@ export interface RootNode extends Node { hoists: JSChildNode[] imports: ImportItem[] cached: number - codegenNode: TemplateChildNode | JSChildNode | undefined + codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined } export type ElementNode = @@ -130,6 +135,7 @@ export interface PlainElementNode extends BaseElementNode { | CacheExpression // when cached by v-once | SequenceExpression // when turned into a block | undefined + ssrCodegenNode?: TemplateLiteral } export interface ComponentNode extends BaseElementNode { @@ -147,7 +153,7 @@ export interface SlotOutletNode extends BaseElementNode { export interface TemplateNode extends BaseElementNode { tagType: ElementTypes.TEMPLATE - codegenNode: ElementCodegenNode | undefined | CacheExpression + // TemplateNode is a container type that always gets compiled away } export interface TextNode extends Node { @@ -232,9 +238,12 @@ export interface TextCallNode extends Node { codegenNode: CallExpression } +// JS Node Types --------------------------------------------------------------- + // 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 // Vue render function generation. + export type JSChildNode = | CallExpression | ObjectExpression @@ -252,6 +261,7 @@ export interface CallExpression extends Node { | string | symbol | JSChildNode + | SSRCodegenNode | TemplateChildNode | TemplateChildNode[])[] } @@ -275,8 +285,10 @@ export interface ArrayExpression extends Node { export interface FunctionExpression extends Node { type: NodeTypes.JS_FUNCTION_EXPRESSION params: ExpressionNode | ExpressionNode[] | undefined - returns: TemplateChildNode | TemplateChildNode[] | JSChildNode + returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode + body?: BlockStatement newline: boolean + // so that codegen knows it needs to generate ScopeId wrapper isSlot: boolean } @@ -299,6 +311,27 @@ export interface CacheExpression extends Node { 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 ---------------------------------------------------------- // createVNode(...) @@ -637,3 +670,13 @@ export function createCacheExpression( loc: locStub } } + +export function createTemplateLiteral( + elements: TemplateLiteral['elements'] +): TemplateLiteral { + return { + type: NodeTypes.JS_TEMPLATE_LITERAL, + elements, + loc: locStub + } +} diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 948b36145..adfc45878 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -18,7 +18,9 @@ import { SequenceExpression, ConditionalExpression, CacheExpression, - locStub + locStub, + SSRCodegenNode, + TemplateLiteral } from './ast' import { SourceMapGenerator, RawSourceMap } from 'source-map' import { @@ -44,7 +46,7 @@ import { } from './runtimeHelpers' import { ImportItem } from './transform' -type CodegenNode = TemplateChildNode | JSChildNode +type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode export interface CodegenResult { code: string @@ -74,7 +76,8 @@ function createCodegenContext( prefixIdentifiers = mode === 'module' || mode === 'cjs', sourceMap = false, filename = `template.vue.html`, - scopeId = null + scopeId = null, + ssr = false }: CodegenOptions ): CodegenContext { const context: CodegenContext = { @@ -83,6 +86,7 @@ function createCodegenContext( sourceMap, filename, scopeId, + ssr, source: ast.loc.source, code: ``, column: 1, @@ -169,7 +173,8 @@ export function generate( indent, deindent, newline, - scopeId + scopeId, + ssr } = context const hasHelpers = ast.helpers.length > 0 const useWithBlock = !prefixIdentifiers && mode !== 'module' @@ -231,10 +236,14 @@ export function generate( } // enter render function - if (genScopeId) { + if (genScopeId && !ssr) { push(`const render = withId(`) } - push(`function render() {`) + if (!ssr) { + push(`function render() {`) + } else { + push(`function ssrRender(_ctx, _push, _parent) {`) + } indent() if (useWithBlock) { @@ -255,7 +264,7 @@ export function generate( } newline() } - } else { + } else if (!ssr) { push(`const _ctx = this`) if (ast.cached > 0) { newline() @@ -276,7 +285,9 @@ export function generate( } // generate the VNode tree expression - push(`return `) + if (!ssr) { + push(`return `) + } if (ast.codegenNode) { genNode(ast.codegenNode, context) } else { @@ -291,7 +302,7 @@ export function generate( deindent() push(`}`) - if (genScopeId) { + if (genScopeId && !ssr) { push(`)`) } @@ -325,7 +336,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) { return } const { push, newline, helper, scopeId, mode } = context - const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module' + const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function' newline() // 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: genCacheExpression(node, context) 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 */ default: if (__DEV__) { @@ -589,10 +612,10 @@ function genFunctionExpression( context: CodegenContext ) { 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 const genScopeId = - !__BROWSER__ && isSlot && scopeId != null && mode === 'module' + !__BROWSER__ && isSlot && scopeId != null && mode !== 'function' if (genScopeId) { push(`withId(`) @@ -604,17 +627,23 @@ function genFunctionExpression( genNode(params, context) } push(`) => `) - if (newline) { + if (newline || body) { push(`{`) indent() - push(`return `) } - if (isArray(returns)) { - genNodeListAsArray(returns, context) - } else { - genNode(returns, context) + if (returns) { + if (newline) { + push(`return `) + } + if (isArray(returns)) { + genNodeListAsArray(returns, context) + } else { + genNode(returns, context) + } + } else if (body) { + genNode(body, context) } - if (newline) { + if (newline || body) { deindent() push(`}`) } @@ -686,3 +715,19 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) { } 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('`') +} diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 78a1dd3e3..435979103 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -31,6 +31,11 @@ export { registerRuntimeHelpers } from './runtimeHelpers' export { transformModel } from './transforms/vModel' 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 import { generateCodeFrame as _genCodeFrame } from '@vue/shared' const generateCodeFrame = _genCodeFrame as ( diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index c55660288..7a0c866c0 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -29,7 +29,7 @@ export interface ParserOptions { export interface TransformOptions { nodeTransforms?: NodeTransform[] - directiveTransforms?: { [name: string]: DirectiveTransform } + directiveTransforms?: { [name: string]: DirectiveTransform | undefined } isBuiltInComponent?: (tag: string) => symbol | void // Transform expressions like {{ foo }} to `_ctx.foo`. // - This is force-enabled in module mode, since modules are by default strict @@ -76,6 +76,8 @@ export interface CodegenOptions { filename?: string // SFC scoped styles ID scopeId?: string | null + // generate SSR specific code? + ssr?: boolean } export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index f7732d61b..ba9aa8727 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -18,7 +18,9 @@ import { transformOn } from './transforms/vOn' import { transformShow } from './transforms/vShow' import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers' -const parserOptions = __BROWSER__ ? parserOptionsMinimal : parserOptionsStandard +export const parserOptions = __BROWSER__ + ? parserOptionsMinimal + : parserOptionsStandard export function compile( template: string, diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index 799887e53..6d2a467cc 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -27,6 +27,6 @@ }, "homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-ssr#readme", "dependencies": { - "@vue/compiler-core": "3.0.0-alpha.4" + "@vue/compiler-dom": "3.0.0-alpha.4" } } diff --git a/packages/compiler-ssr/src/codegen.ts b/packages/compiler-ssr/src/codegen.ts new file mode 100644 index 000000000..27d599da0 --- /dev/null +++ b/packages/compiler-ssr/src/codegen.ts @@ -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(``) + } 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) +} diff --git a/packages/compiler-ssr/src/index.ts b/packages/compiler-ssr/src/index.ts index d8c2d5fd1..32307c089 100644 --- a/packages/compiler-ssr/src/index.ts +++ b/packages/compiler-ssr/src/index.ts @@ -1,3 +1,63 @@ -export function hello(): string { - return 'TODO' +import { + 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 + }) } diff --git a/packages/compiler-ssr/src/runtimeHelpers.ts b/packages/compiler-ssr/src/runtimeHelpers.ts new file mode 100644 index 000000000..8337712ea --- /dev/null +++ b/packages/compiler-ssr/src/runtimeHelpers.ts @@ -0,0 +1 @@ +// diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts new file mode 100644 index 000000000..1a3f9ff09 --- /dev/null +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -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 + +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(``) + } 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 + } + } +} diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts new file mode 100644 index 000000000..602474d90 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -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 + } + } +} diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts new file mode 100644 index 000000000..386b66a45 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts @@ -0,0 +1,70 @@ +import { + NodeTransform, + NodeTypes, + ElementTypes, + TemplateLiteral, + createTemplateLiteral +} from '@vue/compiler-dom' +import { escapeHtml } from '@vue/server-renderer/src' + +/* +## Simple Element + +``` html +
+``` +``` js +return function render(_ctx, _push, _parent) { + _push(`
`) +} +``` + +## Consecutive Elements + +``` html +
+ +
+
+``` +``` js +return function render(_ctx, _push, _parent) { + _push(`
`) +} +``` +*/ + +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) + } + } +} diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts new file mode 100644 index 000000000..4e734dbc1 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts @@ -0,0 +1,3 @@ +import { NodeTransform } from '@vue/compiler-dom' + +export const ssrTransformSlotOutlet: NodeTransform = () => {} diff --git a/packages/compiler-ssr/src/transforms/ssrVBind.ts b/packages/compiler-ssr/src/transforms/ssrVBind.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVBind.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVCloak.ts b/packages/compiler-ssr/src/transforms/ssrVCloak.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVCloak.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVFor.ts b/packages/compiler-ssr/src/transforms/ssrVFor.ts new file mode 100644 index 000000000..cd8fc937d --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVFor.ts @@ -0,0 +1,3 @@ +import { NodeTransform } from '@vue/compiler-dom' + +export const ssrTransformFor: NodeTransform = () => {} diff --git a/packages/compiler-ssr/src/transforms/ssrVHtml.ts b/packages/compiler-ssr/src/transforms/ssrVHtml.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVHtml.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVIf.ts b/packages/compiler-ssr/src/transforms/ssrVIf.ts new file mode 100644 index 000000000..b13beb713 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVIf.ts @@ -0,0 +1,3 @@ +import { NodeTransform } from '@vue/compiler-dom' + +export const ssrTransformIf: NodeTransform = () => {} diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVModel.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVOn.ts b/packages/compiler-ssr/src/transforms/ssrVOn.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVOn.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVShow.ts b/packages/compiler-ssr/src/transforms/ssrVShow.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVShow.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVSlot.ts b/packages/compiler-ssr/src/transforms/ssrVSlot.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVSlot.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/compiler-ssr/src/transforms/ssrVText.ts b/packages/compiler-ssr/src/transforms/ssrVText.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/compiler-ssr/src/transforms/ssrVText.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/server-renderer/__tests__/renderToString.spec.ts b/packages/server-renderer/__tests__/renderToString.spec.ts index 3fc9822b4..65515d9bb 100644 --- a/packages/server-renderer/__tests__/renderToString.spec.ts +++ b/packages/server-renderer/__tests__/renderToString.spec.ts @@ -146,10 +146,11 @@ describe('ssr: renderToString', () => { { msg: 'hello' }, { // optimized slot using string push - default: ({ msg }: any, push: any) => { + default: ({ msg }: any, push: any, p: any) => { push(`${msg}`) }, - _compiled: true // important to avoid slots being normalized + // important to avoid slots being normalized + _compiled: true as any }, parent ) diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index 4a4d0fbf3..1eb03e43b 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -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 { escapeHtml, interpolate } from './ssrUtils' diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 56b7dee98..f19ab09d9 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -4,7 +4,6 @@ import { ComponentInternalInstance, VNode, VNodeArrayChildren, - VNodeNormalizedChildren, createVNode, Text, Comment, @@ -12,7 +11,8 @@ import { Portal, ShapeFlags, ssrUtils, - Slot + Slot, + Slots } from 'vue' import { isString, @@ -99,7 +99,7 @@ export async function renderToString(input: App | VNode): Promise { export function renderComponent( comp: Component, props: Props | null = null, - children: VNodeNormalizedChildren | null = null, + children: Slots | SSRSlots | null = null, parentComponent: ComponentInternalInstance | null = null ): ResolvedSSRBuffer | Promise { return renderComponentVNode( @@ -256,14 +256,16 @@ function renderElement( } } -type OptimizedSlotFn = ( +export type SSRSlots = Record + +export type SSRSlot = ( props: Props, push: PushFn, parentComponent: ComponentInternalInstance | null ) => void export function renderSlot( - slotFn: Slot | OptimizedSlotFn, + slotFn: Slot | SSRSlot, slotProps: Props, push: PushFn, parentComponent: ComponentInternalInstance | null = null