]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler): bail strigification on runtime constant expressions
authorEvan You <yyx990803@gmail.com>
Mon, 4 May 2020 19:15:26 +0000 (15:15 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 4 May 2020 19:15:26 +0000 (15:15 -0400)
packages/compiler-core/src/ast.ts
packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
packages/compiler-dom/src/transforms/stringifyStatic.ts
packages/compiler-sfc/src/templateTransformAssetUrl.ts
packages/compiler-sfc/src/templateTransformSrcset.ts

index 5b5e9e83ef44e04291550fa6cafaef796809e095..7adfc8cba58164b5028723fc94626d44ad4a8063 100644 (file)
@@ -195,6 +195,9 @@ export interface SimpleExpressionNode extends Node {
   // an expression parsed as the params of a function will track
   // 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 {
index 5e942c17af7fb12fbf8b3069e659dd6e3bdcbdac..3dca19a0e8d10d67226eff1a5abeb54d4ca459b4 100644 (file)
@@ -1,4 +1,9 @@
-import { compile, NodeTypes, CREATE_STATIC } from '../../src'
+import {
+  compile,
+  NodeTypes,
+  CREATE_STATIC,
+  createSimpleExpression
+} from '../../src'
 import {
   stringifyStatic,
   StringifyThresholds
@@ -121,4 +126,46 @@ describe('stringify static html', () => {
       ]
     })
   })
+
+  test('should bail on runtime constant v-bind bindings', () => {
+    const { ast } = compile(
+      `<div><div><img src="./foo" />${repeat(
+        `<span class="foo">foo</span>`,
+        StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+      )}</div></div>`,
+      {
+        hoistStatic: true,
+        prefixIdentifiers: true,
+        transformHoist: stringifyStatic,
+        nodeTransforms: [
+          node => {
+            if (node.type === NodeTypes.ELEMENT && node.tag === 'img') {
+              const exp = createSimpleExpression(
+                '_imports_0_',
+                false,
+                node.loc,
+                true
+              )
+              exp.isRuntimeConstant = true
+              node.props[0] = {
+                type: NodeTypes.DIRECTIVE,
+                name: 'bind',
+                arg: createSimpleExpression('src', true),
+                exp,
+                modifiers: [],
+                loc: node.loc
+              }
+            }
+          }
+        ]
+      }
+    )
+    // the expression and the tree are still hoistable
+    expect(ast.hoists.length).toBe(1)
+    // ...but the hoisted tree should not be stringified
+    expect(ast.hoists[0]).toMatchObject({
+      // if it's stringified it will be NodeTypes.CALL_EXPRESSION
+      type: NodeTypes.VNODE_CALL
+    })
+  })
 })
index a4aa7745f8ed22cac15f1dd4077e5571b3cbf700..f648257e48900960d31c6e6ac2e323fb2fc612f6 100644 (file)
@@ -47,12 +47,30 @@ export const enum StringifyThresholds {
 function shouldOptimize(node: ElementNode): boolean {
   let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
   let nodeThreshold = StringifyThresholds.NODE_COUNT
+  let bail = false
 
   // TODO: check for cases where using innerHTML will result in different
   // output compared to imperative node insertions.
   // probably only need to check for most common case
   // i.e. non-phrasing-content tags inside `<p>`
   function walk(node: ElementNode) {
+    // some transforms, e.g. `transformAssetUrls` in `@vue/compiler-sfc` may
+    // convert static attributes into a v-bind with a constnat expresion.
+    // Such constant bindings are eligible for hoisting but not for static
+    // stringification because they cannot be pre-evaluated.
+    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
+      ) {
+        bail = true
+        return false
+      }
+    }
     for (let i = 0; i < node.children.length; i++) {
       if (--nodeThreshold === 0) {
         return true
@@ -65,6 +83,9 @@ function shouldOptimize(node: ElementNode): boolean {
         if (walk(child)) {
           return true
         }
+        if (bail) {
+          return false
+        }
       }
     }
     return false
index 44ebe36af0a18c493f87b9463552acff76e1314a..40189be86afbb5e25489c1d8948989ea34a9826a 100644 (file)
@@ -126,11 +126,14 @@ function getImportsExpressionExp(
     }
     const name = `_imports_${importsArray.length}`
     const exp = createSimpleExpression(name, false, loc, true)
+    exp.isRuntimeConstant = true
     context.imports.add({ exp, path })
     if (hash && path) {
-      return context.hoist(
+      const ret = context.hoist(
         createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
       )
+      ret.isRuntimeConstant = true
+      return ret
     } else {
       return exp
     }
index dccebd9e5c689affef51943a6052cc96171efb8d..06b16ffd67c31b1714360418d0c995804adb05ab 100644 (file)
@@ -86,11 +86,14 @@ export const transformSrcset: NodeTransform = (node, context) => {
             }
           })
 
+          const hoisted = context.hoist(compoundExpression)
+          hoisted.isRuntimeConstant = true
+
           node.props[index] = {
             type: NodeTypes.DIRECTIVE,
             name: 'bind',
             arg: createSimpleExpression('srcset', true, attr.loc),
-            exp: context.hoist(compoundExpression),
+            exp: hoisted,
             modifiers: [],
             loc: attr.loc
           }