]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): v-for codegen w/ correct blocks optimization + key flags
authorEvan You <yyx990803@gmail.com>
Wed, 2 Oct 2019 03:19:48 +0000 (23:19 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 2 Oct 2019 03:19:48 +0000 (23:19 -0400)
12 files changed:
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/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
packages/compiler-core/src/utils.ts
packages/runtime-core/src/createRenderer.ts
packages/shared/src/patchFlags.ts

index efbeb1be1dd84cfde4a7f66c6a3ab85ff41e0d13..4748ed19d2c9904c03b76c25551347be53e76404 100644 (file)
@@ -15,11 +15,11 @@ return function render() {
       (_openBlock(), ok
         ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
         : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
-      (_openBlock(), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
-        return _createVNode(\\"div\\", null, [
+      _createVNode(_Fragment, null, _renderList(list, (value, index) => {
+        return (_openBlock(), _createBlock(\\"div\\", null, [
           _createVNode(\\"span\\", null, _toString(value + index))
-        ])
-      })))
+        ]))
+      }), 128 /* UNKEYED_FRAGMENT */)
     ], 2 /* CLASS */)
   }
 }"
@@ -38,11 +38,11 @@ return function render() {
     (openBlock(), (_ctx.ok)
       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
       : createBlock(Fragment, { key: 1 }, \\"no\\")),
-    (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
-      return createVNode(\\"div\\", null, [
+    createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
+      return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, toString(value + index))
-      ])
-    })))
+      ]))
+    }), 128 /* UNKEYED_FRAGMENT */)
   ], 2 /* CLASS */)
 }"
 `;
@@ -60,11 +60,11 @@ export default function render() {
     (openBlock(), (_ctx.ok)
       ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
       : createBlock(Fragment, { key: 1 }, \\"no\\")),
-    (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
-      return createVNode(\\"div\\", null, [
+    createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
+      return (openBlock(), createBlock(\\"div\\", null, [
         createVNode(\\"span\\", null, _toString(value + index))
-      ])
-    })))
+      ]))
+    }), 128 /* UNKEYED_FRAGMENT */)
   ], 2 /* CLASS */)
 }"
 `;
index d9ce98eb309e84f13a407484692a92ab628a83b4..352f0f62bfc1b3c684742e05bec34a2cf938c42c 100644 (file)
@@ -5,11 +5,42 @@ exports[`compiler: v-for codegen basic v-for 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
-      return _createVNode(\\"span\\")
-    })))
+    return _createVNode(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createBlock(\\"span\\"))
+    }), 128 /* UNKEYED_FRAGMENT */)
+  }
+}"
+`;
+
+exports[`compiler: v-for codegen keyed template v-for 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    
+    return _createVNode(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createBlock(_Fragment, { key: item }, [
+        \\"hello\\",
+        _createVNode(\\"span\\")
+      ]))
+    }), 64 /* KEYED_FRAGMENT */)
+  }
+}"
+`;
+
+exports[`compiler: v-for codegen keyed v-for 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+    
+    return _createVNode(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createBlock(\\"span\\", { key: item }))
+    }), 64 /* KEYED_FRAGMENT */)
   }
 }"
 `;
@@ -19,11 +50,11 @@ exports[`compiler: v-for codegen skipped key 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (value, __, index) => {
-      return _createVNode(\\"span\\")
-    })))
+    return _createVNode(_Fragment, null, _renderList(items, (item, __, index) => {
+      return (_openBlock(), _createBlock(\\"span\\"))
+    }), 128 /* UNKEYED_FRAGMENT */)
   }
 }"
 `;
@@ -33,11 +64,11 @@ exports[`compiler: v-for codegen skipped value & key 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
-      return _createVNode(\\"span\\")
-    })))
+    return _createVNode(_Fragment, null, _renderList(items, (_, __, index) => {
+      return (_openBlock(), _createBlock(\\"span\\"))
+    }), 128 /* UNKEYED_FRAGMENT */)
   }
 }"
 `;
@@ -47,11 +78,11 @@ exports[`compiler: v-for codegen skipped value 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
-      return _createVNode(\\"span\\")
-    })))
+    return _createVNode(_Fragment, null, _renderList(items, (_, key, index) => {
+      return (_openBlock(), _createBlock(\\"span\\"))
+    }), 128 /* UNKEYED_FRAGMENT */)
   }
 }"
 `;
@@ -61,14 +92,14 @@ exports[`compiler: v-for codegen template v-for 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
-      return [
+    return _createVNode(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createBlock(_Fragment, null, [
         \\"hello\\",
         _createVNode(\\"span\\")
-      ]
-    })))
+      ]))
+    }), 128 /* UNKEYED_FRAGMENT */)
   }
 }"
 `;
@@ -78,12 +109,12 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList, Empty: _Empty } = _Vue
+    const { openBlock: _openBlock, renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
-          return _createVNode(\\"div\\")
-        }))
+          return (_openBlock(), _createBlock(\\"div\\"))
+        }), 128 /* UNKEYED_FRAGMENT */)
       : _createBlock(_Empty))
   }
 }"
@@ -94,11 +125,11 @@ exports[`compiler: v-for codegen value + key + index 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+    const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
-    return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
-      return _createVNode(\\"span\\")
-    })))
+    return _createVNode(_Fragment, null, _renderList(items, (item, key, index) => {
+      return (_openBlock(), _createBlock(\\"span\\"))
+    }), 128 /* UNKEYED_FRAGMENT */)
   }
 }"
 `;
index 6509aa12ccc482771ab91b9cf2767311f507cd91..bbd8a8d3023b53609b7fc8e8ec55ea3517c38409 100644 (file)
@@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = `
 
 return function render() {
   with (this) {
-    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, Empty: _Empty } = _Vue
+    const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
     
     return (_openBlock(), ok
       ? _createBlock(_Fragment, { key: 0 }, [
index 0fa36d5bc4f4e71426fb5d947aed638d527008f4..389560959aef7004ecfed2bbc387baed3d98e8bc 100644 (file)
@@ -2,25 +2,29 @@ import { parse } from '../../src/parse'
 import { transform } from '../../src/transform'
 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 { transformExpression } from '../../src/transforms/transformExpression'
 import {
   ForNode,
   NodeTypes,
   SimpleExpressionNode,
   ElementNode,
   InterpolationNode,
-  SequenceExpression,
   CallExpression
 } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
 import { CompilerOptions, generate } from '../../src'
-import { transformExpression } from '../../src/transforms/transformExpression'
 import {
   OPEN_BLOCK,
   CREATE_BLOCK,
   FRAGMENT,
-  RENDER_LIST
+  RENDER_LIST,
+  CREATE_VNODE
 } from '../../src/runtimeConstants'
+import { PatchFlags } from '@vue/runtime-dom'
+import { PatchFlagNames } from '@vue/shared'
+import { createObjectMatcher } from '../testUtils'
 
 function parseWithForTransform(
   template: string,
@@ -34,6 +38,9 @@ function parseWithForTransform(
       ...(options.prefixIdentifiers ? [transformExpression] : []),
       transformElement
     ],
+    directiveTransforms: {
+      bind: transformBind
+    },
     ...options
   })
   return {
@@ -555,31 +562,51 @@ describe('compiler: v-for', () => {
   })
 
   describe('codegen', () => {
-    function assertSharedCodegen(node: SequenceExpression) {
+    function assertSharedCodegen(node: CallExpression, keyed: boolean = false) {
       expect(node).toMatchObject({
-        type: NodeTypes.JS_SEQUENCE_EXPRESSION,
-        expressions: [
-          {
-            type: NodeTypes.JS_CALL_EXPRESSION,
-            callee: `_${OPEN_BLOCK}`,
-            arguments: []
-          },
+        type: NodeTypes.JS_CALL_EXPRESSION,
+        callee: `_${CREATE_VNODE}`,
+        arguments: [
+          `_${FRAGMENT}`,
+          `null`,
           {
             type: NodeTypes.JS_CALL_EXPRESSION,
-            callee: `_${CREATE_BLOCK}`,
+            callee: `_${RENDER_LIST}`,
             arguments: [
-              `_${FRAGMENT}`,
-              `null`,
+              {}, // to be asserted by each test
               {
-                type: NodeTypes.JS_CALL_EXPRESSION,
-                callee: `_${RENDER_LIST}`
+                type: NodeTypes.JS_FUNCTION_EXPRESSION,
+                returns: {
+                  type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+                  expressions: [
+                    {
+                      type: NodeTypes.JS_CALL_EXPRESSION,
+                      callee: `_${OPEN_BLOCK}`
+                    },
+                    {
+                      type: NodeTypes.JS_CALL_EXPRESSION,
+                      callee: `_${CREATE_BLOCK}`
+                    }
+                  ]
+                }
               }
             ]
-          }
+          },
+          keyed
+            ? `${PatchFlags.KEYED_FRAGMENT} /* ${
+                PatchFlagNames[PatchFlags.KEYED_FRAGMENT]
+              } */`
+            : `${PatchFlags.UNKEYED_FRAGMENT} /* ${
+                PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
+              } */`
         ]
       })
-      return (node.expressions[1] as CallExpression)
-        .arguments[2] as CallExpression
+      const renderListArgs = (node.arguments[2] as CallExpression).arguments
+      return {
+        source: renderListArgs[0] as SimpleExpressionNode,
+        params: (renderListArgs[1] as any).params,
+        blockArgs: (renderListArgs[1] as any).returns.expressions[1].arguments
+      }
     }
 
     test('basic v-for', () => {
@@ -587,17 +614,11 @@ describe('compiler: v-for', () => {
         root,
         node: { codegenNode }
       } = parseWithForTransform('<span v-for="(item) in items" />')
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [{ content: `item` }],
-          returns: {
-            type: NodeTypes.ELEMENT,
-            tag: `span`
-          }
-        }
-      ])
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }],
+        blockArgs: [`"span"`]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -606,17 +627,10 @@ describe('compiler: v-for', () => {
         root,
         node: { codegenNode }
       } = parseWithForTransform('<span v-for="(item, key, index) in items" />')
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [
-            { content: `item` },
-            { content: `key` },
-            { content: `index` }
-          ]
-        }
-      ])
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }, { content: `key` }, { content: `index` }]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -624,14 +638,11 @@ describe('compiler: v-for', () => {
       const {
         root,
         node: { codegenNode }
-      } = parseWithForTransform('<span v-for="(,key,index) in items" />')
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [{ content: `_` }, { content: `key` }, { content: `index` }]
-        }
-      ])
+      } = parseWithForTransform('<span v-for="(, key, index) in items" />')
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `_` }, { content: `key` }, { content: `index` }]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -639,18 +650,11 @@ describe('compiler: v-for', () => {
       const {
         root,
         node: { codegenNode }
-      } = parseWithForTransform('<span v-for="(value,,index) in items" />')
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [
-            { content: `value` },
-            { content: `__` },
-            { content: `index` }
-          ]
-        }
-      ])
+      } = parseWithForTransform('<span v-for="(item,,index) in items" />')
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }, { content: `__` }, { content: `index` }]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -659,13 +663,10 @@ describe('compiler: v-for', () => {
         root,
         node: { codegenNode }
       } = parseWithForTransform('<span v-for="(,,index) in items" />')
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [{ content: `_` }, { content: `__` }, { content: `index` }]
-        }
-      ])
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `_` }, { content: `__` }, { content: `index` }]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -676,17 +677,60 @@ describe('compiler: v-for', () => {
       } = parseWithForTransform(
         '<template v-for="item in items">hello<span/></template>'
       )
-      expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
-        { content: `items` },
-        {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: [{ content: `item` }],
-          returns: [
+      expect(assertSharedCodegen(codegenNode)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }],
+        blockArgs: [
+          `_${FRAGMENT}`,
+          `null`,
+          [
             { type: NodeTypes.TEXT, content: `hello` },
             { type: NodeTypes.ELEMENT, tag: `span` }
           ]
-        }
-      ])
+        ]
+      })
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('keyed v-for', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithForTransform('<span v-for="(item) in items" :key="item" />')
+      expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }],
+        blockArgs: [
+          `"span"`,
+          createObjectMatcher({
+            key: `[item]`
+          })
+        ]
+      })
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('keyed template v-for', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithForTransform(
+        '<template v-for="item in items" :key="item">hello<span/></template>'
+      )
+      expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+        source: { content: `items` },
+        params: [{ content: `item` }],
+        blockArgs: [
+          `_${FRAGMENT}`,
+          createObjectMatcher({
+            key: `[item]`
+          }),
+          [
+            { type: NodeTypes.TEXT, content: `hello` },
+            { type: NodeTypes.ELEMENT, tag: `span` }
+          ]
+        ]
+      })
       expect(generate(root).code).toMatchSnapshot()
     })
 
@@ -722,12 +766,25 @@ describe('compiler: v-for', () => {
                       type: NodeTypes.JS_FUNCTION_EXPRESSION,
                       params: [{ content: `i` }],
                       returns: {
-                        type: NodeTypes.ELEMENT,
-                        tag: `div`
+                        type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+                        expressions: [
+                          {
+                            type: NodeTypes.JS_CALL_EXPRESSION,
+                            callee: `_${OPEN_BLOCK}`
+                          },
+                          {
+                            type: NodeTypes.JS_CALL_EXPRESSION,
+                            callee: `_${CREATE_BLOCK}`,
+                            arguments: [`"div"`]
+                          }
+                        ]
                       }
                     }
                   ]
-                }
+                },
+                `${PatchFlags.UNKEYED_FRAGMENT} /* ${
+                  PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
+                } */`
               ]
             }
           }
index cc8f4296ac1154278d262179ab8ef89ce291eb87..999c875a76bc996c47455173247fcfefff49f23c 100644 (file)
@@ -158,7 +158,7 @@ export interface ForNode extends Node {
   keyAlias: ExpressionNode | undefined
   objectIndexAlias: ExpressionNode | undefined
   children: TemplateChildNode[]
-  codegenNode: SequenceExpression
+  codegenNode: CallExpression
 }
 
 // We also include a number of JavaScript AST nodes for code generation.
@@ -198,7 +198,7 @@ export interface ArrayExpression extends Node {
 export interface FunctionExpression extends Node {
   type: NodeTypes.JS_FUNCTION_EXPRESSION
   params: ExpressionNode | ExpressionNode[] | undefined
-  returns: TemplateChildNode | TemplateChildNode[]
+  returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
   newline: boolean
 }
 
index c25518c14b23cfcc27ea1e3272f20bbffa2651b8..81b7cb3f9b4536bcd154db24fbdc3cf4fa5e52be 100644 (file)
@@ -261,7 +261,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
 // - The target position explicitly allows a single node (root, if, for)
 // - The list has length === 1, AND The only child is a:
 //   - text
-//   - expression
 //   - <slot> outlet, which always produces an array
 function genChildren(
   children: TemplateChildNode[],
index ae9155a2458ddefccba5bec21a9556b0df0c25a3..de6d1ffbeaa3669624a83bbb0c6bf00173d9b796 100644 (file)
@@ -163,7 +163,7 @@ export function buildProps(
   // patchFlag analysis
   let patchFlag = 0
   const dynamicPropNames: string[] = []
-  let hasDynammicKeys = false
+  let hasDynamicKeys = false
   let hasClassBinding = false
   let hasStyleBinding = false
   let hasRef = false
@@ -207,7 +207,7 @@ export function buildProps(
       // special case for v-bind and v-on with no argument
       const isBind = name === 'bind'
       if (!arg && (isBind || name === 'on')) {
-        hasDynammicKeys = true
+        hasDynamicKeys = true
         if (exp) {
           if (properties.length) {
             mergeArgs.push(
@@ -249,11 +249,11 @@ export function buildProps(
             hasClassBinding = true
           } else if (name === 'style') {
             hasStyleBinding = true
-          } else {
+          } else if (name !== 'key') {
             dynamicPropNames.push(name)
           }
         } else {
-          hasDynammicKeys = true
+          hasDynamicKeys = true
         }
       }
 
@@ -303,7 +303,7 @@ export function buildProps(
   }
 
   // determine the flags to add
-  if (hasDynammicKeys) {
+  if (hasDynamicKeys) {
     patchFlag |= PatchFlags.FULL_PROPS
   } else {
     if (hasClassBinding) {
index aefd6f0871706fe4cf97ebf26034677785a4cb76..3af7f861926cd617f4acbab01412b7aa09cedfbb 100644 (file)
@@ -11,17 +11,22 @@ import {
   createSequenceExpression,
   createCallExpression,
   createFunctionExpression,
-  ElementTypes
+  ElementTypes,
+  ObjectExpression,
+  createObjectExpression,
+  createObjectProperty
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
-import { getInnerRange } from '../utils'
+import { getInnerRange, findProp } from '../utils'
 import {
   RENDER_LIST,
   OPEN_BLOCK,
   CREATE_BLOCK,
-  FRAGMENT
+  FRAGMENT,
+  CREATE_VNODE
 } from '../runtimeConstants'
 import { processExpression } from './transformExpression'
+import { PatchFlags, PatchFlagNames } from '@vue/shared'
 
 export const transformFor = createStructuralDirectiveTransform(
   'for',
@@ -38,9 +43,19 @@ export const transformFor = createStructuralDirectiveTransform(
         const { helper, addIdentifiers, removeIdentifiers } = context
         const { source, value, key, index } = parseResult
 
-        const codegenNode = createSequenceExpression([
-          createCallExpression(helper(OPEN_BLOCK))
-          // to be filled in on exit after children traverse
+        // create the loop render function expression now, and add the
+        // iterator on exit after all children have been traversed
+        const renderExp = createCallExpression(helper(RENDER_LIST), [source])
+        const keyProp = findProp(node.props, `key`)
+        const fragmentFlag = keyProp
+          ? PatchFlags.KEYED_FRAGMENT
+          : PatchFlags.UNKEYED_FRAGMENT
+        const codegenNode = createCallExpression(helper(CREATE_VNODE), [
+          helper(FRAGMENT),
+          `null`,
+          renderExp,
+          fragmentFlag +
+            (__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``)
         ])
 
         context.replaceNode({
@@ -63,6 +78,13 @@ export const transformFor = createStructuralDirectiveTransform(
         }
 
         return () => {
+          if (!__BROWSER__ && context.prefixIdentifiers) {
+            value && removeIdentifiers(value)
+            key && removeIdentifiers(key)
+            index && removeIdentifiers(index)
+          }
+
+          // finish the codegen now that all children have been traversed
           const params: ExpressionNode[] = []
           if (value) {
             params.push(value)
@@ -83,26 +105,46 @@ export const transformFor = createStructuralDirectiveTransform(
             params.push(index)
           }
 
-          codegenNode.expressions.push(
-            createCallExpression(helper(CREATE_BLOCK), [
-              helper(FRAGMENT),
-              `null`,
-              createCallExpression(helper(RENDER_LIST), [
-                source,
-                createFunctionExpression(
-                  params,
-                  node.tagType === ElementTypes.TEMPLATE ? node.children : node,
-                  true /* force newline to make it more readable */
+          let childBlock
+          if (node.tagType === ElementTypes.TEMPLATE) {
+            // <template v-for="...">
+            // should genereate a fragment block for each loop
+            let childBlockProps: string | ObjectExpression = `null`
+            if (keyProp) {
+              childBlockProps = createObjectExpression([
+                createObjectProperty(
+                  createSimpleExpression(`key`, true),
+                  keyProp.type === NodeTypes.ATTRIBUTE
+                    ? createSimpleExpression(keyProp.value!.content, true)
+                    : keyProp.exp!
                 )
               ])
+            }
+            childBlock = createSequenceExpression([
+              createCallExpression(helper(OPEN_BLOCK)),
+              createCallExpression(helper(CREATE_BLOCK), [
+                helper(FRAGMENT),
+                childBlockProps,
+                node.children
+              ])
+            ])
+          } else {
+            // Normal element v-for. Directly use the child's codegenNode,
+            // but replace createVNode() with createBlock()
+            node.codegenNode!.callee = helper(CREATE_BLOCK)
+            childBlock = createSequenceExpression([
+              createCallExpression(helper(OPEN_BLOCK)),
+              node.codegenNode!
             ])
-          )
-
-          if (!__BROWSER__ && context.prefixIdentifiers) {
-            value && removeIdentifiers(value)
-            key && removeIdentifiers(key)
-            index && removeIdentifiers(index)
           }
+
+          renderExp.arguments.push(
+            createFunctionExpression(
+              params,
+              childBlock,
+              true /* force newline */
+            )
+          )
         }
       } else {
         context.onError(
index 43aac0bcea075c86290076f8bf5ae65d93a01710..b3c6371b5dd4a6297441752be6f7f3930cfc27a8 100644 (file)
@@ -20,9 +20,7 @@ import {
   ObjectExpression,
   createObjectProperty,
   Property,
-  ExpressionNode,
-  TemplateChildNode,
-  FunctionExpression
+  ExpressionNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
@@ -168,17 +166,19 @@ function createChildrenCodegenNode(
   const needFragmentWrapper =
     children.length > 1 || child.type !== NodeTypes.ELEMENT
   if (needFragmentWrapper) {
-    let fragmentChildren: TemplateChildNode[] | FunctionExpression = children
+    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) {
-      fragmentChildren = (child.codegenNode.expressions[1] as CallExpression)
-        .arguments[2] as FunctionExpression
+      const forBlockExp = child.codegenNode
+      // directly use the for block's children and patchFlag
+      blockArgs[2] = forBlockExp.arguments[2]
+      blockArgs[3] = forBlockExp.arguments[3]
     }
-    return createCallExpression(helper(CREATE_BLOCK), [
-      helper(FRAGMENT),
-      keyExp,
-      fragmentChildren
-    ])
+    return createCallExpression(helper(CREATE_BLOCK), blockArgs)
   } else {
     const childCodegen = (child as ElementNode).codegenNode!
     let vnodeCall = childCodegen
index f0c95c73de1d4149077ac4999671a768c57b0340..afd086f0a099a16ddada68de3fab0265b7318998 100644 (file)
@@ -1,4 +1,4 @@
-import { SourceLocation, Position } from './ast'
+import { SourceLocation, Position, ElementNode, NodeTypes } from './ast'
 import { parseScript } from 'meriyah'
 import { walk } from 'estree-walker'
 
@@ -94,3 +94,25 @@ export function assert(condition: boolean, msg?: string) {
     throw new Error(msg || `unexpected compiler condition`)
   }
 }
+
+export function findProp(
+  props: ElementNode['props'],
+  name: string
+): ElementNode['props'][0] | undefined {
+  for (let i = 0; i < props.length; i++) {
+    const p = props[i]
+    if (p.type === NodeTypes.ATTRIBUTE) {
+      if (p.name === name && p.value && !p.value.isEmpty) {
+        return p
+      }
+    } else if (
+      p.arg &&
+      p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
+      p.arg.isStatic &&
+      p.arg.content === name &&
+      p.exp
+    ) {
+      return p
+    }
+  }
+}
index b2b6be6d7060f116a25a92e042c9c22943a0795f..945a8b8e07fa82cbe6bacc5bfc59e8c57e4b9cab 100644 (file)
@@ -1230,7 +1230,7 @@ export function createRenderer<
     // fast path
     const { patchFlag, shapeFlag } = n2
     if (patchFlag) {
-      if (patchFlag & PatchFlags.KEYED_V_FOR) {
+      if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
         // this could be either fully-keyed or mixed (some keyed some not)
         // presence of patchFlag means children are guaranteed to be arrays
         patchKeyedChildren(
@@ -1244,7 +1244,7 @@ export function createRenderer<
           optimized
         )
         return
-      } else if (patchFlag & PatchFlags.UNKEYED_V_FOR) {
+      } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
         // unkeyed
         patchUnkeyedChildren(
           c1 as HostVNode[],
index 8c98cf2c9b7f1c4c10d7e8bebc7058f809be9a34..74c8bf864105553f7b58c014f3e9ad5da45f2c8c 100644 (file)
@@ -47,11 +47,11 @@ export const enum PatchFlags {
   // value.
   NEED_PATCH = 1 << 5,
 
-  // Indicates a v-for fragment with keyed or partially keyed children
-  KEYED_V_FOR = 1 << 6,
+  // Indicates a fragment with keyed or partially keyed children
+  KEYED_FRAGMENT = 1 << 6,
 
-  // Indicates a v-for fragment with unkeyed children.
-  UNKEYED_V_FOR = 1 << 7,
+  // Indicates a fragment with unkeyed children.
+  UNKEYED_FRAGMENT = 1 << 7,
 
   // Indicates a component with dynamic slots (e.g. slot that references a v-for
   // iterated value, or dynamic slot names).
@@ -67,8 +67,8 @@ export const PublicPatchFlags = {
   PROPS: PatchFlags.PROPS,
   NEED_PATCH: PatchFlags.NEED_PATCH,
   FULL_PROPS: PatchFlags.FULL_PROPS,
-  KEYED_V_FOR: PatchFlags.KEYED_V_FOR,
-  UNKEYED_V_FOR: PatchFlags.UNKEYED_V_FOR,
+  KEYED_FRAGMENT: PatchFlags.KEYED_FRAGMENT,
+  UNKEYED_FRAGMENT: PatchFlags.UNKEYED_FRAGMENT,
   DYNAMIC_SLOTS: PatchFlags.DYNAMIC_SLOTS
 }
 
@@ -80,7 +80,7 @@ export const PatchFlagNames = {
   [PatchFlags.PROPS]: `PROPS`,
   [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
   [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
-  [PatchFlags.KEYED_V_FOR]: `KEYED_V_FOR`,
-  [PatchFlags.UNKEYED_V_FOR]: `UNKEYED_V_FOR`,
+  [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
+  [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
   [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`
 }