]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler): generate correct fragment children when it contains single text node...
authorEvan You <yyx990803@gmail.com>
Wed, 2 Oct 2019 03:53:52 +0000 (23:53 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 2 Oct 2019 03:53:52 +0000 (23:53 -0400)
packages/compiler-core/__tests__/__snapshots__/compile.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/vFor.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts

index 4748ed19d2c9904c03b76c25551347be53e76404..99b7dd6c0648a07e5a091d70afd4b3668a65ac86 100644 (file)
@@ -14,7 +14,9 @@ return function render() {
       _toString(world.burn()),
       (_openBlock(), ok
         ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
-        : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
+        : _createBlock(_Fragment, { key: 1 }, [
+            \\"no\\"
+          ])),
       _createVNode(_Fragment, null, _renderList(list, (value, index) => {
         return (_openBlock(), _createBlock(\\"div\\", null, [
           _createVNode(\\"span\\", null, _toString(value + index))
@@ -37,7 +39,9 @@ return function render() {
     toString(_ctx.world.burn()),
     (openBlock(), (_ctx.ok)
       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
-      : createBlock(Fragment, { key: 1 }, \\"no\\")),
+      : createBlock(Fragment, { key: 1 }, [
+          \\"no\\"
+        ])),
     createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
       return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, toString(value + index))
@@ -59,7 +63,9 @@ export default function render() {
     _toString(_ctx.world.burn()),
     (openBlock(), (_ctx.ok)
       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
-      : createBlock(Fragment, { key: 1 }, \\"no\\")),
+      : createBlock(Fragment, { key: 1 }, [
+          \\"no\\"
+        ])),
     createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
       return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, _toString(value + index))
index 352f0f62bfc1b3c684742e05bec34a2cf938c42c..29a751517b450d4087c8117fecbd260ce5183df9 100644 (file)
@@ -104,6 +104,20 @@ return function render() {
 }"
 `;
 
+exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, renderSlot: _renderSlot, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    
+    return _createVNode(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createBlock(_Fragment, null, _renderSlot($slots.default)))
+    }), 128 /* UNKEYED_FRAGMENT */)
+  }
+}"
+`;
+
 exports[`compiler: v-for codegen v-if + v-for 1`] = `
 "const _Vue = Vue
 
index bbd8a8d3023b53609b7fc8e8ec55ea3517c38409..b44c2eaefaafd3757f3ce27a8699055c0df35c42 100644 (file)
@@ -32,6 +32,20 @@ return function render() {
 }"
 `;
 
+exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { openBlock: _openBlock, renderSlot: _renderSlot, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
+    
+    return (_openBlock(), ok
+      ? _createBlock(_Fragment, { key: 0 }, _renderSlot($slots.default))
+      : _createBlock(_Empty))
+  }
+}"
+`;
+
 exports[`compiler: v-if codegen v-if + v-else 1`] = `
 "const _Vue = Vue
 
@@ -57,7 +71,9 @@ return function render() {
       ? _createBlock(\\"div\\", { key: 0 })
       : orNot
         ? _createBlock(\\"p\\", { key: 1 })
-        : _createBlock(_Fragment, { key: 2 }, \\"fine\\"))
+        : _createBlock(_Fragment, { key: 2 }, [
+            \\"fine\\"
+          ]))
   }
 }"
 `;
index 389560959aef7004ecfed2bbc387baed3d98e8bc..161a5e0b51505fbda25bf0a19bf2827d2d342d1e 100644 (file)
@@ -4,6 +4,7 @@ import { transformIf } from '../../src/transforms/vIf'
 import { transformFor } from '../../src/transforms/vFor'
 import { transformBind } from '../../src/transforms/vBind'
 import { transformElement } from '../../src/transforms/transformElement'
+import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
 import { transformExpression } from '../../src/transforms/transformExpression'
 import {
   ForNode,
@@ -20,7 +21,8 @@ import {
   CREATE_BLOCK,
   FRAGMENT,
   RENDER_LIST,
-  CREATE_VNODE
+  CREATE_VNODE,
+  RENDER_SLOT
 } from '../../src/runtimeConstants'
 import { PatchFlags } from '@vue/runtime-dom'
 import { PatchFlagNames } from '@vue/shared'
@@ -36,6 +38,7 @@ function parseWithForTransform(
       transformIf,
       transformFor,
       ...(options.prefixIdentifiers ? [transformExpression] : []),
+      transformSlotOutlet,
       transformElement
     ],
     directiveTransforms: {
@@ -692,6 +695,28 @@ describe('compiler: v-for', () => {
       expect(generate(root).code).toMatchSnapshot()
     })
 
+    test('template v-for w/ <slot/>', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithForTransform(
+        '<template v-for="item in items"><slot/></template>'
+      )
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }],
+        blockArgs: [
+          `_${FRAGMENT}`,
+          `null`,
+          {
+            type: NodeTypes.JS_CALL_EXPRESSION,
+            callee: `_${RENDER_SLOT}`
+          }
+        ]
+      })
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
     test('keyed v-for', () => {
       const {
         root,
index f99d7ddb31ce797bbd48241199b6d0483b7239b1..075451526d785dbad2a031f70ecb80058cbbb73a 100644 (file)
@@ -2,6 +2,7 @@ import { parse } from '../../src/parse'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformElement } from '../../src/transforms/transformElement'
+import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
 import {
   IfNode,
   NodeTypes,
@@ -21,7 +22,8 @@ import {
   EMPTY,
   FRAGMENT,
   MERGE_PROPS,
-  APPLY_DIRECTIVES
+  APPLY_DIRECTIVES,
+  RENDER_SLOT
 } from '../../src/runtimeConstants'
 import { createObjectMatcher } from '../testUtils'
 
@@ -32,7 +34,7 @@ function parseWithIfTransform(
 ) {
   const ast = parse(template, options)
   transform(ast, {
-    nodeTransforms: [transformIf, transformElement],
+    nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
     ...options
   })
   if (!options.onError) {
@@ -344,6 +346,25 @@ describe('compiler: v-if', () => {
       expect(generate(root).code).toMatchSnapshot()
     })
 
+    test('template v-if w/ single <slot/> child', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
+      assertSharedCodegen(codegenNode)
+      const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
+        .consequent as CallExpression
+      expect(branch1.arguments).toMatchObject([
+        `_${FRAGMENT}`,
+        `{ key: 0 }`,
+        {
+          type: NodeTypes.JS_CALL_EXPRESSION,
+          callee: `_${RENDER_SLOT}`
+        }
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
     test('v-if + v-else', () => {
       const {
         root,
index 999c875a76bc996c47455173247fcfefff49f23c..c0941afc375eaa8126f7dab2fa1deb880f5ba6c0 100644 (file)
@@ -176,7 +176,7 @@ export type JSChildNode =
 export interface CallExpression extends Node {
   type: NodeTypes.JS_CALL_EXPRESSION
   callee: string
-  arguments: (string | JSChildNode | TemplateChildNode[])[]
+  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[]
 }
 
 export interface ObjectExpression extends Node {
index 81b7cb3f9b4536bcd154db24fbdc3cf4fa5e52be..ac3950929f58281c6e0b5d366b5406f51e6a9665 100644 (file)
@@ -1,7 +1,6 @@
 import {
   RootNode,
   TemplateChildNode,
-  ElementNode,
   TextNode,
   CommentNode,
   ExpressionNode,
@@ -15,7 +14,6 @@ import {
   InterpolationNode,
   CompoundExpressionNode,
   SimpleExpressionNode,
-  ElementTypes,
   FunctionExpression,
   SequenceExpression,
   ConditionalExpression
@@ -232,7 +230,8 @@ export function generate(
 
   // generate the VNode tree expression
   push(`return `)
-  genChildren(ast.children, context, true)
+
+  genRoot(ast, context)
 
   if (useWithBlock) {
     deindent()
@@ -257,31 +256,13 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
   context.newline()
 }
 
-// This will generate a single vnode call if:
-// - The target position explicitly allows a single node (root, if, for)
-// - The list has length === 1, AND The only child is a:
-//   - text
-//   - <slot> outlet, which always produces an array
-function genChildren(
-  children: TemplateChildNode[],
-  context: CodegenContext,
-  allowSingle: boolean = false
-) {
-  if (!children.length) {
-    return context.push(`null`)
-  }
-  const child = children[0]
-  const type = child.type
-  if (
-    children.length === 1 &&
-    (allowSingle ||
-      type === NodeTypes.TEXT ||
-      type === NodeTypes.INTERPOLATION ||
-      type === NodeTypes.COMPOUND_EXPRESSION ||
-      (type === NodeTypes.ELEMENT &&
-        (child as ElementNode).tagType === ElementTypes.SLOT))
-  ) {
-    genNode(child, context)
+function genRoot(root: RootNode, context: CodegenContext) {
+  // TODO handle blocks
+  const { children } = root
+  if (children.length === 0) {
+    context.push(`null`)
+  } else if (children.length === 1) {
+    genNode(children[0], context)
   } else {
     genNodeListAsArray(children, context)
   }
@@ -316,7 +297,7 @@ function genNodeList(
     if (isString(node)) {
       push(node)
     } else if (isArray(node)) {
-      genChildren(node, context)
+      genNodeListAsArray(node, context)
     } else {
       genNode(node, context)
     }
index de6d1ffbeaa3669624a83bbb0c6bf00173d9b796..26d10f77efa4ef44b5e29e439f219f8677f93373 100644 (file)
@@ -87,8 +87,26 @@ export const transformElement: NodeTransform = (node, context) => {
             patchFlag |= PatchFlags.DYNAMIC_SLOTS
           }
         } else {
-          // only v-for fragments will have keyed/unkeyed flags
-          args.push(node.children)
+          if (node.children.length === 1) {
+            const child = node.children[0]
+            const type = child.type
+            // pass directly if the only child is one of:
+            // - text (plain / interpolation / expression)
+            // - <slot> outlet (already an array)
+            if (
+              type === NodeTypes.TEXT ||
+              type === NodeTypes.INTERPOLATION ||
+              type === NodeTypes.COMPOUND_EXPRESSION ||
+              (type === NodeTypes.ELEMENT &&
+                (child as ElementNode).tagType === ElementTypes.SLOT)
+            ) {
+              args.push(child)
+            } else {
+              args.push(node.children)
+            }
+          } else {
+            args.push(node.children)
+          }
         }
       }
       // patchFlag & dynamicPropNames
index 3af7f861926cd617f4acbab01412b7aa09cedfbb..e73e280ee7e43f3c21f738ee95e7cbd30543aa5b 100644 (file)
@@ -14,7 +14,9 @@ import {
   ElementTypes,
   ObjectExpression,
   createObjectExpression,
-  createObjectProperty
+  createObjectProperty,
+  TemplateChildNode,
+  CallExpression
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { getInnerRange, findProp } from '../utils'
@@ -120,12 +122,23 @@ export const transformFor = createStructuralDirectiveTransform(
                 )
               ])
             }
+            let childBlockChildren: TemplateChildNode[] | CallExpression =
+              node.children
+            if (childBlockChildren.length === 1) {
+              const child = childBlockChildren[0]
+              if (
+                child.type === NodeTypes.ELEMENT &&
+                child.tagType === ElementTypes.SLOT
+              ) {
+                childBlockChildren = child.codegenNode!
+              }
+            }
             childBlock = createSequenceExpression([
               createCallExpression(helper(OPEN_BLOCK)),
               createCallExpression(helper(CREATE_BLOCK), [
                 helper(FRAGMENT),
                 childBlockProps,
-                node.children
+                childBlockChildren
               ])
             ])
           } else {
index b3c6371b5dd4a6297441752be6f7f3930cfc27a8..404662702f0143c852f595fe26a4a7a6a1236a13 100644 (file)
@@ -164,19 +164,30 @@ function createChildrenCodegenNode(
   const { children } = branch
   const child = children[0]
   const needFragmentWrapper =
-    children.length > 1 || child.type !== NodeTypes.ELEMENT
+    children.length !== 1 ||
+    child.type !== NodeTypes.ELEMENT ||
+    child.tagType === ElementTypes.SLOT
   if (needFragmentWrapper) {
     const blockArgs: CallExpression['arguments'] = [
       helper(FRAGMENT),
       keyExp,
       children
     ]
-    // optimize away nested fragments when child is a ForNode
-    if (children.length === 1 && child.type === NodeTypes.FOR) {
-      const forBlockExp = child.codegenNode
-      // directly use the for block's children and patchFlag
-      blockArgs[2] = forBlockExp.arguments[2]
-      blockArgs[3] = forBlockExp.arguments[3]
+    if (children.length === 1) {
+      // optimize away nested fragments when child is a ForNode
+      if (child.type === NodeTypes.FOR) {
+        const forBlockArgs = child.codegenNode.arguments
+        // directly use the for block's children and patchFlag
+        blockArgs[2] = forBlockArgs[2]
+        blockArgs[3] = forBlockArgs[3]
+      } else if (
+        child.type === NodeTypes.ELEMENT &&
+        child.tagType === ElementTypes.SLOT
+      ) {
+        // <template v-if="..."><slot/></template>
+        // since slot always returns array, use it directly as the fragment children.
+        blockArgs[2] = child.codegenNode!
+      }
     }
     return createCallExpression(helper(CREATE_BLOCK), blockArgs)
   } else {