]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler-ssr): v-if
authorEvan You <yyx990803@gmail.com>
Mon, 3 Feb 2020 20:51:41 +0000 (15:51 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 3 Feb 2020 23:31:10 +0000 (18:31 -0500)
14 files changed:
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/__tests__/ssrText.spec.ts
packages/compiler-ssr/__tests__/ssrVIf.spec.ts [new file with mode: 0644]
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts

index a5b0cc9b0fc42f94dfc78fd926f97e41db5f2acc..d28911bd65294bc10903a7693b5c7c5a6f607744 100644 (file)
@@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = `
 
 return function render() {
   with (this) {
-    const { toDisplayString: _toDisplayString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
+    const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", {
       id: \\"foo\\",
@@ -26,7 +26,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
-"const { toDisplayString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
+"const { toDisplayString, createVNode, openBlock, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
 
 return function render() {
   const _ctx = this
@@ -48,7 +48,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests module mode 1`] = `
-"import { toDisplayString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
+"import { toDisplayString, createVNode, openBlock, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
 
 export function render() {
   const _ctx = this
index 10db0e920132e53a811aebf9e133affad5151a64..9e4a7c793dc37cfde1138e733c990b9e01ace8df 100644 (file)
@@ -380,7 +380,7 @@ const _hoisted_2 = _createVNode(\\"span\\")
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       (_openBlock(), ok
index edf9326f638fd43377c7d0549398ca103a4052a0..8baed87fda056216917f82998e1cea62f3756e46 100644 (file)
@@ -155,7 +155,7 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, renderList: _renderList, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
+    const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
index d647b0734535dbbd503c064b14fc9dff580f75cd..3ed8fc915e3deda9e37b16d2650d9e126a41d606 100644 (file)
@@ -5,7 +5,7 @@ exports[`compiler: v-if codegen basic v-if 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(\\"div\\", { key: 0 })
@@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(_Fragment, { key: 0 }, [
@@ -37,7 +37,7 @@ exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
+    const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _renderSlot($slots, \\"default\\", { key: 0 })
@@ -51,7 +51,7 @@ exports[`compiler: v-if codegen v-if + v-else 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(\\"div\\", { key: 0 })
@@ -65,7 +65,7 @@ exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(\\"div\\", { key: 0 })
@@ -81,7 +81,7 @@ exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(\\"div\\", { key: 0 })
@@ -97,7 +97,7 @@ exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
+    const { renderSlot: _renderSlot, openBlock: _openBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _renderSlot($slots, \\"default\\", { key: 0 })
@@ -111,7 +111,7 @@ exports[`compiler: v-if codegen v-if with key 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
+    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(\\"div\\", { key: \\"some-key\\" })
index b6c2e86a28cd640a86f1b9b1588f23d8ef5960e7..9183b9d9e69d503a9b01a4f699eb7337b7ae2660 100644 (file)
@@ -12,7 +12,8 @@ import {
   SimpleExpressionNode,
   SequenceExpression,
   ConditionalExpression,
-  CallExpression
+  CallExpression,
+  IfCodegenNode
 } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
 import { CompilerOptions, generate } from '../../src'
@@ -43,7 +44,7 @@ function parseWithIfTransform(
   }
   return {
     root: ast,
-    node: ast.children[returnIndex] as IfNode
+    node: ast.children[returnIndex] as IfNode & { codegenNode: IfCodegenNode }
   }
 }
 
index 73efd832400b5cc74a563b8dd703129c240d65b2..d686c1aabf64659b5a46363c4eb0754f0776d1d9 100644 (file)
@@ -102,7 +102,7 @@ export interface RootNode extends Node {
   hoists: JSChildNode[]
   imports: ImportItem[]
   cached: number
-  codegenNode: TemplateChildNode | JSChildNode | BlockStatement | undefined
+  codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
 }
 
 export type ElementNode =
@@ -213,7 +213,7 @@ export interface CompoundExpressionNode extends Node {
 export interface IfNode extends Node {
   type: NodeTypes.IF
   branches: IfBranchNode[]
-  codegenNode: IfCodegenNode
+  codegenNode?: IfCodegenNode
 }
 
 export interface IfBranchNode extends Node {
@@ -229,7 +229,7 @@ export interface ForNode extends Node {
   keyAlias: ExpressionNode | undefined
   objectIndexAlias: ExpressionNode | undefined
   children: TemplateChildNode[]
-  codegenNode: ForCodegenNode
+  codegenNode?: ForCodegenNode
 }
 
 export interface TextCallNode extends Node {
index 4359791030bc03bfa81ca73bc3c52ef8994de722..b87cfe421f44cc04d93c183a7b0b90c250711bb3 100644 (file)
@@ -32,7 +32,11 @@ export { transformModel } from './transforms/vModel'
 export { transformOn } from './transforms/vOn'
 
 // exported for compiler-ssr
-export { transformExpression } from './transforms/transformExpression'
+export { processIfBranches } from './transforms/vIf'
+export {
+  transformExpression,
+  processExpression
+} from './transforms/transformExpression'
 export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
 export { buildProps } from './transforms/transformElement'
 
index 3dcc345e4f41fa2e139dd26d768615b7421c6b88..53e197eb04621431d008a5c1dce95a5af0b370b9 100644 (file)
@@ -23,7 +23,8 @@ import {
   BlockCodegenNode,
   SlotOutletCodegenNode,
   ElementCodegenNode,
-  ComponentCodegenNode
+  ComponentCodegenNode,
+  IfNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
@@ -40,73 +41,18 @@ import { injectProp } from '../utils'
 export const transformIf = createStructuralDirectiveTransform(
   /^(if|else|else-if)$/,
   (node, dir, context) => {
-    if (
-      dir.name !== 'else' &&
-      (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
-    ) {
-      const loc = dir.exp ? dir.exp.loc : node.loc
-      context.onError(
-        createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
-      )
-      dir.exp = createSimpleExpression(`true`, false, loc)
-    }
-
-    if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
-      // dir.exp can only be simple expression because vIf transform is applied
-      // before expression transform.
-      dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
-    }
-
-    if (dir.name === 'if') {
-      const branch = createIfBranch(node, dir)
-      const codegenNode = createSequenceExpression([
-        createCallExpression(context.helper(OPEN_BLOCK))
-      ]) as IfCodegenNode
-
-      context.replaceNode({
-        type: NodeTypes.IF,
-        loc: node.loc,
-        branches: [branch],
-        codegenNode
-      })
-
+    return processIfBranches(node, dir, context, (ifNode, branch, isRoot) => {
       // Exit callback. Complete the codegenNode when all children have been
       // transformed.
       return () => {
-        codegenNode.expressions.push(createCodegenNodeForBranch(
-          branch,
-          0,
-          context
-        ) as IfConditionalExpression)
-      }
-    } else {
-      // locate the adjacent v-if
-      const siblings = context.parent!.children
-      const comments = []
-      let i = siblings.indexOf(node)
-      while (i-- >= -1) {
-        const sibling = siblings[i]
-        if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
-          context.removeNode(sibling)
-          comments.unshift(sibling)
-          continue
-        }
-        if (sibling && sibling.type === NodeTypes.IF) {
-          // move the node to the if node's branches
-          context.removeNode()
-          const branch = createIfBranch(node, dir)
-          if (__DEV__ && comments.length) {
-            branch.children = [...comments, ...branch.children]
-          }
-          sibling.branches.push(branch)
-          // since the branch was removed, it will not be traversed.
-          // make sure to traverse here.
-          traverseChildren(branch, context)
-          // make sure to reset currentNode after traversal to indicate this
-          // node has been removed.
-          context.currentNode = null
+        if (isRoot) {
+          ifNode.codegenNode = createSequenceExpression([
+            createCallExpression(context.helper(OPEN_BLOCK)),
+            createCodegenNodeForBranch(branch, 0, context)
+          ]) as IfCodegenNode
+        } else {
           // attach this branch's codegen node to the v-if root.
-          let parentCondition = sibling.codegenNode
+          let parentCondition = ifNode.codegenNode!
             .expressions[1] as ConditionalExpression
           while (
             parentCondition.alternate.type ===
@@ -116,20 +62,92 @@ export const transformIf = createStructuralDirectiveTransform(
           }
           parentCondition.alternate = createCodegenNodeForBranch(
             branch,
-            sibling.branches.length - 1,
+            ifNode.branches.length - 1,
             context
           )
-        } else {
-          context.onError(
-            createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
-          )
         }
-        break
       }
-    }
+    })
   }
 )
 
+export const processIfBranches = (
+  node: ElementNode,
+  dir: DirectiveNode,
+  context: TransformContext,
+  processCodegen?: (
+    node: IfNode,
+    branch: IfBranchNode,
+    isRoot: boolean
+  ) => (() => void) | void
+) => {
+  if (
+    dir.name !== 'else' &&
+    (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
+  ) {
+    const loc = dir.exp ? dir.exp.loc : node.loc
+    context.onError(
+      createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
+    )
+    dir.exp = createSimpleExpression(`true`, false, loc)
+  }
+
+  if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
+    // dir.exp can only be simple expression because vIf transform is applied
+    // before expression transform.
+    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
+  }
+
+  if (dir.name === 'if') {
+    const branch = createIfBranch(node, dir)
+    const ifNode: IfNode = {
+      type: NodeTypes.IF,
+      loc: node.loc,
+      branches: [branch]
+    }
+    context.replaceNode(ifNode)
+    if (processCodegen) {
+      return processCodegen(ifNode, branch, true)
+    }
+  } else {
+    // locate the adjacent v-if
+    const siblings = context.parent!.children
+    const comments = []
+    let i = siblings.indexOf(node)
+    while (i-- >= -1) {
+      const sibling = siblings[i]
+      if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
+        context.removeNode(sibling)
+        comments.unshift(sibling)
+        continue
+      }
+      if (sibling && sibling.type === NodeTypes.IF) {
+        // move the node to the if node's branches
+        context.removeNode()
+        const branch = createIfBranch(node, dir)
+        if (__DEV__ && comments.length) {
+          branch.children = [...comments, ...branch.children]
+        }
+        sibling.branches.push(branch)
+        const onExit = processCodegen && processCodegen(sibling, branch, false)
+        // since the branch was removed, it will not be traversed.
+        // make sure to traverse here.
+        traverseChildren(branch, context)
+        // call on exit
+        if (onExit) onExit()
+        // make sure to reset currentNode after traversal to indicate this
+        // node has been removed.
+        context.currentNode = null
+      } else {
+        context.onError(
+          createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
+        )
+      }
+      break
+    }
+  }
+}
+
 function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
   return {
     type: NodeTypes.IF_BRANCH,
@@ -171,25 +189,25 @@ function createChildrenCodegenNode(
     createSimpleExpression(index + '', false)
   )
   const { children } = branch
-  const child = children[0]
+  const firstChild = children[0]
   const needFragmentWrapper =
-    children.length !== 1 || child.type !== NodeTypes.ELEMENT
+    children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
   if (needFragmentWrapper) {
     const blockArgs: CallExpression['arguments'] = [
       helper(FRAGMENT),
       createObjectExpression([keyProperty]),
       children
     ]
-    if (children.length === 1 && child.type === NodeTypes.FOR) {
+    if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
       // optimize away nested fragments when child is a ForNode
-      const forBlockArgs = child.codegenNode.expressions[1].arguments
+      const forBlockArgs = firstChild.codegenNode!.expressions[1].arguments
       // directly use the for block's children and patchFlag
       blockArgs[2] = forBlockArgs[2]
       blockArgs[3] = forBlockArgs[3]
     }
     return createCallExpression(helper(CREATE_BLOCK), blockArgs)
   } else {
-    const childCodegen = (child as ElementNode).codegenNode as
+    const childCodegen = (firstChild as ElementNode).codegenNode as
       | ElementCodegenNode
       | ComponentCodegenNode
       | SlotOutletCodegenNode
index c4c9add5e24eaa66165233f09d77403701e781b2..cf9ca3138d82fc4b6562ba0d3c70aafd64ccd3fa 100644 (file)
@@ -1,6 +1,6 @@
 import { getCompiledString } from './utils'
 
-describe('element', () => {
+describe('ssr: element', () => {
   test('basic elements', () => {
     expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot(
       `"\`<div></div>\`"`
index 9f0bb22472e93fc839589b6b5ef8fcaef0ec96d8..35ed493405fd4ccad8607893657d89e95c5808e2 100644 (file)
@@ -1,6 +1,6 @@
 import { getCompiledString } from './utils'
 
-describe('text', () => {
+describe('ssr: text', () => {
   test('static text', () => {
     expect(getCompiledString(`foo`)).toMatchInlineSnapshot(`"\`foo\`"`)
   })
diff --git a/packages/compiler-ssr/__tests__/ssrVIf.spec.ts b/packages/compiler-ssr/__tests__/ssrVIf.spec.ts
new file mode 100644 (file)
index 0000000..a5a80d1
--- /dev/null
@@ -0,0 +1,141 @@
+import { compile } from '../src'
+
+describe('ssr: v-if', () => {
+  test('basic', () => {
+    expect(compile(`<div v-if="foo"></div>`).code).toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div></div>\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('with nested content', () => {
+    expect(compile(`<div v-if="foo">hello<span>ok</span></div>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div>hello<span>ok</span></div>\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('v-if + v-else', () => {
+    expect(compile(`<div v-if="foo"/><span v-else/>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div></div>\`)
+        } else {
+          _push(\`<span></span>\`)
+        }
+      }"
+    `)
+  })
+
+  test('v-if + v-else-if', () => {
+    expect(compile(`<div v-if="foo"/><span v-else-if="bar"/>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div></div>\`)
+        } else if (_ctx.bar) {
+          _push(\`<span></span>\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('v-if + v-else-if + v-else', () => {
+    expect(compile(`<div v-if="foo"/><span v-else-if="bar"/><p v-else/>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div></div>\`)
+        } else if (_ctx.bar) {
+          _push(\`<span></span>\`)
+        } else {
+          _push(\`<p></p>\`)
+        }
+      }"
+    `)
+  })
+
+  test('<template v-if> (text)', () => {
+    expect(compile(`<template v-if="foo">hello</template>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<!---->hello<!---->\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('<template v-if> (single element)', () => {
+    // single element should not wrap with fragment
+    expect(compile(`<template v-if="foo"><div>hi</div></template>`).code)
+      .toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<div>hi</div>\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('<template v-if> (multiple element)', () => {
+    expect(
+      compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code
+    ).toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<!----><div>hi</div><div>ho</div><!---->\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
+  })
+
+  test('<template v-if> (with v-for inside)', () => {
+    // TODO should not contain nested fragments
+  })
+
+  test('<template v-if> + normal v-else', () => {
+    expect(
+      compile(
+        `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`
+      ).code
+    ).toMatchInlineSnapshot(`
+      "
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<!----><div>hi</div><div>ho</div><!---->\`)
+        } else {
+          _push(\`<div></div>\`)
+        }
+      }"
+    `)
+  })
+})
index 9eec2c6aebd5870c9648418304bcce376fb5ece3..32366a543f1ab4078ee2599664610ef9c1437116 100644 (file)
@@ -1,7 +1,6 @@
 import {
   RootNode,
   BlockStatement,
-  CallExpression,
   TemplateLiteral,
   createCallExpression,
   createTemplateLiteral,
@@ -10,10 +9,13 @@ import {
   ElementTypes,
   createBlockStatement,
   CompilerOptions,
-  isText
+  isText,
+  IfStatement,
+  CallExpression
 } from '@vue/compiler-dom'
 import { isString, escapeHtml, NO } from '@vue/shared'
 import { INTERPOLATE } from './runtimeHelpers'
+import { processIf } from './transforms/ssrVIf'
 
 // Because SSR codegen output is completely different from client-side output
 // (e.g. multiple elements can be concatenated into a single template literal
@@ -37,22 +39,19 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
   ast.codegenNode = createBlockStatement(context.body)
 }
 
-type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
+export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
 
-function createSSRTransformContext(options: CompilerOptions) {
+export function createSSRTransformContext(options: CompilerOptions) {
   const body: BlockStatement['body'] = []
-  let currentCall: CallExpression | null = null
   let currentString: TemplateLiteral | null = null
 
   return {
     options,
     body,
     pushStringPart(part: TemplateLiteral['elements'][0]) {
-      if (!currentCall) {
-        currentCall = createCallExpression(`_push`)
-        body.push(currentCall)
-      }
       if (!currentString) {
+        const currentCall = createCallExpression(`_push`)
+        body.push(currentCall)
         currentString = createTemplateLiteral([])
         currentCall.arguments.push(currentString)
       }
@@ -63,11 +62,16 @@ function createSSRTransformContext(options: CompilerOptions) {
       } else {
         bufferedElements.push(part)
       }
+    },
+    pushStatement(statement: IfStatement | CallExpression) {
+      // close current string
+      currentString = null
+      body.push(statement)
     }
   }
 }
 
-function processChildren(
+export function processChildren(
   children: TemplateChildNode[],
   context: SSRTransformContext
 ) {
@@ -98,7 +102,7 @@ function processChildren(
     } else if (child.type === NodeTypes.INTERPOLATION) {
       context.pushStringPart(createCallExpression(INTERPOLATE, [child.content]))
     } else if (child.type === NodeTypes.IF) {
-      // TODO
+      processIf(child, context)
     } else if (child.type === NodeTypes.FOR) {
       // TODO
     }
index bb3e0200a43d4502501e905c590ed1aa3829ceb5..2f37023b53df2bccb7f73ebafa9acbf0d4d2758e 100644 (file)
@@ -8,33 +8,6 @@ import {
 } from '@vue/compiler-dom'
 import { escapeHtml } from '@vue/shared'
 
-/*
-## 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 &&
index b13beb713d8c1b6542138d08fd74a8c4d9bd22b6..565321e50f8a88f4a9878564feacf62c03fd2ad7 100644 (file)
@@ -1,3 +1,76 @@
-import { NodeTransform } from '@vue/compiler-dom'
+import {
+  createStructuralDirectiveTransform,
+  processIfBranches,
+  IfNode,
+  createIfStatement,
+  createBlockStatement,
+  createCallExpression,
+  IfBranchNode,
+  BlockStatement,
+  NodeTypes
+} from '@vue/compiler-dom'
+import {
+  SSRTransformContext,
+  createSSRTransformContext,
+  processChildren
+} from '../ssrCodegenTransform'
 
-export const ssrTransformIf: NodeTransform = () => {}
+// This is the plugin for the first transform pass, which simply constructs the
+// if node and its branches.
+export const ssrTransformIf = createStructuralDirectiveTransform(
+  /^(if|else|else-if)$/,
+  processIfBranches
+)
+
+// This is called during the 2nd transform pass to construct the SSR-sepcific
+// codegen nodes.
+export function processIf(node: IfNode, context: SSRTransformContext) {
+  const [rootBranch] = node.branches
+  const ifStatement = createIfStatement(
+    rootBranch.condition!,
+    processIfBranch(rootBranch, context)
+  )
+  context.pushStatement(ifStatement)
+
+  let currentIf = ifStatement
+  for (let i = 1; i < node.branches.length; i++) {
+    const branch = node.branches[i]
+    const branchBlockStatement = processIfBranch(branch, context)
+    if (branch.condition) {
+      // else-if
+      currentIf = currentIf.alternate = createIfStatement(
+        branch.condition,
+        branchBlockStatement
+      )
+    } else {
+      // else
+      currentIf.alternate = branchBlockStatement
+    }
+  }
+
+  if (!currentIf.alternate) {
+    currentIf.alternate = createBlockStatement([
+      createCallExpression(`_push`, ['`<!---->`'])
+    ])
+  }
+}
+
+function processIfBranch(
+  branch: IfBranchNode,
+  context: SSRTransformContext
+): BlockStatement {
+  const { children } = branch
+  const firstChild = children[0]
+  // TODO optimize away nested fragments when the only child is a ForNode
+  const needFragmentWrapper =
+    children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
+  const childContext = createSSRTransformContext(context.options)
+  if (needFragmentWrapper) {
+    childContext.pushStringPart(`<!---->`)
+  }
+  processChildren(branch.children, childContext)
+  if (needFragmentWrapper) {
+    childContext.pushStringPart(`<!---->`)
+  }
+  return createBlockStatement(childContext.body)
+}