wip(compiler): property deduping
This commit is contained in:
parent
f79433cbb3
commit
3e8cd3f25f
@ -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')
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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(`,`)
|
||||
|
@ -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
|
||||
|
@ -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,9 +26,15 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||
processExpression(prop.exp, context)
|
||||
}
|
||||
if (prop.arg && !prop.arg.isStatic) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user