]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(ssr): initial scaffold for compiler-ssr
authorEvan You <yyx990803@gmail.com>
Sun, 2 Feb 2020 05:05:27 +0000 (00:05 -0500)
committerEvan You <yyx990803@gmail.com>
Sun, 2 Feb 2020 05:05:27 +0000 (00:05 -0500)
26 files changed:
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/options.ts
packages/compiler-dom/src/index.ts
packages/compiler-ssr/package.json
packages/compiler-ssr/src/codegen.ts [new file with mode: 0644]
packages/compiler-ssr/src/index.ts
packages/compiler-ssr/src/runtimeHelpers.ts [new file with mode: 0644]
packages/compiler-ssr/src/ssrCodegenTransform.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrTransformElement.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVBind.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVCloak.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVFor.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVHtml.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVIf.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVModel.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVOn.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVShow.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVSlot.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVText.ts [new file with mode: 0644]
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/src/index.ts
packages/server-renderer/src/renderToString.ts

index edd7918e7f8dec8c9e8e55b9082fd43d87221522..058d78b126d30e0db35b70c61da7b4e73c00f6f1 100644 (file)
@@ -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
+  }
+}
index 948b361452a84a45d277ce234fa3ba2e8d8f02f8..adfc45878c59cccc952f736eeb36cd1ae955da2c 100644 (file)
@@ -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('`')
+}
index 78a1dd3e3a2f6ee6a114363b14c5d9168f1551fe..4359791030bc03bfa81ca73bc3c52ef8994de722 100644 (file)
@@ -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 (
index c556602888218e00fe90cdb8ce20aa63c5bd9038..7a0c866c0b50e7be9500292a11144bcd5b4ae7e8 100644 (file)
@@ -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
index f7732d61b4c068b32003f42a7c8a00965f5d9f4a..ba9aa8727b8c5b47b9553bad47c7f793c4b13bf9 100644 (file)
@@ -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,
index 799887e53acd0759e53770299b35c3c17bf30d3b..6d2a467cc61b3ec5d9fcdd7d53efc6e487533f58 100644 (file)
@@ -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 (file)
index 0000000..27d599d
--- /dev/null
@@ -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)
+}
index d8c2d5fd1421e289933555875c3c8d61f21ae602..32307c0896b88c9dbbb95ac8299efb9fe638a405 100644 (file)
@@ -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 (file)
index 0000000..8337712
--- /dev/null
@@ -0,0 +1 @@
+//
diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts
new file mode 100644 (file)
index 0000000..1a3f9ff
--- /dev/null
@@ -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
+    }
+  }
+}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
new file mode 100644 (file)
index 0000000..602474d
--- /dev/null
@@ -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 (file)
index 0000000..386b66a
--- /dev/null
@@ -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)
+    }
+  }
+}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
new file mode 100644 (file)
index 0000000..4e734db
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..cd8fc93
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..b13beb7
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -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 (file)
index 0000000..70b786d
--- /dev/null
@@ -0,0 +1 @@
+// TODO
index 3fc9822b4ebf6c5f4df570da40cd993922471eb9..65515d9bb8b4712443ec3c26550b9040ea75e2c9 100644 (file)
@@ -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(`<span>${msg}</span>`)
                     },
-                    _compiled: true // important to avoid slots being normalized
+                    // important to avoid slots being normalized
+                    _compiled: true as any
                   },
                   parent
                 )
index 4a4d0fbf3f2a9515a049aa923b867614ef1b9bc8..1eb03e43bf074cb7ab7eaa76759b742a5553dec9 100644 (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 { escapeHtml, interpolate } from './ssrUtils'
index 56b7dee982c2d526dd2b90a0d37d4838f2aa9be4..f19ab09d99c75c636d2ad0ab5b25211b1ec33130 100644 (file)
@@ -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<string> {
 export function renderComponent(
   comp: Component,
   props: Props | null = null,
-  children: VNodeNormalizedChildren | null = null,
+  children: Slots | SSRSlots | null = null,
   parentComponent: ComponentInternalInstance | null = null
 ): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
   return renderComponentVNode(
@@ -256,14 +256,16 @@ function renderElement(
   }
 }
 
-type OptimizedSlotFn = (
+export type SSRSlots = Record<string, SSRSlot>
+
+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