]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler): property deduping
authorEvan You <yyx990803@gmail.com>
Wed, 25 Sep 2019 16:39:46 +0000 (12:39 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 25 Sep 2019 16:39:46 +0000 (12:39 -0400)
packages/compiler-core/__tests__/transforms/transformElement.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/transformExpression.ts
packages/compiler-core/src/transforms/vBindClass.ts [deleted file]
packages/compiler-core/src/transforms/vBindStyle.ts [deleted file]

index 00152a751a410f4c62c2bbec2ff4e23b861d3948..137d373b8e5d0633c9332f2816b0383ca7da3305 100644 (file)
@@ -3,7 +3,8 @@ import {
   CompilerOptions,
   parse,
   transform,
-  ErrorCodes
+  ErrorCodes,
+  compile
 } from '../../src'
 import { transformElement } from '../../src/transforms/transformElement'
 import {
@@ -305,10 +306,7 @@ describe('compiler: element transform', () => {
           foo(dir) {
             _dir = dir
             return {
-              props: [
-                createObjectProperty(dir.arg!, dir.exp!, dir.loc),
-                createObjectProperty(dir.arg!, dir.exp!, dir.loc)
-              ],
+              props: [createObjectProperty(dir.arg!, dir.exp!, dir.loc)],
               needRuntime: true
             }
           }
@@ -328,11 +326,6 @@ describe('compiler: element transform', () => {
           {
             type: NodeTypes.JS_OBJECT_EXPRESSION,
             properties: [
-              {
-                type: NodeTypes.JS_PROPERTY,
-                key: _dir!.arg,
-                value: _dir!.exp
-              },
               {
                 type: NodeTypes.JS_PROPERTY,
                 key: _dir!.arg,
@@ -457,5 +450,12 @@ describe('compiler: element transform', () => {
     ])
   })
 
+  test('props dedupe', () => {
+    const { code } = compile(
+      `<div class="a" :class="b" @click.foo="a" @click.bar="b" style="color: red" :style="{fontSize: 14}" />`
+    )
+    console.log(code)
+  })
+
   test.todo('slot outlets')
 })
index 24b8401100bd1a1d609163eb7fd3f6a32f6142ac..de828ef6dd556b3dab43f2511ff712f470aeda7c 100644 (file)
@@ -159,7 +159,7 @@ export interface ObjectExpression extends Node {
 export interface Property extends Node {
   type: NodeTypes.JS_PROPERTY
   key: ExpressionNode
-  value: ExpressionNode
+  value: JSChildNode
 }
 
 export interface ArrayExpression extends Node {
index 62e8312502dc9836d44eaad9b47f7669ce6638d2..94d7241f64dfd0a7ba0934e47e60be5dc221b239 100644 (file)
@@ -453,7 +453,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
     genExpressionAsPropertyKey(key, context)
     push(`: `)
     // value
-    genExpression(value, context)
+    genNode(value, context)
     if (i < properties.length - 1) {
       // will only reach this if it's multilines
       push(`,`)
index 7b73411c7409bd9ab1e0679a96a2ec0e80bcd9a5..fa58cb946de670413b2b695cfd75fdd9dc790d51 100644 (file)
@@ -12,7 +12,8 @@ import {
   createArrayExpression,
   createObjectProperty,
   createExpression,
-  createObjectExpression
+  createObjectExpression,
+  Property
 } from '../ast'
 import { isArray } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
@@ -135,7 +136,9 @@ function buildProps(
       if (!arg && (isBind || name === 'on')) {
         if (exp) {
           if (properties.length) {
-            mergeArgs.push(createObjectExpression(properties, elementLoc))
+            mergeArgs.push(
+              createObjectExpression(dedupeProperties(properties), elementLoc)
+            )
             properties = []
           }
           if (isBind) {
@@ -187,7 +190,9 @@ function buildProps(
   // has v-bind="object" or v-on="object", wrap with mergeProps
   if (mergeArgs.length) {
     if (properties.length) {
-      mergeArgs.push(createObjectExpression(properties, elementLoc))
+      mergeArgs.push(
+        createObjectExpression(dedupeProperties(properties), elementLoc)
+      )
     }
     if (mergeArgs.length > 1) {
       context.imports.add(MERGE_PROPS)
@@ -197,7 +202,10 @@ function buildProps(
       propsExpression = mergeArgs[0]
     }
   } else {
-    propsExpression = createObjectExpression(properties, elementLoc)
+    propsExpression = createObjectExpression(
+      dedupeProperties(properties),
+      elementLoc
+    )
   }
 
   return {
@@ -206,6 +214,86 @@ function buildProps(
   }
 }
 
+// Dedupe props in an object literal.
+// Literal duplicated attributes would have been warned during the parse phase,
+// however, it's possible to encounter duplicated `onXXX` handlers with different
+// modifiers. We also need to merge static and dynamic class / style attributes.
+// - onXXX handlers / style: merge into array
+// - class: merge into single expression with concatenation
+function dedupeProperties(properties: Property[]): Property[] {
+  const knownProps: Record<string, Property> = {}
+  const deduped: Property[] = []
+  for (let i = 0; i < properties.length; i++) {
+    const prop = properties[i]
+    // dynamic key named are always allowed
+    if (!prop.key.isStatic) {
+      deduped.push(prop)
+      continue
+    }
+    const name = prop.key.content
+    const existing = knownProps[name]
+    if (existing) {
+      if (name.startsWith('on')) {
+        mergeAsArray(existing, prop)
+      } else if (name === 'style') {
+        mergeStyles(existing, prop)
+      } else if (name === 'class') {
+        mergeClasses(existing, prop)
+      }
+      // unexpected duplicate, should have emitted error during parse
+    } else {
+      knownProps[name] = prop
+      deduped.push(prop)
+    }
+  }
+  return deduped
+}
+
+function mergeAsArray(existing: Property, incoming: Property) {
+  if (existing.value.type === NodeTypes.JS_ARRAY_EXPRESSION) {
+    existing.value.elements.push(incoming.value)
+  } else {
+    existing.value = createArrayExpression(
+      [existing.value, incoming.value],
+      existing.loc
+    )
+  }
+}
+
+// Merge dynamic and static style into a single prop
+export function mergeStyles(existing: Property, incoming: Property) {
+  if (
+    existing.value.type === NodeTypes.JS_OBJECT_EXPRESSION &&
+    incoming.value.type === NodeTypes.JS_OBJECT_EXPRESSION
+  ) {
+    // if both are objects, merge the object expressions.
+    // style="color: red" :style="{ a: b }"
+    // -> { color: "red", a: b }
+    existing.value.properties.push(...incoming.value.properties)
+  } else {
+    // otherwise merge as array
+    // style="color:red" :style="a"
+    // -> style: [{ color: "red" }, a]
+    mergeAsArray(existing, incoming)
+  }
+}
+
+// Merge dynamic and static class into a single prop
+function mergeClasses(existing: Property, incoming: Property) {
+  const e = existing.value as ExpressionNode
+  const children =
+    e.children ||
+    (e.children = [
+      {
+        ...e,
+        children: undefined
+      }
+    ])
+  // :class="expression" class="string"
+  // -> class: expression + "string"
+  children.push(` + " " + `, incoming.value as ExpressionNode)
+}
+
 function createDirectiveArgs(
   dir: DirectiveNode,
   context: TransformContext
index 12dbf51513399c3e6a0477f79520f736b98868fd..224438fa636981ea34965be00c6af2fafa3d48a9 100644 (file)
@@ -14,7 +14,6 @@ import { NodeTransform, TransformContext } from '../transform'
 import { NodeTypes, createExpression, ExpressionNode } from '../ast'
 import { Node, Function, Identifier } from 'estree'
 import { advancePositionWithClone } from '../utils'
-
 export const transformExpression: NodeTransform = (node, context) => {
   if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
     processExpression(node, context)
@@ -27,8 +26,14 @@ export const transformExpression: NodeTransform = (node, context) => {
           processExpression(prop.exp, context)
         }
         if (prop.arg && !prop.arg.isStatic) {
-          processExpression(prop.arg, context)
+          if (prop.name === 'class') {
+            // TODO special expression optimization for classes
+          } else {
+            processExpression(prop.arg, context)
+          }
         }
+      } else if (prop.name === 'style') {
+        // TODO parse inline CSS literals into objects
       }
     }
   }
diff --git a/packages/compiler-core/src/transforms/vBindClass.ts b/packages/compiler-core/src/transforms/vBindClass.ts
deleted file mode 100644 (file)
index d5392cf..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// Optimizations
-// - b -> b (use runtime normalization)
-// - ['foo', b] -> 'foo' + normalize(b)
-// - { a, b: c } -> (a ? a : '') + (b ? c : '')
-// - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '')
-
-// Also merge dynamic and static class into a single prop
-
-// Attach CLASS patchFlag if necessary
diff --git a/packages/compiler-core/src/transforms/vBindStyle.ts b/packages/compiler-core/src/transforms/vBindStyle.ts
deleted file mode 100644 (file)
index b3f23cf..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-// Optimizations
-// The compiler pre-compiles static string styles into static objects
-// + detects and hoists inline static objects
-
-// e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted as
-
-// ``` js
-// const style = { color: 'red' }
-// render() { return e('div', { style }) }
-// ```
-
-// Also nerge dynamic and static style into a single prop
-
-// Attach STYLE patchFlag if necessary