wip(compiler): property deduping
This commit is contained in:
parent
f79433cbb3
commit
3e8cd3f25f
@ -3,7 +3,8 @@ import {
|
|||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
parse,
|
parse,
|
||||||
transform,
|
transform,
|
||||||
ErrorCodes
|
ErrorCodes,
|
||||||
|
compile
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import {
|
import {
|
||||||
@ -305,10 +306,7 @@ describe('compiler: element transform', () => {
|
|||||||
foo(dir) {
|
foo(dir) {
|
||||||
_dir = dir
|
_dir = dir
|
||||||
return {
|
return {
|
||||||
props: [
|
props: [createObjectProperty(dir.arg!, dir.exp!, dir.loc)],
|
||||||
createObjectProperty(dir.arg!, dir.exp!, dir.loc),
|
|
||||||
createObjectProperty(dir.arg!, dir.exp!, dir.loc)
|
|
||||||
],
|
|
||||||
needRuntime: true
|
needRuntime: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,11 +326,6 @@ describe('compiler: element transform', () => {
|
|||||||
{
|
{
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: _dir!.arg,
|
|
||||||
value: _dir!.exp
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_PROPERTY,
|
type: NodeTypes.JS_PROPERTY,
|
||||||
key: _dir!.arg,
|
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')
|
test.todo('slot outlets')
|
||||||
})
|
})
|
||||||
|
@ -159,7 +159,7 @@ export interface ObjectExpression extends Node {
|
|||||||
export interface Property extends Node {
|
export interface Property extends Node {
|
||||||
type: NodeTypes.JS_PROPERTY
|
type: NodeTypes.JS_PROPERTY
|
||||||
key: ExpressionNode
|
key: ExpressionNode
|
||||||
value: ExpressionNode
|
value: JSChildNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArrayExpression extends Node {
|
export interface ArrayExpression extends Node {
|
||||||
|
@ -453,7 +453,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
|||||||
genExpressionAsPropertyKey(key, context)
|
genExpressionAsPropertyKey(key, context)
|
||||||
push(`: `)
|
push(`: `)
|
||||||
// value
|
// value
|
||||||
genExpression(value, context)
|
genNode(value, context)
|
||||||
if (i < properties.length - 1) {
|
if (i < properties.length - 1) {
|
||||||
// will only reach this if it's multilines
|
// will only reach this if it's multilines
|
||||||
push(`,`)
|
push(`,`)
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createExpression,
|
createExpression,
|
||||||
createObjectExpression
|
createObjectExpression,
|
||||||
|
Property
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
@ -135,7 +136,9 @@ function buildProps(
|
|||||||
if (!arg && (isBind || name === 'on')) {
|
if (!arg && (isBind || name === 'on')) {
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
mergeArgs.push(createObjectExpression(properties, elementLoc))
|
mergeArgs.push(
|
||||||
|
createObjectExpression(dedupeProperties(properties), elementLoc)
|
||||||
|
)
|
||||||
properties = []
|
properties = []
|
||||||
}
|
}
|
||||||
if (isBind) {
|
if (isBind) {
|
||||||
@ -187,7 +190,9 @@ function buildProps(
|
|||||||
// has v-bind="object" or v-on="object", wrap with mergeProps
|
// has v-bind="object" or v-on="object", wrap with mergeProps
|
||||||
if (mergeArgs.length) {
|
if (mergeArgs.length) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
mergeArgs.push(createObjectExpression(properties, elementLoc))
|
mergeArgs.push(
|
||||||
|
createObjectExpression(dedupeProperties(properties), elementLoc)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (mergeArgs.length > 1) {
|
if (mergeArgs.length > 1) {
|
||||||
context.imports.add(MERGE_PROPS)
|
context.imports.add(MERGE_PROPS)
|
||||||
@ -197,7 +202,10 @@ function buildProps(
|
|||||||
propsExpression = mergeArgs[0]
|
propsExpression = mergeArgs[0]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
propsExpression = createObjectExpression(properties, elementLoc)
|
propsExpression = createObjectExpression(
|
||||||
|
dedupeProperties(properties),
|
||||||
|
elementLoc
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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(
|
function createDirectiveArgs(
|
||||||
dir: DirectiveNode,
|
dir: DirectiveNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
|
@ -14,7 +14,6 @@ import { NodeTransform, TransformContext } from '../transform'
|
|||||||
import { NodeTypes, createExpression, ExpressionNode } from '../ast'
|
import { NodeTypes, createExpression, ExpressionNode } from '../ast'
|
||||||
import { Node, Function, Identifier } from 'estree'
|
import { Node, Function, Identifier } from 'estree'
|
||||||
import { advancePositionWithClone } from '../utils'
|
import { advancePositionWithClone } from '../utils'
|
||||||
|
|
||||||
export const transformExpression: NodeTransform = (node, context) => {
|
export const transformExpression: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
|
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
|
||||||
processExpression(node, context)
|
processExpression(node, context)
|
||||||
@ -27,8 +26,14 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
processExpression(prop.exp, context)
|
processExpression(prop.exp, context)
|
||||||
}
|
}
|
||||||
if (prop.arg && !prop.arg.isStatic) {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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