]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-ssr): generate correct children for transition-group
authorEvan You <yyx990803@gmail.com>
Fri, 27 Nov 2020 17:22:14 +0000 (12:22 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 27 Nov 2020 17:22:14 +0000 (12:22 -0500)
fix #2510

packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrVFor.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts
packages/runtime-core/src/components/BaseTransition.ts

index d175d2dfc116de0bc13d3ffd68f0e35f58d619b3..2fdd8010bc1f0854cf086c85e025cbd95b9912c7 100644 (file)
@@ -275,14 +275,6 @@ describe('ssr: components', () => {
         }"
       `)
 
-      expect(compile(`<transition-group><div/></transition-group>`).code)
-        .toMatchInlineSnapshot(`
-        "
-        return function ssrRender(_ctx, _push, _parent, _attrs) {
-          _push(\`<!--[--><div></div><!--]-->\`)
-        }"
-      `)
-
       expect(compile(`<keep-alive><foo/></keep-alive>`).code)
         .toMatchInlineSnapshot(`
         "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
@@ -295,5 +287,93 @@ describe('ssr: components', () => {
         }"
       `)
     })
+
+    // transition-group should flatten and concat its children fragments into
+    // a single one
+    describe('transition-group', () => {
+      test('basic', () => {
+        expect(
+          compile(
+            `<transition-group><div v-for="i in list"/></transition-group>`
+          ).code
+        ).toMatchInlineSnapshot(`
+          "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
+
+          return function ssrRender(_ctx, _push, _parent, _attrs) {
+            _push(\`<!--[-->\`)
+            _ssrRenderList(_ctx.list, (i) => {
+              _push(\`<div></div>\`)
+            })
+            _push(\`<!--]-->\`)
+          }"
+        `)
+      })
+
+      test('with static tag', () => {
+        expect(
+          compile(
+            `<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
+          ).code
+        ).toMatchInlineSnapshot(`
+          "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
+
+          return function ssrRender(_ctx, _push, _parent, _attrs) {
+            _push(\`<ul>\`)
+            _ssrRenderList(_ctx.list, (i) => {
+              _push(\`<div></div>\`)
+            })
+            _push(\`</ul>\`)
+          }"
+        `)
+      })
+
+      test('with dynamic tag', () => {
+        expect(
+          compile(
+            `<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
+          ).code
+        ).toMatchInlineSnapshot(`
+          "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
+
+          return function ssrRender(_ctx, _push, _parent, _attrs) {
+            _push(\`<\${_ctx.someTag}>\`)
+            _ssrRenderList(_ctx.list, (i) => {
+              _push(\`<div></div>\`)
+            })
+            _push(\`</\${_ctx.someTag}>\`)
+          }"
+        `)
+      })
+
+      test('with multi fragments children', () => {
+        expect(
+          compile(
+            `<transition-group>
+              <div v-for="i in 10"/>
+              <div v-for="i in 10"/>
+              <template v-if="ok"><div>ok</div></template>
+            </transition-group>`
+          ).code
+        ).toMatchInlineSnapshot(`
+          "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
+
+          return function ssrRender(_ctx, _push, _parent, _attrs) {
+            _push(\`<!--[-->\`)
+            _ssrRenderList(10, (i) => {
+              _push(\`<div></div>\`)
+            })
+            _ssrRenderList(10, (i) => {
+              _push(\`<div></div>\`)
+            })
+            if (_ctx.ok) {
+              _push(\`<div>ok</div>\`)
+            } else {
+              _push(\`<!---->\`)
+            }
+            _push(\`<!--]-->\`)
+          }"
+        `)
+      })
+    })
   })
 })
index 949db9c58130e00b571946d18edf25b5db20aa06..cebce7433bf364d21f85dfd520c172ca4afa8ff3 100644 (file)
@@ -128,7 +128,8 @@ function createChildContext(
 export function processChildren(
   children: TemplateChildNode[],
   context: SSRTransformContext,
-  asFragment = false
+  asFragment = false,
+  disableNestedFragments = false
 ) {
   if (asFragment) {
     context.pushStringPart(`<!--[-->`)
@@ -176,10 +177,10 @@ export function processChildren(
         )
         break
       case NodeTypes.IF:
-        ssrProcessIf(child, context)
+        ssrProcessIf(child, context, disableNestedFragments)
         break
       case NodeTypes.FOR:
-        ssrProcessFor(child, context)
+        ssrProcessFor(child, context, disableNestedFragments)
         break
       case NodeTypes.IF_BRANCH:
         // no-op - handled by ssrProcessIf
index 69bf0662fe2c9381f5125cbe3a0f3c503a199036..c2cf509c5783996f3ee41d9ef021d1f223faa73f 100644 (file)
@@ -46,6 +46,7 @@ import {
   ssrProcessSuspense,
   ssrTransformSuspense
 } from './ssrTransformSuspense'
+import { ssrProcessTransitionGroup } from './ssrTransformTransitionGroup'
 import { isSymbol, isObject, isArray } from '@vue/shared'
 
 // We need to construct the slot functions in the 1st pass to ensure proper
@@ -176,9 +177,11 @@ export function ssrProcessComponent(
       return ssrProcessTeleport(node, context)
     } else if (component === SUSPENSE) {
       return ssrProcessSuspense(node, context)
+    } else if (component === TRANSITION_GROUP) {
+      return ssrProcessTransitionGroup(node, context)
     } else {
       // real fall-through (e.g. KeepAlive): just render its children.
-      processChildren(node.children, context, component === TRANSITION_GROUP)
+      processChildren(node.children, context)
     }
   } else {
     // finish up slot function expressions from the 1st pass.
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts b/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
new file mode 100644 (file)
index 0000000..3622900
--- /dev/null
@@ -0,0 +1,41 @@
+import { ComponentNode, findProp, NodeTypes } from '@vue/compiler-dom'
+import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
+
+export function ssrProcessTransitionGroup(
+  node: ComponentNode,
+  context: SSRTransformContext
+) {
+  const tag = findProp(node, 'tag')
+  if (tag) {
+    if (tag.type === NodeTypes.DIRECTIVE) {
+      // dynamic :tag
+      context.pushStringPart(`<`)
+      context.pushStringPart(tag.exp!)
+      context.pushStringPart(`>`)
+
+      processChildren(
+        node.children,
+        context,
+        false,
+        /**
+         * TransitionGroup has the special runtime behavior of flattening and
+         * concatenating all children into a single fragment (in order for them to
+         * be pathced using the same key map) so we need to account for that here
+         * by disabling nested fragment wrappers from being generated.
+         */
+        true
+      )
+      context.pushStringPart(`</`)
+      context.pushStringPart(tag.exp!)
+      context.pushStringPart(`>`)
+    } else {
+      // static tag
+      context.pushStringPart(`<${tag.value!.content}>`)
+      processChildren(node.children, context, false, true)
+      context.pushStringPart(`</${tag.value!.content}>`)
+    }
+  } else {
+    // fragment
+    processChildren(node.children, context, true, true)
+  }
+}
index 0dd8b858eb204d783bc527ea210d5debcaea7f83..583873b66ff83a2c8a204d60c953f550a9723bbe 100644 (file)
@@ -21,9 +21,14 @@ export const ssrTransformFor = createStructuralDirectiveTransform(
 
 // This is called during the 2nd transform pass to construct the SSR-specific
 // codegen nodes.
-export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
+export function ssrProcessFor(
+  node: ForNode,
+  context: SSRTransformContext,
+  disableNestedFragments = false
+) {
   const needFragmentWrapper =
-    node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
+    !disableNestedFragments &&
+    (node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT)
   const renderLoop = createFunctionExpression(
     createForLoopParams(node.parseResult)
   )
@@ -32,13 +37,17 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
     context,
     needFragmentWrapper
   )
-  // v-for always renders a fragment
-  context.pushStringPart(`<!--[-->`)
+  // v-for always renders a fragment unless explicitly disabled
+  if (!disableNestedFragments) {
+    context.pushStringPart(`<!--[-->`)
+  }
   context.pushStatement(
     createCallExpression(context.helper(SSR_RENDER_LIST), [
       node.source,
       renderLoop
     ])
   )
-  context.pushStringPart(`<!--]-->`)
+  if (!disableNestedFragments) {
+    context.pushStringPart(`<!--]-->`)
+  }
 }
index 9eea340b70e062024a99daa71c5e3eb24a06663b..57f77eafd30ec8424128ff9d5ab170af6a5a6606 100644 (file)
@@ -22,18 +22,26 @@ export const ssrTransformIf = createStructuralDirectiveTransform(
 
 // This is called during the 2nd transform pass to construct the SSR-specific
 // codegen nodes.
-export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
+export function ssrProcessIf(
+  node: IfNode,
+  context: SSRTransformContext,
+  disableNestedFragments = false
+) {
   const [rootBranch] = node.branches
   const ifStatement = createIfStatement(
     rootBranch.condition!,
-    processIfBranch(rootBranch, context)
+    processIfBranch(rootBranch, context, disableNestedFragments)
   )
   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)
+    const branchBlockStatement = processIfBranch(
+      branch,
+      context,
+      disableNestedFragments
+    )
     if (branch.condition) {
       // else-if
       currentIf = currentIf.alternate = createIfStatement(
@@ -55,10 +63,12 @@ export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
 
 function processIfBranch(
   branch: IfBranchNode,
-  context: SSRTransformContext
+  context: SSRTransformContext,
+  disableNestedFragments = false
 ): BlockStatement {
   const { children } = branch
   const needFragmentWrapper =
+    !disableNestedFragments &&
     (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)
index fc0ebb9e42c040c07055a113aa9d0ebddfa41a10..674eb795616807a464ace68c03950c7793299531 100644 (file)
@@ -471,7 +471,7 @@ export function getTransitionRawChildren(
   }
   // #1126 if a transition children list contains multiple sub fragments, these
   // fragments will be merged into a flat children array. Since each v-for
-  // fragment may contain different static bindings inside, we need to de-top
+  // fragment may contain different static bindings inside, we need to de-op
   // these children to force full diffs to ensure correct behavior.
   if (keyedFragmentCount > 1) {
     for (let i = 0; i < ret.length; i++) {