]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(compiler-ssr): v-for tests
authorEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 01:47:41 +0000 (20:47 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 01:47:41 +0000 (20:47 -0500)
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-ssr/__tests__/ssrVFor.spec.ts [new file with mode: 0644]
packages/compiler-ssr/__tests__/ssrVIf.spec.ts
packages/compiler-ssr/src/transforms/ssrVFor.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts

index 9081cb3fa3e1c68b899860921a6e04ef9b81f680..d8a8fe1b0040d908de3af8a8ea4b70e20ccd7e32 100644 (file)
@@ -20,7 +20,8 @@ import {
   SlotOutletNode,
   ElementNode,
   DirectiveNode,
-  ForNode
+  ForNode,
+  PlainElementNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
@@ -70,6 +71,9 @@ export const transformFor = createStructuralDirectiveTransform(
         // finish the codegen now that all children have been traversed
         let childBlock
         const isTemplate = isTemplateNode(node)
+        const { children } = forNode
+        const needFragmentWrapper =
+          children.length > 1 || children[0].type !== NodeTypes.ELEMENT
         const slotOutlet = isSlotOutlet(node)
           ? node
           : isTemplate &&
@@ -94,8 +98,8 @@ export const transformFor = createStructuralDirectiveTransform(
             // the props for renderSlot is passed as the 3rd argument.
             injectProp(childBlock, keyProperty, context)
           }
-        } else if (isTemplate) {
-          // <template v-for="...">
+        } else if (needFragmentWrapper) {
+          // <template v-for="..."> with text or multi-elements
           // should generate a fragment block for each loop
           childBlock = createBlockExpression(
             createCallExpression(helper(CREATE_BLOCK), [
@@ -111,7 +115,8 @@ export const transformFor = createStructuralDirectiveTransform(
         } else {
           // Normal element v-for. Directly use the child's codegenNode
           // arguments, but replace createVNode() with createBlock()
-          let codegenNode = node.codegenNode as ElementCodegenNode
+          let codegenNode = (children[0] as PlainElementNode)
+            .codegenNode as ElementCodegenNode
           if (codegenNode.callee === WITH_DIRECTIVES) {
             codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
           } else {
diff --git a/packages/compiler-ssr/__tests__/ssrVFor.spec.ts b/packages/compiler-ssr/__tests__/ssrVFor.spec.ts
new file mode 100644 (file)
index 0000000..ac11021
--- /dev/null
@@ -0,0 +1,126 @@
+import { compile } from '../src'
+
+describe('ssr: v-for', () => {
+  test('basic', () => {
+    expect(compile(`<div v-for="i in list" />`).code).toMatchInlineSnapshot(`
+      "const { _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (i) => {
+          _push(\`<div></div>\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('nested content', () => {
+    expect(compile(`<div v-for="i in list">foo<span>bar</span></div>`).code)
+      .toMatchInlineSnapshot(`
+      "const { _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (i) => {
+          _push(\`<div>foo<span>bar</span></div>\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('nested v-for', () => {
+    expect(
+      compile(
+        `<div v-for="row, i in list">` +
+          `<div v-for="j in row">{{ i }},{{ j }}</div>` +
+          `</div>`
+      ).code
+    ).toMatchInlineSnapshot(`
+      "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (row, i) => {
+          _push(\`<div><!---->\`)
+          _renderList(row, (j) => {
+            _push(\`<div>\${_interpolate(i)},\${_interpolate(j)}</div>\`)
+          })
+          _push(\`<!----></div>\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('template v-for (text)', () => {
+    expect(compile(`<template v-for="i in list">{{ i }}</template>`).code)
+      .toMatchInlineSnapshot(`
+      "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (i) => {
+          _push(\`<!---->\${_interpolate(i)}<!---->\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('template v-for (single element)', () => {
+    expect(
+      compile(`<template v-for="i in list"><span>{{ i }}</span></template>`)
+        .code
+    ).toMatchInlineSnapshot(`
+      "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (i) => {
+          _push(\`<span>\${_interpolate(i)}</span>\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('template v-for (multi element)', () => {
+    expect(
+      compile(
+        `<template v-for="i in list"><span>{{ i }}</span><span>{{ i + 1 }}</span></template>`
+      ).code
+    ).toMatchInlineSnapshot(`
+      "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, (i) => {
+          _push(\`<!----><span>\${_interpolate(i)}</span><span>\${_interpolate(i + 1)}</span><!---->\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+
+  test('render loop args should not be prefixed', () => {
+    const { code } = compile(
+      `<div v-for="{ foo }, index in list">{{ foo + bar + index }}</div>`
+    )
+    expect(code).toMatch(`_ctx.bar`)
+    expect(code).not.toMatch(`_ctx.foo`)
+    expect(code).not.toMatch(`_ctx.index`)
+    expect(code).toMatchInlineSnapshot(`
+      "const { _interpolate, _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<!---->\`)
+        _renderList(_ctx.list, ({ foo }, index) => {
+          _push(\`<div>\${_interpolate(foo + _ctx.bar + index)}</div>\`)
+        })
+        _push(\`<!---->\`)
+      }"
+    `)
+  })
+})
index a5a80d121855e9f576fcfc1c1068cfadca2cdeae..a12ad34f61be9786ad99874f3b77ac8626efc574 100644 (file)
@@ -119,7 +119,23 @@ describe('ssr: v-if', () => {
   })
 
   test('<template v-if> (with v-for inside)', () => {
-    // TODO should not contain nested fragments
+    expect(
+      compile(`<template v-if="foo"><div v-for="i in list"/></template>`).code
+    ).toMatchInlineSnapshot(`
+      "const { _renderList } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        if (_ctx.foo) {
+          _push(\`<!---->\`)
+          _renderList(_ctx.list, (i) => {
+            _push(\`<div></div>\`)
+          })
+          _push(\`<!---->\`)
+        } else {
+          _push(\`<!---->\`)
+        }
+      }"
+    `)
   })
 
   test('<template v-if> + normal v-else', () => {
index 8e03edf31eb3575e29d2a7bd3d92c9a6a8749077..06e687f31bd347aeaf68daff619d90eb6a4ccff0 100644 (file)
@@ -5,7 +5,8 @@ import {
   createCallExpression,
   createFunctionExpression,
   createForLoopParams,
-  createBlockStatement
+  createBlockStatement,
+  NodeTypes
 } from '@vue/compiler-dom'
 import {
   SSRTransformContext,
@@ -23,16 +24,28 @@ export const ssrTransformFor = createStructuralDirectiveTransform(
 // This is called during the 2nd transform pass to construct the SSR-sepcific
 // codegen nodes.
 export function processFor(node: ForNode, context: SSRTransformContext) {
+  const childContext = createChildContext(context)
+  const needFragmentWrapper =
+    node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
+  if (needFragmentWrapper) {
+    childContext.pushStringPart(`<!---->`)
+  }
+  processChildren(node.children, childContext)
+  if (needFragmentWrapper) {
+    childContext.pushStringPart(`<!---->`)
+  }
   const renderLoop = createFunctionExpression(
     createForLoopParams(node.parseResult)
   )
-  const childContext = createChildContext(context)
-  processChildren(node.children, childContext)
   renderLoop.body = createBlockStatement(childContext.body)
+
+  // v-for always renders a fragment
+  context.pushStringPart(`<!---->`)
   context.pushStatement(
     createCallExpression(context.helper(SSR_RENDER_LIST), [
       node.source,
       renderLoop
     ])
   )
+  context.pushStringPart(`<!---->`)
 }
index ea7d985cadb94ba8cc173931d358c87d74299659..905dd5391fe941a6b73d33916aec7c993b4b1b2a 100644 (file)
@@ -59,15 +59,15 @@ function processIfBranch(
   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
+    (children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
+    // optimize away nested fragments when the only child is a ForNode
+    !(children.length === 1 && children[0].type === NodeTypes.FOR)
   const childContext = createChildContext(context)
   if (needFragmentWrapper) {
     childContext.pushStringPart(`<!---->`)
   }
-  processChildren(branch.children, childContext)
+  processChildren(children, childContext)
   if (needFragmentWrapper) {
     childContext.pushStringPart(`<!---->`)
   }