]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-core): more hoisting optimizations (#276)
authorHcySunYang <HcySunYang@outlook.com>
Tue, 15 Oct 2019 15:41:24 +0000 (23:41 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 15 Oct 2019 15:41:24 +0000 (11:41 -0400)
packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
packages/compiler-core/__tests__/utils.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-dom/__tests__/parse.spec.ts

index 2b733c3f5a7459afd379144452bd8d4432cac277..6240288477835d84a6fe9cbdfef1d64a272c6bb2 100644 (file)
@@ -3986,6 +3986,7 @@ Object {
         Object {
           "content": Object {
             "content": "a < b",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -7151,6 +7152,7 @@ Object {
         Object {
           "content": Object {
             "content": "'</div>'",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -7320,6 +7322,7 @@ Object {
         Object {
           "arg": Object {
             "content": "se",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -7640,6 +7643,7 @@ Object {
     Object {
       "content": Object {
         "content": "",
+        "isConstant": false,
         "isStatic": false,
         "loc": Object {
           "end": Object {
@@ -7798,6 +7802,7 @@ Object {
         Object {
           "arg": Object {
             "content": "class",
+            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -7816,6 +7821,7 @@ Object {
           },
           "exp": Object {
             "content": "{ some: condition }",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -7876,6 +7882,7 @@ Object {
         Object {
           "arg": Object {
             "content": "style",
+            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -7894,6 +7901,7 @@ Object {
           },
           "exp": Object {
             "content": "{ color: 'red' }",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -7983,6 +7991,7 @@ Object {
             Object {
               "arg": Object {
                 "content": "style",
+                "isConstant": true,
                 "isStatic": true,
                 "loc": Object {
                   "end": Object {
@@ -8001,6 +8010,7 @@ Object {
               },
               "exp": Object {
                 "content": "{ color: 'red' }",
+                "isConstant": false,
                 "isStatic": false,
                 "loc": Object {
                   "end": Object {
@@ -8080,6 +8090,7 @@ Object {
         Object {
           "arg": Object {
             "content": "class",
+            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -8098,6 +8109,7 @@ Object {
           },
           "exp": Object {
             "content": "{ some: condition }",
+            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
index e4dcf2b2b549b23494979fd12c53f03492ab05b2..3e0ccef7986bf8b85990c71e6df6d0fdfce3cb84 100644 (file)
@@ -298,6 +298,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `message`,
           isStatic: false,
+          isConstant: false,
           loc: {
             start: { offset: 2, line: 1, column: 3 },
             end: { offset: 9, line: 1, column: 10 },
@@ -322,6 +323,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a<b`,
           isStatic: false,
+          isConstant: false,
           loc: {
             start: { offset: 3, line: 1, column: 4 },
             end: { offset: 6, line: 1, column: 7 },
@@ -347,6 +349,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a<b`,
           isStatic: false,
+          isConstant: false,
           loc: {
             start: { offset: 3, line: 1, column: 4 },
             end: { offset: 6, line: 1, column: 7 },
@@ -365,6 +368,7 @@ describe('compiler: parse', () => {
         content: {
           type: NodeTypes.SIMPLE_EXPRESSION,
           isStatic: false,
+          isConstant: false,
           content: 'c>d',
           loc: {
             start: { offset: 12, line: 1, column: 13 },
@@ -390,6 +394,8 @@ describe('compiler: parse', () => {
         content: {
           type: NodeTypes.SIMPLE_EXPRESSION,
           isStatic: false,
+          // The `isConstant` is the default value and will be determined in `transformExpression`.
+          isConstant: false,
           content: '"</div>"',
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -974,6 +980,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: false,
+          isConstant: false,
           loc: {
             start: { offset: 11, line: 1, column: 12 },
             end: { offset: 12, line: 1, column: 13 },
@@ -999,6 +1006,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'click',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'click',
@@ -1071,6 +1079,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'click',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'click',
@@ -1107,6 +1116,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'a',
@@ -1127,6 +1137,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
+          isConstant: false,
 
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -1153,6 +1164,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'a',
@@ -1173,6 +1185,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
+          isConstant: false,
 
           loc: {
             start: { offset: 13, line: 1, column: 14 },
@@ -1199,6 +1212,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'a',
@@ -1219,6 +1233,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
+          isConstant: false,
 
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -1245,6 +1260,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
+          isConstant: true,
 
           loc: {
             source: 'a',
@@ -1265,6 +1281,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
+          isConstant: false,
 
           loc: {
             start: { offset: 14, line: 1, column: 15 },
@@ -1291,6 +1308,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
+          isConstant: true,
           loc: {
             source: 'a',
             start: {
@@ -1310,6 +1328,8 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: '{ b }',
           isStatic: false,
+          // The `isConstant` is the default value and will be determined in transformExpression
+          isConstant: false,
           loc: {
             start: { offset: 10, line: 1, column: 11 },
             end: { offset: 15, line: 1, column: 16 },
index bebf5aada1cfab5729a93951b73f170eb551b865..dba4bf1f095287845076cdb6c52fee6e16efdc7e 100644 (file)
@@ -152,6 +152,93 @@ return function render() {
 }"
 `;
 
+exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
+"const _Vue = Vue
+const _createVNode = Vue.createVNode
+
+const _hoisted_1 = { class: { foo: true }}
+
+return function render() {
+  with (this) {
+    const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+    
+    return (_openBlock(), _createBlock(\\"div\\", null, [
+      _createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */)
+    ]))
+  }
+}"
+`;
+
+exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
+"const _Vue = Vue
+const _createVNode = Vue.createVNode
+
+const _hoisted_1 = _createVNode(\\"span\\", null, [\\"foo \\", _toString(1), _toString(2)])
+
+return function render() {
+  with (this) {
+    const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+    
+    return (_openBlock(), _createBlock(\\"div\\", null, [
+      _hoisted_1
+    ]))
+  }
+}"
+`;
+
+exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
+"const _Vue = Vue
+const _createVNode = Vue.createVNode
+
+const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1), 1 /* TEXT */)
+
+return function render() {
+  with (this) {
+    const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+    
+    return (_openBlock(), _createBlock(\\"div\\", null, [
+      _hoisted_1
+    ]))
+  }
+}"
+`;
+
+exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable (2) 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
+    
+    return (_openBlock(), _createBlock(\\"div\\", null, [
+      (_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
+        return (_openBlock(), _createBlock(\\"p\\", null, [
+          _createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
+        ]))
+      }), 128 /* UNKEYED_FRAGMENT */))
+    ]))
+  }
+}"
+`;
+
+exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
+    
+    return (_openBlock(), _createBlock(\\"div\\", null, [
+      (_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
+        return (_openBlock(), _createBlock(\\"p\\", null, [
+          _createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
+        ]))
+      }), 128 /* UNKEYED_FRAGMENT */))
+    ]))
+  }
+}"
+`;
+
 exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
 "const _Vue = Vue
 
index 60ae861bf5835a16fa821fb074f8836c384a2411..d998aa26cf69203e527d209c9f64c2dd08871d42 100644 (file)
@@ -1,4 +1,10 @@
-import { parse, transform, NodeTypes, generate } from '../../src'
+import {
+  parse,
+  transform,
+  NodeTypes,
+  generate,
+  CompilerOptions
+} from '../../src'
 import {
   OPEN_BLOCK,
   CREATE_BLOCK,
@@ -8,17 +14,24 @@ import {
   RENDER_LIST
 } from '../../src/runtimeHelpers'
 import { transformElement } from '../../src/transforms/transformElement'
+import { transformExpression } from '../../src/transforms/transformExpression'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformFor } from '../../src/transforms/vFor'
 import { transformBind } from '../../src/transforms/vBind'
 import { createObjectMatcher, genFlagText } from '../testUtils'
 import { PatchFlags } from '@vue/shared'
 
-function transformWithHoist(template: string) {
+function transformWithHoist(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
   transform(ast, {
     hoistStatic: true,
-    nodeTransforms: [transformIf, transformFor, transformElement],
+    prefixIdentifiers: options.prefixIdentifiers,
+    nodeTransforms: [
+      transformIf,
+      transformFor,
+      ...(options.prefixIdentifiers ? [transformExpression] : []),
+      transformElement
+    ],
     directiveTransforms: {
       bind: transformBind
     }
@@ -429,4 +442,186 @@ describe('compiler: hoistStatic transform', () => {
     })
     expect(generate(root).code).toMatchSnapshot()
   })
+
+  describe('prefixIdentifiers', () => {
+    test('hoist nested static tree with static interpolation', () => {
+      const { root, args } = transformWithHoist(
+        `<div><span>foo {{ 1 }} {{ 2 }}</span></div>`,
+        {
+          prefixIdentifiers: true
+        }
+      )
+      expect(root.hoists).toMatchObject([
+        {
+          type: NodeTypes.JS_CALL_EXPRESSION,
+          callee: CREATE_VNODE,
+          arguments: [
+            `"span"`,
+            `null`,
+            [
+              {
+                type: NodeTypes.TEXT,
+                content: `foo `
+              },
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `1`,
+                  isStatic: false,
+                  isConstant: true
+                }
+              },
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `2`,
+                  isStatic: false,
+                  isConstant: true
+                }
+              }
+            ]
+          ]
+        }
+      ])
+      expect(args).toMatchObject([
+        `"div"`,
+        `null`,
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            codegenNode: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `_hoisted_1`
+            }
+          }
+        ]
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('hoist nested static tree with static prop value', () => {
+      const { root, args } = transformWithHoist(
+        `<div><span :foo="0">{{ 1 }}</span></div>`,
+        {
+          prefixIdentifiers: true
+        }
+      )
+
+      expect(root.hoists).toMatchObject([
+        {
+          type: NodeTypes.JS_CALL_EXPRESSION,
+          callee: CREATE_VNODE,
+          arguments: [
+            `"span"`,
+            createObjectMatcher({ foo: `[0]` }),
+            {
+              type: NodeTypes.INTERPOLATION,
+              content: {
+                content: `1`,
+                isStatic: false,
+                isConstant: true
+              }
+            },
+            '1 /* TEXT */'
+          ]
+        }
+      ])
+      expect(args).toMatchObject([
+        `"div"`,
+        `null`,
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            codegenNode: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `_hoisted_1`
+            }
+          }
+        ]
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('hoist class with static object value', () => {
+      const { root, args } = transformWithHoist(
+        `<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
+        {
+          prefixIdentifiers: true
+        }
+      )
+
+      expect(root.hoists).toMatchObject([
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          properties: [
+            {
+              key: {
+                content: `class`,
+                isConstant: true,
+                isStatic: true
+              },
+              value: {
+                content: `{ foo: true }`,
+                isConstant: true,
+                isStatic: false
+              }
+            }
+          ]
+        }
+      ])
+      expect(args).toMatchObject([
+        `"div"`,
+        `null`,
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            codegenNode: {
+              callee: CREATE_VNODE,
+              arguments: [
+                `"span"`,
+                {
+                  type: NodeTypes.SIMPLE_EXPRESSION,
+                  content: `_hoisted_1`
+                },
+                {
+                  type: NodeTypes.INTERPOLATION,
+                  content: {
+                    content: `_ctx.bar`,
+                    isConstant: false,
+                    isStatic: false
+                  }
+                },
+                `1 /* TEXT */`
+              ]
+            }
+          }
+        ]
+      ])
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('should NOT hoist expressions that with scope variable', () => {
+      const { root } = transformWithHoist(
+        `<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
+        {
+          prefixIdentifiers: true
+        }
+      )
+
+      expect(root.hoists.length).toBe(0)
+      expect(generate(root).code).toMatchSnapshot()
+    })
+
+    test('should NOT hoist expressions that with scope variable (2)', () => {
+      const { root } = transformWithHoist(
+        `<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
+        {
+          prefixIdentifiers: true
+        }
+      )
+
+      expect(root.hoists.length).toBe(0)
+      expect(generate(root).code).toMatchSnapshot()
+    })
+  })
 })
index 417d0d185341374a3fa02986472b8093e866fdac..d93d321299b94dd704858e3251198a81cde43915 100644 (file)
@@ -79,6 +79,7 @@ describe('isEmptyExpression', () => {
         content: '',
         type: NodeTypes.SIMPLE_EXPRESSION,
         isStatic: true,
+        isConstant: true,
         loc: null as any
       })
     ).toBe(true)
@@ -90,6 +91,7 @@ describe('isEmptyExpression', () => {
         content: '  \t  ',
         type: NodeTypes.SIMPLE_EXPRESSION,
         isStatic: true,
+        isConstant: true,
         loc: null as any
       })
     ).toBe(true)
@@ -101,6 +103,7 @@ describe('isEmptyExpression', () => {
         content: 'foo',
         type: NodeTypes.SIMPLE_EXPRESSION,
         isStatic: true,
+        isConstant: true,
         loc: null as any
       })
     ).toBe(false)
index 559167659d21aaadaffc835f417c52b56bd7ec6e..0ca3e2c93d4ec4b0b189de6a8d0f7799940971dd 100644 (file)
@@ -172,6 +172,7 @@ export interface SimpleExpressionNode extends Node {
   type: NodeTypes.SIMPLE_EXPRESSION
   content: string
   isStatic: boolean
+  isConstant: boolean
   // an expression parsed as the params of a function will track
   // the identifiers declared inside the function body.
   identifiers?: string[]
@@ -501,11 +502,13 @@ export function createObjectProperty(
 export function createSimpleExpression(
   content: SimpleExpressionNode['content'],
   isStatic: SimpleExpressionNode['isStatic'],
-  loc: SourceLocation = locStub
+  loc: SourceLocation = locStub,
+  isConstant: boolean = false
 ): SimpleExpressionNode {
   return {
     type: NodeTypes.SIMPLE_EXPRESSION,
     loc,
+    isConstant,
     content,
     isStatic
   }
index 323d46e0806fc4c6f7afdf8717fda50dc0cb4442..c369d45644d29e2d617ea3c500ceacb11b4e480e 100644 (file)
@@ -564,9 +564,12 @@ function parseAttribute(
       )
       let content = match[2]
       let isStatic = true
+      // Non-dynamic arg is a constant.
+      let isConstant = true
 
       if (content.startsWith('[')) {
         isStatic = false
+        isConstant = false
 
         if (!content.endsWith(']')) {
           emitError(
@@ -582,6 +585,7 @@ function parseAttribute(
         type: NodeTypes.SIMPLE_EXPRESSION,
         content,
         isStatic,
+        isConstant,
         loc
       }
     }
@@ -607,6 +611,8 @@ function parseAttribute(
         type: NodeTypes.SIMPLE_EXPRESSION,
         content: value.content,
         isStatic: false,
+        // Set `isConstant` to false by default and will decide in transformExpression
+        isConstant: false,
         loc: value.loc
       },
       arg,
@@ -713,6 +719,8 @@ function parseInterpolation(
     content: {
       type: NodeTypes.SIMPLE_EXPRESSION,
       isStatic: false,
+      // Set `isConstant` to false by default and will decide in transformExpression
+      isConstant: false,
       content,
       loc: getSelection(context, innerStart, innerEnd)
     },
index e74a2e7de34c4afce619f08dbb0e8e1660b0ee6f..5743d68136cdeae7e81bf44181624705fda1be56 100644 (file)
@@ -2,6 +2,7 @@ import {
   RootNode,
   NodeTypes,
   TemplateChildNode,
+  SimpleExpressionNode,
   ElementTypes,
   ElementCodegenNode,
   PlainElementNode,
@@ -11,7 +12,7 @@ import {
 } from '../ast'
 import { TransformContext } from '../transform'
 import { APPLY_DIRECTIVES } from '../runtimeHelpers'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlags, isString, isSymbol } from '@vue/shared'
 import { isSlotOutlet, findProp } from '../utils'
 
 function hasDynamicKey(node: ElementNode) {
@@ -107,7 +108,7 @@ function getPatchFlag(node: PlainElementNode): number | undefined {
 }
 
 function isStaticNode(
-  node: TemplateChildNode,
+  node: TemplateChildNode | SimpleExpressionNode,
   resultCache: Map<TemplateChildNode, boolean>
 ): boolean {
   switch (node.type) {
@@ -119,7 +120,7 @@ function isStaticNode(
         return resultCache.get(node) as boolean
       }
       const flag = getPatchFlag(node)
-      if (!flag) {
+      if (!flag || flag === PatchFlags.TEXT) {
         // element self is static. check its children.
         for (let i = 0; i < node.children.length; i++) {
           if (!isStaticNode(node.children[i], resultCache)) {
@@ -137,9 +138,17 @@ function isStaticNode(
       return true
     case NodeTypes.IF:
     case NodeTypes.FOR:
+      return false
     case NodeTypes.INTERPOLATION:
+      return isStaticNode(node.content, resultCache)
+    case NodeTypes.SIMPLE_EXPRESSION:
+      return node.isConstant
     case NodeTypes.COMPOUND_EXPRESSION:
-      return false
+      return node.children.every(child => {
+        return (
+          isString(child) || isSymbol(child) || isStaticNode(child, resultCache)
+        )
+      })
     default:
       if (__DEV__) {
         const exhaustiveCheck: never = node
index dd0f2d0e07be1c37267dcfd5f2ba848b568fc952..d54f2be39da5e4a066081decbea77af4ee86e295 100644 (file)
@@ -190,7 +190,12 @@ export function buildProps(
 
   const analyzePatchFlag = ({ key, value }: Property) => {
     if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
-      if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {
+      if (
+        value.type !== NodeTypes.SIMPLE_EXPRESSION ||
+        // E.g: <p :foo="1 + 2" />.
+        // Do not add prop `foo` to `dynamicPropNames`.
+        (!value.isStatic && !value.isConstant)
+      ) {
         const name = key.content
         if (name === 'ref') {
           hasRef = true
index e3c2c028b80d52f4b847e33c107058057367d4d6..083b3023cc7725e5ee9543ca2834821ac5ceee05 100644 (file)
@@ -61,6 +61,7 @@ export const transformExpression: NodeTransform = (node, context) => {
 
 interface PrefixMeta {
   prefix?: string
+  isConstant: boolean
   start: number
   end: number
   scopeIds?: Set<string>
@@ -108,6 +109,7 @@ export function processExpression(
   const ids: (Identifier & PrefixMeta)[] = []
   const knownIds = Object.create(context.identifiers)
 
+  let isConstant = true
   // walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
   walkJS(ast, {
     enter(node: Node & PrefixMeta, parent) {
@@ -120,8 +122,15 @@ export function processExpression(
               node.prefix = `${node.name}: `
             }
             node.name = `_ctx.${node.name}`
+            node.isConstant = false
+            isConstant = false
             ids.push(node)
           } else if (!isStaticPropertyKey(node, parent)) {
+            // This means this identifier is pointing to a scope variable (a v-for alias, or a v-slot prop)
+            // which is also dynamic and cannot be hoisted.
+            node.isConstant = !(
+              knownIds[node.name] && shouldPrefix(node, parent)
+            )
             // also generate sub-expressions for other identifiers for better
             // source map support. (except for property keys which are static)
             ids.push(node)
@@ -190,11 +199,16 @@ export function processExpression(
     }
     const source = rawExp.slice(start, end)
     children.push(
-      createSimpleExpression(id.name, false, {
-        source,
-        start: advancePositionWithClone(node.loc.start, source, start),
-        end: advancePositionWithClone(node.loc.start, source, end)
-      })
+      createSimpleExpression(
+        id.name,
+        false,
+        {
+          source,
+          start: advancePositionWithClone(node.loc.start, source, start),
+          end: advancePositionWithClone(node.loc.start, source, end)
+        },
+        id.isConstant /* isConstant */
+      )
     )
     if (i === ids.length - 1 && end < rawExp.length) {
       children.push(rawExp.slice(end))
@@ -206,6 +220,7 @@ export function processExpression(
     ret = createCompoundExpression(children, node.loc)
   } else {
     ret = node
+    ret.isConstant = isConstant
   }
   ret.identifiers = Object.keys(knownIds)
   return ret
index 752d08c4ff54cdbd6b075fc8b0860ee3e075c575..6a5b22b4acc55a6cc4248d7a12054aa2d0a7f96c 100644 (file)
@@ -117,6 +117,7 @@ describe('DOM parser', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a < b`,
           isStatic: false,
+          isConstant: false,
           loc: {
             start: { offset: 8, line: 1, column: 9 },
             end: { offset: 16, line: 1, column: 17 },