]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler): better constant hoist/stringify checks
authorEvan You <yyx990803@gmail.com>
Sat, 21 Nov 2020 00:26:07 +0000 (19:26 -0500)
committerEvan You <yyx990803@gmail.com>
Sat, 21 Nov 2020 00:26:07 +0000 (19:26 -0500)
22 files changed:
packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/transformText.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vModel.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-dom/__tests__/parse.spec.ts
packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
packages/compiler-dom/src/transforms/transformStyle.ts
packages/compiler-sfc/src/templateTransformAssetUrl.ts
packages/compiler-sfc/src/templateTransformSrcset.ts

index a645e631d60e3a3110170f3c77a07c4181654514..d14631367cb3977cab7809c26435196c6c7e4b98 100644 (file)
@@ -3322,8 +3322,8 @@ Object {
       "children": Array [
         Object {
           "content": Object {
+            "constType": 0,
             "content": "a < b",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -6084,8 +6084,8 @@ Object {
       "children": Array [
         Object {
           "content": Object {
+            "constType": 0,
             "content": "'</div>'",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -6259,8 +6259,8 @@ Object {
       "props": Array [
         Object {
           "arg": Object {
+            "constType": 0,
             "content": "se",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -6593,8 +6593,8 @@ Object {
   "children": Array [
     Object {
       "content": Object {
+        "constType": 0,
         "content": "",
-        "isConstant": false,
         "isStatic": false,
         "loc": Object {
           "end": Object {
@@ -6758,8 +6758,8 @@ Object {
       "props": Array [
         Object {
           "arg": Object {
+            "constType": 3,
             "content": "class",
-            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -6777,8 +6777,8 @@ Object {
             "type": 4,
           },
           "exp": Object {
+            "constType": 0,
             "content": "{ some: condition }",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -6838,8 +6838,8 @@ Object {
       "props": Array [
         Object {
           "arg": Object {
+            "constType": 3,
             "content": "style",
-            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -6857,8 +6857,8 @@ Object {
             "type": 4,
           },
           "exp": Object {
+            "constType": 0,
             "content": "{ color: 'red' }",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
@@ -6950,8 +6950,8 @@ Object {
           "props": Array [
             Object {
               "arg": Object {
+                "constType": 3,
                 "content": "style",
-                "isConstant": true,
                 "isStatic": true,
                 "loc": Object {
                   "end": Object {
@@ -6969,8 +6969,8 @@ Object {
                 "type": 4,
               },
               "exp": Object {
+                "constType": 0,
                 "content": "{ color: 'red' }",
-                "isConstant": false,
                 "isStatic": false,
                 "loc": Object {
                   "end": Object {
@@ -7049,8 +7049,8 @@ Object {
       "props": Array [
         Object {
           "arg": Object {
+            "constType": 3,
             "content": "class",
-            "isConstant": true,
             "isStatic": true,
             "loc": Object {
               "end": Object {
@@ -7068,8 +7068,8 @@ Object {
             "type": 4,
           },
           "exp": Object {
+            "constType": 0,
             "content": "{ some: condition }",
-            "isConstant": false,
             "isStatic": false,
             "loc": Object {
               "end": Object {
index f4a11561cc6ee16ad847b762f9149a8d58e0ea95..809c209f80be0a449c9b9e43f9df003fcfbe1bb0 100644 (file)
@@ -20,7 +20,8 @@ import {
   IfConditionalExpression,
   createVNodeCall,
   VNodeCall,
-  DirectiveArguments
+  DirectiveArguments,
+  ConstantTypes
 } from '../src'
 import {
   CREATE_VNODE,
@@ -304,7 +305,12 @@ describe('compiler: codegen', () => {
         codegenNode: {
           type: NodeTypes.FOR,
           loc: locStub,
-          source: createSimpleExpression('1 + 2', false, locStub, true),
+          source: createSimpleExpression(
+            '1 + 2',
+            false,
+            locStub,
+            ConstantTypes.CAN_STRINGIFY
+          ),
           valueAlias: undefined,
           keyAlias: undefined,
           objectIndexAlias: undefined,
index 3fdb3d950ad539693750d7efc168b3717d245e57..a93b5e0404c85cd7f14cc47e1a01f97fd241e7b2 100644 (file)
@@ -9,7 +9,8 @@ import {
   NodeTypes,
   Position,
   TextNode,
-  InterpolationNode
+  InterpolationNode,
+  ConstantTypes
 } from '../src/ast'
 
 describe('compiler: parse', () => {
@@ -177,7 +178,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `message`,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 2, line: 1, column: 3 },
             end: { offset: 9, line: 1, column: 10 },
@@ -202,7 +203,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a<b`,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 3, line: 1, column: 4 },
             end: { offset: 6, line: 1, column: 7 },
@@ -228,7 +229,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a<b`,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 3, line: 1, column: 4 },
             end: { offset: 6, line: 1, column: 7 },
@@ -247,7 +248,7 @@ describe('compiler: parse', () => {
         content: {
           type: NodeTypes.SIMPLE_EXPRESSION,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           content: 'c>d',
           loc: {
             start: { offset: 12, line: 1, column: 13 },
@@ -273,8 +274,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,
+          // The `constType` is the default value and will be determined in `transformExpression`.
+          constType: ConstantTypes.NOT_CONSTANT,
           content: '"</div>"',
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -303,7 +304,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `msg`,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 4, line: 1, column: 5 },
             end: { offset: 7, line: 1, column: 8 },
@@ -1028,7 +1029,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 11, line: 1, column: 12 },
             end: { offset: 12, line: 1, column: 13 },
@@ -1054,7 +1055,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'click',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'click',
@@ -1091,7 +1092,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'event',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             source: '[event]',
@@ -1164,7 +1165,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'click',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'click',
@@ -1201,7 +1202,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a.b',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             source: '[a.b]',
@@ -1238,7 +1239,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'a',
@@ -1259,7 +1260,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -1286,7 +1287,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'a',
@@ -1307,7 +1308,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             start: { offset: 13, line: 1, column: 14 },
@@ -1334,7 +1335,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'a',
@@ -1355,7 +1356,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             start: { offset: 8, line: 1, column: 9 },
@@ -1382,7 +1383,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
 
           loc: {
             source: 'a',
@@ -1403,7 +1404,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'b',
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
 
           loc: {
             start: { offset: 14, line: 1, column: 15 },
@@ -1430,7 +1431,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'a',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
           loc: {
             source: 'a',
             start: {
@@ -1450,8 +1451,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,
+          // The `constType` is the default value and will be determined in transformExpression
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 10, line: 1, column: 11 },
             end: { offset: 15, line: 1, column: 16 },
@@ -1478,7 +1479,7 @@ describe('compiler: parse', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: 'foo.bar',
           isStatic: true,
-          isConstant: true,
+          constType: ConstantTypes.CAN_STRINGIFY,
           loc: {
             source: 'foo.bar',
             start: {
index c13f1c48f0ce127111878d1f2ba87f0cf86f8521..37e916284799cc241067bdf3268932a8c40049a9 100644 (file)
@@ -7,7 +7,8 @@ import {
   VNodeCall,
   IfNode,
   ElementNode,
-  ForNode
+  ForNode,
+  ConstantTypes
 } from '../../src'
 import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers'
 import { transformElement } from '../../src/transforms/transformElement'
@@ -469,7 +470,7 @@ describe('compiler: hoistStatic transform', () => {
             content: {
               content: `1`,
               isStatic: false,
-              isConstant: true
+              constType: ConstantTypes.CAN_STRINGIFY
             }
           }
         }
@@ -505,13 +506,13 @@ describe('compiler: hoistStatic transform', () => {
             {
               key: {
                 content: `class`,
-                isConstant: true,
-                isStatic: true
+                isStatic: true,
+                constType: ConstantTypes.CAN_STRINGIFY
               },
               value: {
                 content: `{ foo: true }`,
-                isConstant: true,
-                isStatic: false
+                isStatic: false,
+                constType: ConstantTypes.CAN_STRINGIFY
               }
             }
           ]
@@ -534,8 +535,8 @@ describe('compiler: hoistStatic transform', () => {
                 type: NodeTypes.INTERPOLATION,
                 content: {
                   content: `_ctx.bar`,
-                  isConstant: false,
-                  isStatic: false
+                  isStatic: false,
+                  constType: ConstantTypes.NOT_CONSTANT
                 }
               },
               patchFlag: `1 /* TEXT */`
index 9bddda9e792bcc211dde754ab7fe574e03058524..e3d7e5058e9d3c254ce9222f0b611c5866385d95 100644 (file)
@@ -5,7 +5,8 @@ import {
   DirectiveNode,
   NodeTypes,
   CompilerOptions,
-  InterpolationNode
+  InterpolationNode,
+  ConstantTypes
 } from '../../src'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformExpression } from '../../src/transforms/transformExpression'
@@ -408,7 +409,7 @@ describe('compiler: expression transform', () => {
         type: NodeTypes.SIMPLE_EXPRESSION,
         content: `13000n`,
         isStatic: false,
-        isConstant: true
+        constType: ConstantTypes.CAN_STRINGIFY
       })
     })
 
index 411aba2c4610a70de3ef1a749389409d60b12a15..50d8d625d6557788da91f3d549095f7ad176970a 100644 (file)
@@ -12,7 +12,8 @@ import {
   SimpleExpressionNode,
   ElementNode,
   InterpolationNode,
-  ForCodegenNode
+  ForCodegenNode,
+  ConstantTypes
 } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
 import { CompilerOptions, generate } from '../../src'
@@ -760,7 +761,7 @@ describe('compiler: v-for', () => {
           false /* disableTracking */
         )
       ).toMatchObject({
-        source: { content: `10`, isConstant: true },
+        source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY },
         params: [{ content: `item` }],
         innerVNodeCall: {
           tag: `"p"`,
@@ -772,7 +773,7 @@ describe('compiler: v-for', () => {
               type: NodeTypes.SIMPLE_EXPRESSION,
               content: 'item',
               isStatic: false,
-              isConstant: false
+              constType: ConstantTypes.NOT_CONSTANT
             }
           },
           patchFlag: genFlagText(PatchFlags.TEXT)
index 35849833c66bbb40a87aaf030a33f4dd69c82897..c1eaf80cbb7c2abc1e1b74f6bea822a8e6676ff0 100644 (file)
@@ -189,11 +189,23 @@ export interface DirectiveNode extends Node {
   parseResult?: ForParseResult
 }
 
+/**
+ * Static types have several levels.
+ * Higher levels implies lower levels. e.g. a node that can be stringified
+ * can always be hoisted and skipped for patch.
+ */
+export const enum ConstantTypes {
+  NOT_CONSTANT = 0,
+  CAN_SKIP_PATCH,
+  CAN_HOIST,
+  CAN_STRINGIFY
+}
+
 export interface SimpleExpressionNode extends Node {
   type: NodeTypes.SIMPLE_EXPRESSION
   content: string
   isStatic: boolean
-  isConstant: boolean
+  constType: ConstantTypes
   /**
    * Indicates this is an identifier for a hoist vnode call and points to the
    * hoisted node.
@@ -204,11 +216,6 @@ export interface SimpleExpressionNode extends Node {
    * the identifiers declared inside the function body.
    */
   identifiers?: string[]
-  /**
-   * some expressions (e.g. transformAssetUrls import identifiers) are constant,
-   * but cannot be stringified because they must be first evaluated at runtime.
-   */
-  isRuntimeConstant?: boolean
 }
 
 export interface InterpolationNode extends Node {
@@ -611,14 +618,14 @@ export function createSimpleExpression(
   content: SimpleExpressionNode['content'],
   isStatic: SimpleExpressionNode['isStatic'],
   loc: SourceLocation = locStub,
-  isConstant: boolean = false
+  constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
 ): SimpleExpressionNode {
   return {
     type: NodeTypes.SIMPLE_EXPRESSION,
     loc,
-    isConstant,
     content,
-    isStatic
+    isStatic,
+    constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
   }
 }
 
index 2d66e84ae5bb1f4d87b1751f8aaf5e876a8b6679..63cb285f2477105301303d28229beca2ed5e3a42 100644 (file)
@@ -22,7 +22,8 @@ import {
   TextNode,
   TemplateChildNode,
   InterpolationNode,
-  createRoot
+  createRoot,
+  ConstantTypes
 } from './ast'
 
 type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
@@ -656,7 +657,9 @@ function parseAttribute(
         type: NodeTypes.SIMPLE_EXPRESSION,
         content,
         isStatic,
-        isConstant: isStatic,
+        constType: isStatic
+          ? ConstantTypes.CAN_STRINGIFY
+          : ConstantTypes.NOT_CONSTANT,
         loc
       }
     }
@@ -677,8 +680,8 @@ function parseAttribute(
         content: value.content,
         isStatic: false,
         // Treat as non-constant by default. This can be potentially set to
-        // true by `transformExpression` to make it eligible for hoisting.
-        isConstant: false,
+        // other values by `transformExpression` to make it eligible for hoisting.
+        constType: ConstantTypes.NOT_CONSTANT,
         loc: value.loc
       },
       arg,
@@ -785,7 +788,7 @@ function parseInterpolation(
       type: NodeTypes.SIMPLE_EXPRESSION,
       isStatic: false,
       // Set `isConstant` to false by default and will decide in transformExpression
-      isConstant: false,
+      constType: ConstantTypes.NOT_CONSTANT,
       content,
       loc: getSelection(context, innerStart, innerEnd)
     },
index eddd03caa14217492ac47da43acb1b1bf93676c2..0db0e7a0bca52e19d7b32da5b1d7aaf61653d6e9 100644 (file)
@@ -15,7 +15,8 @@ import {
   CacheExpression,
   createCacheExpression,
   TemplateLiteral,
-  createVNodeCall
+  createVNodeCall,
+  ConstantTypes
 } from './ast'
 import {
   isString,
@@ -245,7 +246,7 @@ export function createTransformContext(
         `_hoisted_${context.hoists.length}`,
         false,
         exp.loc,
-        true
+        ConstantTypes.CAN_HOIST
       )
       identifier.hoisted = exp
       return identifier
index a4131c6d6f6b2e75e64dfc752531b2ed43b36fa1..1c066d7b8ebeb1d3bb66c0d0cf6c95886426ab0e 100644 (file)
@@ -1,4 +1,5 @@
 import {
+  ConstantTypes,
   RootNode,
   NodeTypes,
   TemplateChildNode,
@@ -37,16 +38,10 @@ export function isSingleElementRoot(
   )
 }
 
-const enum StaticType {
-  NOT_STATIC = 0,
-  FULL_STATIC,
-  HAS_RUNTIME_CONSTANT
-}
-
 function walk(
   node: ParentNode,
   context: TransformContext,
-  resultCache: Map<TemplateChildNode, StaticType>,
+  resultCache: Map<TemplateChildNode, ConstantTypes>,
   doNotHoistNode: boolean = false
 ) {
   let hasHoistedNode = false
@@ -58,7 +53,7 @@ function walk(
   // @vue/compiler-dom), but doing it here allows us to perform only one full
   // walk of the AST and allow `stringifyStatic` to stop walking as soon as its
   // stringficiation threshold is met.
-  let hasRuntimeConstant = false
+  let canStringify = true
 
   const { children } = node
   for (let i = 0; i < children.length; i++) {
@@ -68,20 +63,20 @@ function walk(
       child.type === NodeTypes.ELEMENT &&
       child.tagType === ElementTypes.ELEMENT
     ) {
-      let staticType
-      if (
-        !doNotHoistNode &&
-        (staticType = getStaticType(child, resultCache)) > 0
-      ) {
-        if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
-          hasRuntimeConstant = true
+      const constantType = doNotHoistNode
+        ? ConstantTypes.NOT_CONSTANT
+        : getConstantType(child, resultCache)
+      if (constantType > ConstantTypes.NOT_CONSTANT) {
+        if (constantType < ConstantTypes.CAN_STRINGIFY) {
+          canStringify = false
+        }
+        if (constantType >= ConstantTypes.CAN_HOIST) {
+          ;(child.codegenNode as VNodeCall).patchFlag =
+            PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
+          child.codegenNode = context.hoist(child.codegenNode!)
+          hasHoistedNode = true
+          continue
         }
-        // whole tree is static
-        ;(child.codegenNode as VNodeCall).patchFlag =
-          PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
-        child.codegenNode = context.hoist(child.codegenNode!)
-        hasHoistedNode = true
-        continue
       } else {
         // node may contain dynamic children, but its props may be eligible for
         // hoisting.
@@ -92,7 +87,8 @@ function walk(
             (!flag ||
               flag === PatchFlags.NEED_PATCH ||
               flag === PatchFlags.TEXT) &&
-            !hasNonHoistableProps(child)
+            getGeneratedPropsConstantType(child, resultCache) >=
+              ConstantTypes.CAN_HOIST
           ) {
             const props = getNodeProps(child)
             if (props) {
@@ -102,13 +98,15 @@ function walk(
         }
       }
     } else if (child.type === NodeTypes.TEXT_CALL) {
-      const staticType = getStaticType(child.content, resultCache)
-      if (staticType > 0) {
-        if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
-          hasRuntimeConstant = true
+      const contentType = getConstantType(child.content, resultCache)
+      if (contentType > 0) {
+        if (contentType < ConstantTypes.CAN_STRINGIFY) {
+          canStringify = false
+        }
+        if (contentType >= ConstantTypes.CAN_HOIST) {
+          child.codegenNode = context.hoist(child.codegenNode)
+          hasHoistedNode = true
         }
-        child.codegenNode = context.hoist(child.codegenNode)
-        hasHoistedNode = true
       }
     }
 
@@ -131,19 +129,19 @@ function walk(
     }
   }
 
-  if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
+  if (canStringify && hasHoistedNode && context.transformHoist) {
     context.transformHoist(children, context, node)
   }
 }
 
-export function getStaticType(
+export function getConstantType(
   node: TemplateChildNode | SimpleExpressionNode,
-  resultCache: Map<TemplateChildNode, StaticType> = new Map()
-): StaticType {
+  resultCache: Map<TemplateChildNode, ConstantTypes> = new Map()
+): ConstantTypes {
   switch (node.type) {
     case NodeTypes.ELEMENT:
       if (node.tagType !== ElementTypes.ELEMENT) {
-        return StaticType.NOT_STATIC
+        return ConstantTypes.NOT_CONSTANT
       }
       const cached = resultCache.get(node)
       if (cached !== undefined) {
@@ -151,40 +149,64 @@ export function getStaticType(
       }
       const codegenNode = node.codegenNode!
       if (codegenNode.type !== NodeTypes.VNODE_CALL) {
-        return StaticType.NOT_STATIC
+        return ConstantTypes.NOT_CONSTANT
       }
       const flag = getPatchFlag(codegenNode)
-      if (!flag && !hasNonHoistableProps(node)) {
-        // element self is static. check its children.
-        let returnType = StaticType.FULL_STATIC
+      if (!flag) {
+        let returnType = ConstantTypes.CAN_STRINGIFY
+
+        // Element itself has no patch flag. However we still need to check:
+
+        // 1. Even for a node with no patch flag, it is possible for it to contain
+        // non-hoistable expressions that refers to scope variables, e.g. compiler
+        // injected keys or cached event handlers. Therefore we need to always
+        // check the codegenNode's props to be sure.
+        const generatedPropsType = getGeneratedPropsConstantType(
+          node,
+          resultCache
+        )
+        if (generatedPropsType === ConstantTypes.NOT_CONSTANT) {
+          resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+          return ConstantTypes.NOT_CONSTANT
+        }
+        if (generatedPropsType < returnType) {
+          returnType = generatedPropsType
+        }
+
+        // 2. its children.
         for (let i = 0; i < node.children.length; i++) {
-          const childType = getStaticType(node.children[i], resultCache)
-          if (childType === StaticType.NOT_STATIC) {
-            resultCache.set(node, StaticType.NOT_STATIC)
-            return StaticType.NOT_STATIC
-          } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
-            returnType = StaticType.HAS_RUNTIME_CONSTANT
+          const childType = getConstantType(node.children[i], resultCache)
+          if (childType === ConstantTypes.NOT_CONSTANT) {
+            resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+            return ConstantTypes.NOT_CONSTANT
+          }
+          if (childType < returnType) {
+            returnType = childType
           }
         }
 
-        // check if any of the props contain runtime constants
-        if (returnType !== StaticType.HAS_RUNTIME_CONSTANT) {
+        // 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0
+        // type, check if any of the props can cause the type to be lowered
+        // we can skip can_patch because it's guaranteed by the absence of a
+        // patchFlag.
+        if (returnType > ConstantTypes.CAN_SKIP_PATCH) {
           for (let i = 0; i < node.props.length; i++) {
             const p = node.props[i]
-            if (
-              p.type === NodeTypes.DIRECTIVE &&
-              p.name === 'bind' &&
-              p.exp &&
-              (p.exp.type === NodeTypes.COMPOUND_EXPRESSION ||
-                p.exp.isRuntimeConstant)
-            ) {
-              returnType = StaticType.HAS_RUNTIME_CONSTANT
+            if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind' && p.exp) {
+              const expType = getConstantType(p.exp, resultCache)
+              if (expType === ConstantTypes.NOT_CONSTANT) {
+                resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+                return ConstantTypes.NOT_CONSTANT
+              }
+              if (expType < returnType) {
+                returnType = expType
+              }
             }
           }
         }
 
         // only svg/foreignObject could be block here, however if they are
-        // stati then they don't need to be blocks since there will be no
+        // static then they don't need to be blocks since there will be no
         // nested updates.
         if (codegenNode.isBlock) {
           codegenNode.isBlock = false
@@ -193,37 +215,33 @@ export function getStaticType(
         resultCache.set(node, returnType)
         return returnType
       } else {
-        resultCache.set(node, StaticType.NOT_STATIC)
-        return StaticType.NOT_STATIC
+        resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+        return ConstantTypes.NOT_CONSTANT
       }
     case NodeTypes.TEXT:
     case NodeTypes.COMMENT:
-      return StaticType.FULL_STATIC
+      return ConstantTypes.CAN_STRINGIFY
     case NodeTypes.IF:
     case NodeTypes.FOR:
     case NodeTypes.IF_BRANCH:
-      return StaticType.NOT_STATIC
+      return ConstantTypes.NOT_CONSTANT
     case NodeTypes.INTERPOLATION:
     case NodeTypes.TEXT_CALL:
-      return getStaticType(node.content, resultCache)
+      return getConstantType(node.content, resultCache)
     case NodeTypes.SIMPLE_EXPRESSION:
-      return node.isRuntimeConstant
-        ? StaticType.HAS_RUNTIME_CONSTANT
-        : node.isConstant
-          ? StaticType.FULL_STATIC
-          : StaticType.NOT_STATIC
+      return node.constType
     case NodeTypes.COMPOUND_EXPRESSION:
-      let returnType = StaticType.FULL_STATIC
+      let returnType = ConstantTypes.CAN_STRINGIFY
       for (let i = 0; i < node.children.length; i++) {
         const child = node.children[i]
         if (isString(child) || isSymbol(child)) {
           continue
         }
-        const childType = getStaticType(child, resultCache)
-        if (childType === StaticType.NOT_STATIC) {
-          return StaticType.NOT_STATIC
-        } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
-          returnType = StaticType.HAS_RUNTIME_CONSTANT
+        const childType = getConstantType(child, resultCache)
+        if (childType === ConstantTypes.NOT_CONSTANT) {
+          return ConstantTypes.NOT_CONSTANT
+        } else if (childType < returnType) {
+          returnType = childType
         }
       }
       return returnType
@@ -232,33 +250,40 @@ export function getStaticType(
         const exhaustiveCheck: never = node
         exhaustiveCheck
       }
-      return StaticType.NOT_STATIC
+      return ConstantTypes.NOT_CONSTANT
   }
 }
 
-/**
- * Even for a node with no patch flag, it is possible for it to contain
- * non-hoistable expressions that refers to scope variables, e.g. compiler
- * injected keys or cached event handlers. Therefore we need to always check the
- * codegenNode's props to be sure.
- */
-function hasNonHoistableProps(node: PlainElementNode): boolean {
+function getGeneratedPropsConstantType(
+  node: PlainElementNode,
+  resultCache: Map<TemplateChildNode, ConstantTypes>
+): ConstantTypes {
+  let returnType = ConstantTypes.CAN_STRINGIFY
   const props = getNodeProps(node)
   if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
     const { properties } = props
     for (let i = 0; i < properties.length; i++) {
       const { key, value } = properties[i]
-      if (
-        key.type !== NodeTypes.SIMPLE_EXPRESSION ||
-        !key.isStatic ||
-        (value.type !== NodeTypes.SIMPLE_EXPRESSION ||
-          (!value.isStatic && !value.isConstant))
-      ) {
-        return true
+      const keyType = getConstantType(key, resultCache)
+      if (keyType === ConstantTypes.NOT_CONSTANT) {
+        return keyType
+      }
+      if (keyType < returnType) {
+        returnType = keyType
+      }
+      if (value.type !== NodeTypes.SIMPLE_EXPRESSION) {
+        return ConstantTypes.NOT_CONSTANT
+      }
+      const valueType = getConstantType(value, resultCache)
+      if (valueType === ConstantTypes.NOT_CONSTANT) {
+        return valueType
+      }
+      if (valueType < returnType) {
+        returnType = valueType
       }
     }
   }
-  return false
+  return returnType
 }
 
 function getNodeProps(node: PlainElementNode) {
index 1d4a46a133ea56dbc2fceb3e1cee9be8243af87e..f2f1459e05cd9f83269a4a017f9698e50a1a89c6 100644 (file)
@@ -18,7 +18,8 @@ import {
   VNodeCall,
   TemplateTextChildNode,
   DirectiveArguments,
-  createVNodeCall
+  createVNodeCall,
+  ConstantTypes
 } from '../ast'
 import {
   PatchFlags,
@@ -53,7 +54,7 @@ import {
   isStaticExp
 } from '../utils'
 import { buildSlots } from './vSlot'
-import { getStaticType } from './hoistStatic'
+import { getConstantType } from './hoistStatic'
 import { BindingTypes } from '../options'
 
 // some directive transforms (e.g. v-model) may return a symbol for runtime
@@ -166,7 +167,10 @@ export const transformElement: NodeTransform = (node, context) => {
         const hasDynamicTextChild =
           type === NodeTypes.INTERPOLATION ||
           type === NodeTypes.COMPOUND_EXPRESSION
-        if (hasDynamicTextChild && !getStaticType(child)) {
+        if (
+          hasDynamicTextChild &&
+          getConstantType(child) === ConstantTypes.NOT_CONSTANT
+        ) {
           patchFlag |= PatchFlags.TEXT
         }
         // pass directly if the only child is a text node
@@ -343,7 +347,7 @@ export function buildProps(
         value.type === NodeTypes.JS_CACHE_EXPRESSION ||
         ((value.type === NodeTypes.SIMPLE_EXPRESSION ||
           value.type === NodeTypes.COMPOUND_EXPRESSION) &&
-          getStaticType(value) > 0)
+          getConstantType(value) > 0)
       ) {
         // skip if the prop is a cached handler or has constant value
         return
index 83f80e87553765022cac2c85f05e98fad7ffc4c3..1f2004bce3dc26a2f295f355d386d46df03950dc 100644 (file)
@@ -14,7 +14,8 @@ import {
   ExpressionNode,
   SimpleExpressionNode,
   CompoundExpressionNode,
-  createCompoundExpression
+  createCompoundExpression,
+  ConstantTypes
 } from '../ast'
 import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
 import {
@@ -190,25 +191,26 @@ export function processExpression(
 
   // fast path if expression is a simple identifier.
   const rawExp = node.content
-  // bail on parens to prevent any possible function invocations.
-  const bailConstant = rawExp.indexOf(`(`) > -1
+  // bail constant on parens (function invocation) and dot (member access)
+  const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0
+
   if (isSimpleIdentifier(rawExp)) {
-    // const bindings exposed from setup - we know they never change
-    // marking it as runtime constant will prevent it from being listed as
-    // a dynamic prop.
-    if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
-      node.isRuntimeConstant = true
-    }
-    if (
-      !asParams &&
-      !context.identifiers[rawExp] &&
-      !isGloballyWhitelisted(rawExp) &&
-      !isLiteralWhitelisted(rawExp)
-    ) {
+    const isScopeVarReference = context.identifiers[rawExp]
+    const isAllowedGlobal = isGloballyWhitelisted(rawExp)
+    const isLiteral = isLiteralWhitelisted(rawExp)
+    if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
+      // const bindings exposed from setup can be skipped for patching but
+      // cannot be hoisted to module scope
+      if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
+        node.constType = ConstantTypes.CAN_SKIP_PATCH
+      }
       node.content = rewriteIdentifier(rawExp)
-    } else if (!context.identifiers[rawExp] && !bailConstant) {
-      // mark node constant for hoisting unless it's referring a scope variable
-      node.isConstant = true
+    } else if (!isScopeVarReference) {
+      if (isLiteral) {
+        node.constType = ConstantTypes.CAN_STRINGIFY
+      } else {
+        node.constType = ConstantTypes.CAN_HOIST
+      }
     }
     return node
   }
@@ -342,7 +344,7 @@ export function processExpression(
           start: advancePositionWithClone(node.loc.start, source, start),
           end: advancePositionWithClone(node.loc.start, source, end)
         },
-        id.isConstant /* isConstant */
+        id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
       )
     )
     if (i === ids.length - 1 && end < rawExp.length) {
@@ -355,7 +357,9 @@ export function processExpression(
     ret = createCompoundExpression(children, node.loc)
   } else {
     ret = node
-    ret.isConstant = !bailConstant
+    ret.constType = bailConstant
+      ? ConstantTypes.NOT_CONSTANT
+      : ConstantTypes.CAN_STRINGIFY
   }
   ret.identifiers = Object.keys(knownIds)
   return ret
index 7419497fe1a0c5510196127ce15e46ca8dc77676..103ceb6fb5c3986fc977816b9f62f6565f0ae481 100644 (file)
@@ -4,11 +4,13 @@ import {
   CompoundExpressionNode,
   createCallExpression,
   CallExpression,
-  ElementTypes
+  ElementTypes,
+  ConstantTypes
 } from '../ast'
 import { isText } from '../utils'
 import { CREATE_TEXT } from '../runtimeHelpers'
 import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { getConstantType } from './hoistStatic'
 
 // Merge adjacent text nodes and expressions into a single expression
 // e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
@@ -78,7 +80,10 @@ export const transformText: NodeTransform = (node, context) => {
             callArgs.push(child)
           }
           // mark dynamic text with flag so it gets patched inside a block
-          if (!context.ssr && child.type !== NodeTypes.TEXT) {
+          if (
+            !context.ssr &&
+            getConstantType(child) === ConstantTypes.NOT_CONSTANT
+          ) {
             callArgs.push(
               `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
             )
index 63c4458c1a99966bca512db2b18da74413fe190f..7949c014fb2c76f9b8f1b8107152fa51ec38a8b9 100644 (file)
@@ -77,7 +77,7 @@ export const transformFor = createStructuralDirectiveTransform(
 
       const isStableFragment =
         forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
-        forNode.source.isConstant
+        forNode.source.constType > 0
       const fragmentFlag = isStableFragment
         ? PatchFlags.STABLE_FRAGMENT
         : keyProp
index 76a79e53f21625771aa4966820e6bc4c4d7d1019..e98604d037baa05a6c9a72ad5e548492e8255c09 100644 (file)
@@ -21,7 +21,8 @@ import {
   createVNodeCall,
   AttributeNode,
   locStub,
-  CacheExpression
+  CacheExpression,
+  ConstantTypes
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
@@ -227,7 +228,12 @@ function createChildrenCodegenNode(
   const { helper } = context
   const keyProperty = createObjectProperty(
     `key`,
-    createSimpleExpression(`${keyIndex}`, false, locStub, true)
+    createSimpleExpression(
+      `${keyIndex}`,
+      false,
+      locStub,
+      ConstantTypes.CAN_HOIST
+    )
   )
   const { children } = branch
   const firstChild = children[0]
index 7bdffd81c0aa791cb4fa14d9547165ae17813434..3151c958e312c91cac68780c0fae271d1c731377 100644 (file)
@@ -6,7 +6,8 @@ import {
   NodeTypes,
   Property,
   ElementTypes,
-  ExpressionNode
+  ExpressionNode,
+  ConstantTypes
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
@@ -125,7 +126,12 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
     props.push(
       createObjectProperty(
         modifiersKey,
-        createSimpleExpression(`{ ${modifiers} }`, false, dir.loc, true)
+        createSimpleExpression(
+          `{ ${modifiers} }`,
+          false,
+          dir.loc,
+          ConstantTypes.CAN_HOIST
+        )
       )
     )
   }
index 00db326800790f6e21cc1be75526998d46c4c9e3..207d53e4195a5018199b0da056f61ce0c4a056a9 100644 (file)
@@ -87,7 +87,7 @@ export const transformOn: DirectiveTransform = (
         context.cacheHandlers &&
         // runtime constants don't need to be cached
         // (this is analyzed by compileScript in SFC <script setup>)
-        !(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.isRuntimeConstant) &&
+        !(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.constType > 0) &&
         // #1541 bail if this is a member exp handler passed to a component -
         // we need to use the original function to preserve arity,
         // e.g. <transition> relies on checking cb.length to determine
index 9f227fa2184a0fda0687884ac78db1e9dd422998..736ea729fc4a67e3bc12d7937238984503373400 100644 (file)
@@ -6,7 +6,8 @@ import {
   ErrorCodes,
   ElementTypes,
   InterpolationNode,
-  AttributeNode
+  AttributeNode,
+  ConstantTypes
 } from '@vue/compiler-core'
 import { parserOptions, DOMNamespaces } from '../src/parserOptions'
 
@@ -253,7 +254,7 @@ describe('DOM parser', () => {
           type: NodeTypes.SIMPLE_EXPRESSION,
           content: `a < b`,
           isStatic: false,
-          isConstant: false,
+          constType: ConstantTypes.NOT_CONSTANT,
           loc: {
             start: { offset: 8, line: 1, column: 9 },
             end: { offset: 16, line: 1, column: 17 },
index 5dc5621725b4802746c2193ba97d9590a1d961e9..7918df9a61045cde20cd485e9055d671bfacd7e6 100644 (file)
@@ -2,7 +2,8 @@ import {
   compile,
   NodeTypes,
   CREATE_STATIC,
-  createSimpleExpression
+  createSimpleExpression,
+  ConstantTypes
 } from '../../src'
 import {
   stringifyStatic,
@@ -176,9 +177,8 @@ describe('stringify static html', () => {
                 '_imports_0_',
                 false,
                 node.loc,
-                true
+                ConstantTypes.CAN_HOIST
               )
-              exp.isRuntimeConstant = true
               node.props[0] = {
                 type: NodeTypes.DIRECTIVE,
                 name: 'bind',
index b226f26056aedf98d8d876e75267514d6ac340f2..d87b241609fd56e50e1241d7f0bd743b61c9ea7f 100644 (file)
@@ -3,7 +3,8 @@ import {
   NodeTypes,
   createSimpleExpression,
   SimpleExpressionNode,
-  SourceLocation
+  SourceLocation,
+  ConstantTypes
 } from '@vue/compiler-core'
 import { parseStringStyle } from '@vue/shared'
 
@@ -36,5 +37,10 @@ const parseInlineCSS = (
   loc: SourceLocation
 ): SimpleExpressionNode => {
   const normalized = parseStringStyle(cssText)
-  return createSimpleExpression(JSON.stringify(normalized), false, loc, true)
+  return createSimpleExpression(
+    JSON.stringify(normalized),
+    false,
+    loc,
+    ConstantTypes.CAN_STRINGIFY
+  )
 }
index 968b0606b8cb072abf9421a2b723d739dd67780a..78211e6ab0288d40823d5571647a160a156b45b8 100644 (file)
@@ -1,5 +1,6 @@
 import path from 'path'
 import {
+  ConstantTypes,
   createSimpleExpression,
   ExpressionNode,
   NodeTransform,
@@ -159,19 +160,26 @@ function getImportsExpressionExp(
       return existing.exp as ExpressionNode
     }
     const name = `_imports_${importsArray.length}`
-    const exp = createSimpleExpression(name, false, loc, true)
-    exp.isRuntimeConstant = true
+    const exp = createSimpleExpression(
+      name,
+      false,
+      loc,
+      ConstantTypes.CAN_HOIST
+    )
     context.imports.add({ exp, path })
     if (hash && path) {
-      const ret = context.hoist(
-        createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
+      return context.hoist(
+        createSimpleExpression(
+          `${name} + '${hash}'`,
+          false,
+          loc,
+          ConstantTypes.CAN_HOIST
+        )
       )
-      ret.isRuntimeConstant = true
-      return ret
     } else {
       return exp
     }
   } else {
-    return createSimpleExpression(`''`, false, loc, true)
+    return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_HOIST)
   }
 }
index 7fcf60754b728086d9ffe7e650ba5606c0ca4352..a667d984e1649ba3ec374e28a90c21d4ccf9b8e3 100644 (file)
@@ -1,5 +1,6 @@
 import path from 'path'
 import {
+  ConstantTypes,
   createCompoundExpression,
   createSimpleExpression,
   NodeTransform,
@@ -107,14 +108,14 @@ export const transformSrcset: NodeTransform = (
                     `_imports_${existingImportsIndex}`,
                     false,
                     attr.loc,
-                    true
+                    ConstantTypes.CAN_HOIST
                   )
                 } else {
                   exp = createSimpleExpression(
                     `_imports_${importsArray.length}`,
                     false,
                     attr.loc,
-                    true
+                    ConstantTypes.CAN_HOIST
                   )
                   context.imports.add({ exp, path })
                 }
@@ -125,7 +126,7 @@ export const transformSrcset: NodeTransform = (
                 `"${url}"`,
                 false,
                 attr.loc,
-                true
+                ConstantTypes.CAN_HOIST
               )
               compoundExpression.children.push(exp)
             }
@@ -140,7 +141,7 @@ export const transformSrcset: NodeTransform = (
           })
 
           const hoisted = context.hoist(compoundExpression)
-          hoisted.isRuntimeConstant = true
+          hoisted.constType = ConstantTypes.CAN_HOIST
 
           node.props[index] = {
             type: NodeTypes.DIRECTIVE,