CompilerOptions,
parse,
transform,
- ErrorCodes
+ ErrorCodes,
+ compile
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import {
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
}
}
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
- {
- type: NodeTypes.JS_PROPERTY,
- key: _dir!.arg,
- value: _dir!.exp
- },
{
type: NodeTypes.JS_PROPERTY,
key: _dir!.arg,
])
})
+ 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')
})
createArrayExpression,
createObjectProperty,
createExpression,
- createObjectExpression
+ createObjectExpression,
+ Property
} from '../ast'
import { isArray } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
if (!arg && (isBind || name === 'on')) {
if (exp) {
if (properties.length) {
- mergeArgs.push(createObjectExpression(properties, elementLoc))
+ mergeArgs.push(
+ createObjectExpression(dedupeProperties(properties), elementLoc)
+ )
properties = []
}
if (isBind) {
// 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)
propsExpression = mergeArgs[0]
}
} else {
- propsExpression = createObjectExpression(properties, elementLoc)
+ propsExpression = createObjectExpression(
+ dedupeProperties(properties),
+ elementLoc
+ )
}
return {
}
}
+// 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
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)
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
}
}
}