提交 34d1e5e1 编写于 作者: fxy060608's avatar fxy060608

wip(mp): v-bind:style object syntax

上级 c211db8b
......@@ -29,14 +29,30 @@ describe('compiler: transform class', () => {
`<view :class="foo"/>`,
`<view class="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.foo }
return { a: _normalizeClass(_ctx.foo) }
}`
)
assert(
`<view :class="foo | bar"/>`,
`<view class="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.foo | _ctx.bar }
return { a: _normalizeClass(_ctx.foo | _ctx.bar) }
}`
)
})
test('v-bind:class basic + class ', () => {
assert(
`<view :class="foo" class="bar"/>`,
`<view class="{{[a, 'bar']}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.foo) }
}`
)
assert(
`<view class="bar" :class="foo"/>`,
`<view class="{{['bar', a]}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.foo) }
}`
)
})
......@@ -49,12 +65,87 @@ describe('compiler: transform class', () => {
}`
)
assert(
`<view :class="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1 }"/>`,
`<view class="{{['a', 'c', a && 'g', b && 'h', c && 'i']}}"/>`,
`<view :class="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1, j, [k]:1, [l]:m, ...n, ...{a:true}, ...{b:o} }"/>`,
`<view class="{{['a', 'c', a && 'g', b && 'h', c && 'i', d && 'j', e, g && f, h, i, j]}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1, d: _ctx.j, e: _ctx.k, f: _ctx.l, g: _ctx.m, h: _normalizeClass(_ctx.n), i: _normalizeClass({ a: true }), j: _normalizeClass({ b: _ctx.o }) }
}`
)
})
test('v-bind:class object syntax + class', () => {
assert(
`<view :class="{ red: isRed }" class="foo bar"/>`,
`<view class="{{[a && 'red', 'foo bar']}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.isRed }
}`
)
assert(
`<view class="foo bar" :class="{ red: isRed }"/>`,
`<view class="{{['foo bar', a && 'red']}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.isRed }
}`
)
assert(
`<view :class="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1, j, [k]:1, [l]:m, ...n, ...{a:true}, ...{b:o} }" class="foo bar"/>`,
`<view class="{{['a', 'c', a && 'g', b && 'h', c && 'i', d && 'j', e, g && f, h, i, j, 'foo bar']}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1, d: _ctx.j, e: _ctx.k, f: _ctx.l, g: _ctx.m, h: _normalizeClass(_ctx.n), i: _normalizeClass({ a: true }), j: _normalizeClass({ b: _ctx.o }) }
}`
)
assert(
`<view class="foo bar" :class="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1, j, [k]:1, [l]:m, ...n, ...{a:true}, ...{b:o} }"/>`,
`<view class="{{['foo bar', 'a', 'c', a && 'g', b && 'h', c && 'i', d && 'j', e, g && f, h, i, j]}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1, d: _ctx.j, e: _ctx.k, f: _ctx.l, g: _ctx.m, h: _normalizeClass(_ctx.n), i: _normalizeClass({ a: true }), j: _normalizeClass({ b: _ctx.o }) }
}`
)
})
test('v-bind:class array syntax', () => {
assert(
`<view :class="[classA, classB]"/>`,
`<view class="{{[a, b]}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
}`
)
assert(
`<view :class="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]"/>`,
`<view class="{{[a, b, c, 'classE', d, e, f, g, h]}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass(_ctx.classH), g: _normalizeClass([_ctx.classI, _ctx.classJ]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
}`
)
})
test('v-bind:class array syntax + class', () => {
assert(
`<view :class="[classA, classB]" class="foo bar"/>`,
`<view class="{{[a, b, 'foo bar']}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
}`
)
assert(
`<view class="foo bar" :class="[classA, classB]"/>`,
`<view class="{{['foo bar', a, b]}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
}`
)
assert(
`<view :class="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]" class="foo bar"/>`,
`<view class="{{[a, b, c, 'classE', d, e, f, g, h, 'foo bar']}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass(_ctx.classH), g: _normalizeClass([_ctx.classI, _ctx.classJ]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
}`
)
assert(
`<view class="foo bar" :class="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]"/>`,
`<view class="{{['foo bar', a, b, c, 'classE', d, e, f, g, h]}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 }
return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass(_ctx.classH), g: _normalizeClass([_ctx.classI, _ctx.classJ]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
}`
)
})
test('v-bind:class array syntax', () => {})
})
import { assert } from './testUtils'
describe('compiler: transform style', () => {
test(`static style`, () => {
assert(
`<view style="color: green"/>`,
`<view style="color: green"/>`,
`(_ctx, _cache) => {
return {}
}`
)
assert(
`<view style="color: green;font-size: 15px"/>`,
`<view style="color: green;font-size: 15px"/>`,
`(_ctx, _cache) => {
return {}
}`
)
})
test('v-bind:style basic', () => {
assert(
`<view :style="foo"/>`,
`<view style="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeStyle(_ctx.foo) }
}`
)
assert(
`<view :style="foo | bar"/>`,
`<view style="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeStyle(_ctx.foo | _ctx.bar) }
}`
)
})
test('v-bind:style basic + style ', () => {
assert(
`<view :style="foo" style="color:green;"/>`,
`<view style="{{a + ';' + 'color:green;'}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeStyle(_ctx.foo) }
}`
)
assert(
`<view style="color:green;" :style="foo"/>`,
`<view style="{{'color:green;' + ';' + a}}"/>`,
`(_ctx, _cache) => {
return { a: _normalizeStyle(_ctx.foo) }
}`
)
})
test('v-bind:style object syntax', () => {
assert(
`<view :style="{ color: 'green' }"/>`,
`<view style="{{'color:' + 'green'}}"/>`,
`(_ctx, _cache) => {
return {}
}`
)
// 暂不支持数组,用的较少
// display:['-webkit-box', '-ms-flexbox', 'flex']
assert(
`<view :style="{color:'green',fontSize:'15px',backgroundColor: handle(bg),fontWeight,[padding]:box.padding,...style,...{margin:'0px'}}"/>`,
`<view style="{{'color:' + 'green' + ';' + ('font-size:' + '15px') + ';' + ('background-color:' + a) + ';' + ('font-weight:' + b) + ';' + (c + ':' + d) + ';' + e + ';' + f}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.handle(_ctx.bg), b: _ctx.fontWeight, c: _hyphenate(_ctx.padding), d: _ctx.box.padding, e: _normalizeStyle(_ctx.style), f: _normalizeStyle({ margin: '0px' }) }
}`
)
})
// test('v-bind:style object syntax + style', () => {
// assert(
// `<view :style="{ red: isRed }" style="foo bar"/>`,
// `<view style="{{[a && 'red', 'foo bar']}}"/>`,
// `(_ctx, _cache) => {
// return { a: _ctx.isRed }
// }`
// )
// assert(
// `<view style="foo bar" :style="{ red: isRed }"/>`,
// `<view style="{{['foo bar', a && 'red']}}"/>`,
// `(_ctx, _cache) => {
// return { a: _ctx.isRed }
// }`
// )
// assert(
// `<view :style="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1, j, [k]:1, [l]:m, ...n, ...{a:true}, ...{b:o} }" style="foo bar"/>`,
// `<view style="{{['a', 'c', a && 'g', b && 'h', c && 'i', d && 'j', e, g && f, h, i, j, 'foo bar']}}"/>`,
// `(_ctx, _cache) => {
// return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1, d: _ctx.j, e: _ctx.k, f: _ctx.l, g: _ctx.m, h: _normalizeClass({ ..._ctx.n }), i: _normalizeClass({ ...{ a: true } }), j: _normalizeClass({ ...{ b: _ctx.o } }) }
// }`
// )
// assert(
// `<view style="foo bar" :style="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1, j, [k]:1, [l]:m, ...n, ...{a:true}, ...{b:o} }"/>`,
// `<view style="{{['foo bar', 'a', 'c', a && 'g', b && 'h', c && 'i', d && 'j', e, g && f, h, i, j]}}"/>`,
// `(_ctx, _cache) => {
// return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1, d: _ctx.j, e: _ctx.k, f: _ctx.l, g: _ctx.m, h: _normalizeClass({ ..._ctx.n }), i: _normalizeClass({ ...{ a: true } }), j: _normalizeClass({ ...{ b: _ctx.o } }) }
// }`
// )
// })
// test('v-bind:style array syntax', () => {
// assert(
// `<view :style="[classA, classB]"/>`,
// `<view style="{{[a, b]}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
// }`
// )
// assert(
// `<view :style="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]"/>`,
// `<view style="{{[a, b, c, 'classE', d, e, f, g, h]}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass([..._ctx.classH]), g: _normalizeClass([...[_ctx.classI, _ctx.classJ]]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
// }`
// )
// })
// test('v-bind:style array syntax + style', () => {
// assert(
// `<view :style="[classA, classB]" style="foo bar"/>`,
// `<view style="{{[a, b, 'foo bar']}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
// }`
// )
// assert(
// `<view style="foo bar" :style="[classA, classB]"/>`,
// `<view style="{{['foo bar', a, b]}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB) }
// }`
// )
// assert(
// `<view :style="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]" style="foo bar"/>`,
// `<view style="{{[a, b, c, 'classE', d, e, f, g, h, 'foo bar']}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass([..._ctx.classH]), g: _normalizeClass([...[_ctx.classI, _ctx.classJ]]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
// }`
// )
// assert(
// `<view style="foo bar" :style="[classA, classB, { classC: isC, classD: isD }, 'classE', isF ? 'classF' : '', isG && 'classG', ...classH, ...[classI,classJ], handle(classK) ]"/>`,
// `<view style="{{['foo bar', a, b, c, 'classE', d, e, f, g, h]}}"/>`,
// `(_ctx, _cache) => {
// return { a: _normalizeClass(_ctx.classA), b: _normalizeClass(_ctx.classB), c: _normalizeClass({ classC: _ctx.isC, classD: _ctx.isD }), d: _normalizeClass(_ctx.isF ? 'classF' : ''), e: _normalizeClass(_ctx.isG && 'classG'), f: _normalizeClass([..._ctx.classH]), g: _normalizeClass([...[_ctx.classI, _ctx.classJ]]), h: _normalizeClass(_ctx.handle(_ctx.classK)) }
// }`
// )
// })
})
......@@ -33,10 +33,10 @@ function assert(
describe('compiler', () => {
test('should wrap as function if expression is inline statement', () => {
assert(
`<view :class="{ a: 1, b: 0, c: true, d: false, e: null, f: undefined, g: ok, h: handle(ok), i: ok>1 }"/>`,
`<view class="{{['a', 'c', a && 'g', b && 'h', c && 'i' ]}}"/>`,
`<view :class="{ ...{red:red} }"/>`,
`<view class="{{[ a ]}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 }
return { a: _normalizeClass({ red: _ctx.red }) }
}`,
{}
)
......
......@@ -23,17 +23,14 @@ import {
Pattern,
RestElement,
ArrowFunctionExpression,
logicalExpression,
stringLiteral,
StringLiteral,
isIdentifier,
isStringLiteral,
isLiteral,
isBooleanLiteral,
isBigIntLiteral,
isDecimalLiteral,
Literal,
LogicalExpression,
isNullLiteral,
} from '@babel/types'
import {
......@@ -167,37 +164,11 @@ function createVForArrowFunctionExpression({
)
}
export function createClassBindingArrayExpression(expr: ObjectExpression) {
const elements: (LogicalExpression | StringLiteral)[] = []
expr.properties.forEach((prop) => {
const { value } = prop as ObjectProperty
if (isUndefined(value as Expression)) {
// remove {a:undefined}
return
}
if (isLiteral(value)) {
// {a:true,b:1,c:0} => ['a','b']
if (isTrueExpr(value)) {
elements.push(parseStringLiteral((prop as ObjectProperty).key))
}
return
}
elements.push(
logicalExpression(
'&&',
value as Expression,
parseStringLiteral((prop as ObjectProperty).key)
)
)
})
return arrayExpression(elements)
}
export function isUndefined(expr: Expression) {
return isIdentifier(expr) && expr.name === 'undefined'
}
function isTrueExpr(expr: Literal) {
export function isTrueExpr(expr: Literal) {
if (isNullLiteral(expr)) {
return false
}
......@@ -213,7 +184,7 @@ function isTrueExpr(expr: Literal) {
return true
}
function parseStringLiteral(
export function parseStringLiteral(
expr: Expression | Identifier | StringLiteral | NumericLiteral
) {
if (isIdentifier(expr)) {
......
......@@ -2,8 +2,10 @@ import { registerRuntimeHelpers } from '@vue/compiler-core'
export const V_ON = Symbol(`vOn`)
export const V_FOR = Symbol(`vFor`)
export const HYPHENATE = Symbol(`hyphenate`)
registerRuntimeHelpers({
[V_ON]: 'vOn',
[V_FOR]: 'vFor',
[HYPHENATE]: 'hyphenate',
})
import {
Expression,
isObjectExpression,
isArrayExpression,
arrayExpression,
stringLiteral,
ArrayExpression,
isStringLiteral,
identifier,
isSpreadElement,
ObjectExpression,
objectProperty,
booleanLiteral,
isObjectProperty,
Identifier,
isLiteral,
isIdentifier,
LogicalExpression,
logicalExpression,
StringLiteral,
} from '@babel/types'
import {
DirectiveNode,
NodeTypes,
AttributeNode,
createSimpleExpression,
ExpressionNode,
createCompoundExpression,
NORMALIZE_CLASS,
SourceLocation,
} from '@vue/compiler-core'
import { parseExpr, isTrueExpr, isUndefined, parseStringLiteral } from '../ast'
import { genBabelExpr } from '../codegen'
import { TransformContext } from '../transform'
import {
parseExprWithRewrite,
rewriteExpression,
rewriteSpreadElement,
} from './utils'
export function isClassBinding({ arg, exp }: DirectiveNode) {
return (
arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.content === 'class'
)
}
export function findStaticClassIndex(props: (AttributeNode | DirectiveNode)[]) {
return props.findIndex((prop) => prop.name === 'class')
}
export function rewriteClass(
index: number,
classBindingProp: DirectiveNode,
props: (AttributeNode | DirectiveNode)[],
context: TransformContext
) {
if (!classBindingProp.exp) {
return
}
const expr = parseExpr(classBindingProp.exp, context)
if (!expr) {
return
}
let classBidingExpr: Expression = expr
if (isObjectExpression(expr)) {
classBidingExpr = createClassBindingByObjectExpression(
rewriteClassObjectExpression(expr, classBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
classBidingExpr = createClassBindingByArrayExpression(
rewriteClassArrayExpression(expr, context)
)
} else {
classBidingExpr = parseExpr(
rewriteClassExpression(classBindingProp.exp, context).content,
context
) as Expression
}
const staticClassPropIndex = findStaticClassIndex(props)
if (staticClassPropIndex > -1) {
const staticClass = (props[staticClassPropIndex] as AttributeNode).value!
.content
if (staticClass.trim()) {
if (!isArrayExpression(classBidingExpr)) {
classBidingExpr = arrayExpression([classBidingExpr])
}
if (index > staticClassPropIndex) {
classBidingExpr.elements.unshift(stringLiteral(staticClass))
} else {
classBidingExpr.elements.push(stringLiteral(staticClass))
}
}
}
classBindingProp.exp = createSimpleExpression(genBabelExpr(classBidingExpr))
}
function rewriteClassExpression(
expr: ExpressionNode,
context: TransformContext
) {
return rewriteExpression(
createCompoundExpression([
context.helperString(NORMALIZE_CLASS) + '(',
expr,
')',
]),
context
)
}
function rewriteClassArrayExpression(
expr: ArrayExpression,
context: TransformContext
) {
expr.elements.forEach((prop, index) => {
if (!isStringLiteral(prop)) {
const code = genBabelExpr(
arrayExpression([isSpreadElement(prop) ? prop.argument : prop])
)
expr.elements[index] = identifier(
rewriteClassExpression(
createSimpleExpression(code.slice(1, -1), false),
context
).content
)
}
})
return expr
}
function rewriteClassObjectExpression(
expr: ObjectExpression,
loc: SourceLocation,
context: TransformContext
) {
expr.properties.forEach((prop, index) => {
if (isSpreadElement(prop)) {
// <view :class="{...obj}"/>
// <view class="{{[a]}}"/>
const newExpr = rewriteSpreadElement(NORMALIZE_CLASS, prop, loc, context)
if (newExpr) {
expr.properties[index] = objectProperty(
newExpr,
booleanLiteral(true),
true
)
}
} else if (isObjectProperty(prop)) {
const { key, value, computed } = prop
if (computed) {
// {[handle(computedKey)]:1} => {[a]:1}
prop.key = parseExprWithRewrite(
genBabelExpr(key as Expression),
loc,
context,
key as Expression
) as Identifier
}
if (isLiteral(value)) {
return
} else {
const newExpr = parseExprWithRewrite(
genBabelExpr(value as Expression),
loc,
context,
value as Expression
)
if (newExpr) {
prop.value = newExpr
}
}
}
})
return expr
}
function createClassBindingByArrayExpression(expr: ArrayExpression) {
const elements: (StringLiteral | Identifier)[] = []
expr.elements.forEach((prop) => {
if (isStringLiteral(prop) || isIdentifier(prop)) {
elements.push(prop)
}
})
return arrayExpression(elements)
}
function createClassBindingByObjectExpression(expr: ObjectExpression) {
const elements: (LogicalExpression | StringLiteral | Identifier)[] = []
expr.properties.forEach((prop) => {
if (isObjectProperty(prop)) {
const { value } = prop
if (isUndefined(value as Expression)) {
// remove {a:undefined}
return
}
if (isLiteral(value)) {
// {a:true,b:1,c:0} => ['a','b']
if (isTrueExpr(value)) {
elements.push(
prop.computed
? (prop.key as Identifier)
: parseStringLiteral(prop.key)
)
}
return
}
elements.push(
logicalExpression(
'&&',
value as Expression,
prop.computed ? prop.key : parseStringLiteral(prop.key)
)
)
}
})
return arrayExpression(elements)
}
import { BaseNode } from 'estree'
import { walk } from 'estree-walker'
import {
Expression,
isIdentifier,
isLiteral,
isObjectExpression,
isReferenced,
ObjectExpression,
ObjectProperty,
stringLiteral,
} from '@babel/types'
import {
AttributeNode,
createCompoundExpression,
createSimpleExpression,
DirectiveNode,
ExpressionNode,
NodeTypes,
SimpleExpressionNode,
SourceLocation,
TO_DISPLAY_STRING,
} from '@vue/compiler-core'
import { NodeTransform } from '../transform'
import { isForElementNode } from './vFor'
import { rewriteExpression } from './utils'
import { isSelfKey, rewriteSelfKey } from './transformKey'
import {
createClassBindingArrayExpression,
createObjectProperty,
isUndefined,
parseExpr,
} from '../ast'
import { genBabelExpr, genExpr } from '../codegen'
import { CodegenScope, CodegenVForScope } from '../options'
findStaticClassIndex,
isClassBinding,
rewriteClass,
} from './transformClass'
import {
isRootScope,
isVForScope,
isVIfScope,
NodeTransform,
TransformContext,
} from '../transform'
import { ForElementNode, isForElementNode } from './vFor'
findStaticStyleIndex,
isStyleBinding,
rewriteStyle,
} from './transformStyle'
export const transformIdentifier: NodeTransform = (node, context) => {
return () => {
......@@ -52,6 +33,8 @@ export const transformIdentifier: NodeTransform = (node, context) => {
} else if (node.type === NodeTypes.ELEMENT) {
const vFor = isForElementNode(node) && node.vFor
const { props } = node
let hasClassBinding = false
let hasStyleBinding = false
for (let i = 0; i < props.length; i++) {
const dir = props[i]
if (dir.type === NodeTypes.DIRECTIVE) {
......@@ -69,196 +52,29 @@ export const transformIdentifier: NodeTransform = (node, context) => {
if (isSelfKey(dir, vFor)) {
rewriteSelfKey(dir)
} else if (isClassBinding(dir)) {
hasClassBinding = true
rewriteClass(i, dir, props, context)
} else if (isStyleBinding(dir)) {
hasStyleBinding = true
rewriteStyle(i, dir, props, context)
} else {
dir.exp = rewriteExpression(exp, context)
}
}
}
}
}
}
}
function isSelfKey(
{ arg, exp }: DirectiveNode,
vFor: ForElementNode['vFor'] | false
) {
return (
vFor &&
arg &&
exp &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.content === 'key' &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
exp.content === vFor.valueAlias
)
}
function rewriteSelfKey(dir: DirectiveNode) {
;(dir.exp as SimpleExpressionNode).content = '*this'
}
function isClassBinding({ arg, exp }: DirectiveNode) {
return (
arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.content === 'class'
)
}
function findStaticClassIndex(props: (AttributeNode | DirectiveNode)[]) {
return props.findIndex((prop) => prop.name === 'class')
}
function rewriteClass(
index: number,
classBindingProp: DirectiveNode,
props: (AttributeNode | DirectiveNode)[],
context: TransformContext
) {
if (!classBindingProp.exp) {
return
}
const staticClassPropIndex = findStaticClassIndex(props)
const staticClass =
staticClassPropIndex > -1
? (props[staticClassPropIndex] as AttributeNode).value!.content
: ''
const expr = parseExpr(classBindingProp.exp, context)
if (!expr) {
return
}
if (isObjectExpression(expr)) {
// 重写{ key:value }所有的 value
rewriteObjectExpression(expr, classBindingProp.loc, context)
const arrExpr = createClassBindingArrayExpression(expr)
if (staticClass) {
if (index > staticClassPropIndex) {
arrExpr.elements.unshift(stringLiteral(staticClass))
} else {
arrExpr.elements.push(stringLiteral(staticClass))
}
}
classBindingProp.exp = createSimpleExpression(genBabelExpr(arrExpr))
} else {
classBindingProp.exp = rewriteExpression(classBindingProp.exp, context)
}
}
function rewriteObjectExpression(
expr: ObjectExpression,
loc: SourceLocation,
context: TransformContext
) {
expr.properties.forEach((prop) => {
const { value } = prop as ObjectProperty
if (isLiteral(value)) {
return
} else {
const newExpr = parseExpr(
rewriteExpression(
createSimpleExpression(genBabelExpr(value as Expression), false, loc),
context,
value as Expression
),
context
)
if (newExpr) {
;(prop as ObjectProperty).value = newExpr
if (hasClassBinding) {
const staticClassIndex = findStaticClassIndex(props)
if (staticClassIndex > -1) {
props.splice(staticClassIndex, 1)
}
}
}
})
}
export function rewriteExpression(
node: ExpressionNode,
context: TransformContext,
babelNode?: Expression,
scope: CodegenScope = context.currentScope
) {
if (node.type === NodeTypes.SIMPLE_EXPRESSION && node.isStatic) {
return node
}
if (!babelNode) {
const code = genExpr(node)
babelNode = parseExpr(code, context, node)
if (!babelNode) {
return createSimpleExpression(code)
}
}
if (isUndefined(babelNode)) {
return createSimpleExpression('undefined', false, node.loc)
}
scope = findScope(babelNode, scope)!
const id = scope.id.next()
scope.properties.push(createObjectProperty(id, babelNode!))
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
const firstChild = node.children[0]
if (isSimpleExpression(firstChild)) {
const content = firstChild.content.trim()
if (scope.identifiers.includes(content)) {
return createSimpleExpression(content + '.' + id)
if (hasStyleBinding) {
const staticStyleIndex = findStaticStyleIndex(props)
if (staticStyleIndex > -1) {
props.splice(staticStyleIndex, 1)
}
}
}
}
return createSimpleExpression(id)
}
// function findReferencedScope(
// node: Expression,
// scope: CodegenScope
// ): CodegenRootScope | CodegenVForScope {
// if (isRootScope(scope)) {
// return scope
// }
// }
function findScope(node: Expression, scope: CodegenScope) {
if (isRootScope(scope) || isVIfScope(scope)) {
return scope
}
return findVForScope(node, scope) || scope
}
function findVForScope(
node: Expression,
scope: CodegenScope
): CodegenVForScope | undefined {
if (isVForScope(scope)) {
if (isReferencedScope(node, scope)) {
return scope
}
}
// if (scope.parent) {
// return findVForScope(node, scope.parent)
// }
}
function isReferencedScope(node: Expression, scope: CodegenVForScope) {
const knownIds: string[] = scope.locals
let referenced = false
walk(node as unknown as BaseNode, {
enter(node: BaseNode, parent: BaseNode) {
if (referenced) {
return this.skip()
}
if (!isIdentifier(node)) {
return
}
if (
parent &&
knownIds.includes(node.name) &&
isReferenced(node, parent as any)
) {
referenced = true
return this.skip()
}
},
})
return referenced
}
function isSimpleExpression(val: any): val is SimpleExpressionNode {
return val.type && val.type === NodeTypes.SIMPLE_EXPRESSION
}
import {
DirectiveNode,
NodeTypes,
SimpleExpressionNode,
} from '@vue/compiler-core'
import { ForElementNode } from './vFor'
export function isSelfKey(
{ arg, exp }: DirectiveNode,
vFor: ForElementNode['vFor'] | false
) {
return (
vFor &&
arg &&
exp &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.content === 'key' &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
exp.content === vFor.valueAlias
)
}
export function rewriteSelfKey(dir: DirectiveNode) {
;(dir.exp as SimpleExpressionNode).content = '*this'
}
import {
Expression,
isObjectExpression,
isArrayExpression,
arrayExpression,
stringLiteral,
ArrayExpression,
isStringLiteral,
isSpreadElement,
identifier,
ObjectExpression,
isLiteral,
isObjectProperty,
binaryExpression,
} from '@babel/types'
import {
DirectiveNode,
NodeTypes,
AttributeNode,
createSimpleExpression,
ExpressionNode,
createCompoundExpression,
NORMALIZE_STYLE,
SourceLocation,
} from '@vue/compiler-core'
import { hyphenate } from '@vue/shared'
import { HYPHENATE } from '../runtimeHelpers'
import { parseExpr, parseStringLiteral } from '../ast'
import { genBabelExpr } from '../codegen'
import { TransformContext } from '../transform'
import {
parseExprWithRewrite,
rewirteWithHelper,
rewriteExpression,
rewriteSpreadElement,
} from './utils'
export function isStyleBinding({ arg, exp }: DirectiveNode) {
return (
arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.content === 'style'
)
}
export function findStaticStyleIndex(props: (AttributeNode | DirectiveNode)[]) {
return props.findIndex((prop) => prop.name === 'style')
}
export function rewriteStyle(
index: number,
styleBindingProp: DirectiveNode,
props: (AttributeNode | DirectiveNode)[],
context: TransformContext
) {
if (!styleBindingProp.exp) {
return
}
const expr = parseExpr(styleBindingProp.exp, context)
if (!expr) {
return
}
let styleBidingExpr: Expression | undefined = expr
if (isObjectExpression(expr)) {
styleBidingExpr = createStyleBindingByObjectExpression(
rewriteStyleObjectExpression(expr, styleBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
styleBidingExpr = createStyleBindingByArrayExpression(
rewriteStyleArrayExpression(expr, context)
)
} else {
styleBidingExpr = parseExpr(
rewriteStyleExpression(styleBindingProp.exp, context).content,
context
) as Expression
}
if (!styleBidingExpr) {
return
}
const staticStylePropIndex = findStaticStyleIndex(props)
if (staticStylePropIndex > -1) {
const staticStyle = (props[staticStylePropIndex] as AttributeNode).value!
.content
if (staticStyle.trim()) {
if (index > staticStylePropIndex) {
styleBidingExpr = binaryExpression(
'+',
addSemicolon(stringLiteral(staticStyle)),
styleBidingExpr
)
} else {
styleBidingExpr = binaryExpression(
'+',
addSemicolon(styleBidingExpr),
stringLiteral(staticStyle)
)
}
}
}
styleBindingProp.exp = createSimpleExpression(genBabelExpr(styleBidingExpr))
}
function rewriteStyleExpression(
expr: ExpressionNode,
context: TransformContext
) {
return rewriteExpression(
createCompoundExpression([
context.helperString(NORMALIZE_STYLE) + '(',
expr,
')',
]),
context
)
}
function rewriteStyleArrayExpression(
expr: ArrayExpression,
context: TransformContext
) {
expr.elements.forEach((prop, index) => {
if (!isStringLiteral(prop)) {
const code = genBabelExpr(arrayExpression([prop]))
expr.elements[index] = identifier(
rewriteStyleExpression(
createSimpleExpression(
isSpreadElement(prop) ? code : code.slice(1, -1),
false
),
context
).content
)
}
})
return expr
}
function rewriteStyleObjectExpression(
expr: ObjectExpression,
loc: SourceLocation,
context: TransformContext
) {
expr.properties.forEach((prop, index) => {
if (isSpreadElement(prop)) {
// <view :style="{...obj}"/>
// <view style="{{a}}"/>
const newExpr = rewriteSpreadElement(NORMALIZE_STYLE, prop, loc, context)
if (newExpr) {
prop.argument = newExpr
}
} else if (isObjectProperty(prop)) {
const { key, value, computed } = prop
if (computed) {
// {[handle(computedKey)]:1} => {[a]:1}
const newExpr = rewirteWithHelper(HYPHENATE, key, loc, context)
if (newExpr) {
prop.key = newExpr
}
} else {
// {fontSize:'15px'} => {'font-size':'15px'}
prop.key = parseStringLiteral(prop.key)
prop.key.value = hyphenate(prop.key.value) + ':'
}
if (isLiteral(value)) {
return
} else {
const newExpr = parseExprWithRewrite(
genBabelExpr(value as Expression),
loc,
context,
value as Expression
)
if (newExpr) {
prop.value = newExpr
}
}
}
})
return expr
}
function addSemicolon(expr: Expression) {
return createBinaryExpression(expr, stringLiteral(';'))
}
function createBinaryExpression(left: Expression, right: Expression) {
return binaryExpression('+', left, right)
}
function createStyleBindingByArrayExpression(expr: ArrayExpression) {
let result: Expression | undefined
return result
}
function createStyleBindingByObjectExpression(expr: ObjectExpression) {
let result: Expression | undefined
function concat(expr: Expression) {
if (!result) {
result = expr
} else {
result = createBinaryExpression(addSemicolon(result), expr)
}
}
expr.properties.forEach((prop) => {
if (isSpreadElement(prop)) {
concat(prop.argument)
} else if (isObjectProperty(prop)) {
const { key, value } = prop
const expr = createBinaryExpression(
isStringLiteral(key)
? key // 之前已经补充了:
: createBinaryExpression(key, stringLiteral(':')),
value as Expression
)
concat(expr)
}
})
return result
}
import {
Expression,
Identifier,
identifier,
isIdentifier,
isReferenced,
MemberExpression,
objectProperty,
SpreadElement,
} from '@babel/types'
import {
createSimpleExpression,
ExpressionNode,
NodeTypes,
SimpleExpressionNode,
SourceLocation,
} from '@vue/compiler-core'
import { walk, BaseNode } from 'estree-walker'
import { isUndefined, parseExpr } from '../ast'
import { genBabelExpr, genExpr } from '../codegen'
import { CodegenScope, CodegenVForScope } from '../options'
import {
isRootScope,
isVForScope,
isVIfScope,
TransformContext,
} from '../transform'
export function rewriteSpreadElement(
name: symbol,
expr: SpreadElement,
loc: SourceLocation,
context: TransformContext
) {
return rewirteWithHelper(name, expr.argument, loc, context)
}
export function rewirteWithHelper(
name: symbol,
expr: Expression,
loc: SourceLocation,
context: TransformContext
) {
return parseExprWithRewrite(
context.helperString(name) + '(' + genBabelExpr(expr) + ')',
loc,
context
)
}
export function parseExprWithRewrite(
code: string,
loc: SourceLocation,
context: TransformContext,
node?: Expression
) {
return parseExpr(
rewriteExpression(createSimpleExpression(code, false, loc), context, node),
context
) as Identifier | MemberExpression | undefined
}
export function rewriteExpression(
node: ExpressionNode,
context: TransformContext,
babelNode?: Expression,
scope: CodegenScope = context.currentScope
) {
if (node.type === NodeTypes.SIMPLE_EXPRESSION && node.isStatic) {
return node
}
if (!babelNode) {
const code = genExpr(node)
babelNode = parseExpr(code, context, node)
if (!babelNode) {
return createSimpleExpression(code)
}
}
if (isUndefined(babelNode)) {
return createSimpleExpression('undefined', false, node.loc)
}
scope = findScope(babelNode, scope)!
const id = scope.id.next()
scope.properties.push(objectProperty(identifier(id), babelNode!))
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
const firstChild = node.children[0]
if (isSimpleExpression(firstChild)) {
const content = firstChild.content.trim()
if (scope.identifiers.includes(content)) {
return createSimpleExpression(content + '.' + id)
}
}
}
return createSimpleExpression(id)
}
// function findReferencedScope(
// node: Expression,
// scope: CodegenScope
// ): CodegenRootScope | CodegenVForScope {
// if (isRootScope(scope)) {
// return scope
// }
// }
function findScope(node: Expression, scope: CodegenScope) {
if (isRootScope(scope) || isVIfScope(scope)) {
return scope
}
return findVForScope(node, scope) || scope
}
function findVForScope(
node: Expression,
scope: CodegenScope
): CodegenVForScope | undefined {
if (isVForScope(scope)) {
if (isReferencedScope(node, scope)) {
return scope
}
}
// if (scope.parent) {
// return findVForScope(node, scope.parent)
// }
}
function isReferencedScope(node: Expression, scope: CodegenVForScope) {
const knownIds: string[] = scope.locals
let referenced = false
walk(node as unknown as BaseNode, {
enter(node: BaseNode, parent: BaseNode) {
if (referenced) {
return this.skip()
}
if (!isIdentifier(node)) {
return
}
if (
parent &&
knownIds.includes(node.name) &&
isReferenced(node, parent as any)
) {
referenced = true
return this.skip()
}
},
})
return referenced
}
function isSimpleExpression(val: any): val is SimpleExpressionNode {
return val.type && val.type === NodeTypes.SIMPLE_EXPRESSION
}
......@@ -26,8 +26,7 @@ import {
import { CodegenScope } from '../options'
import { NodeTransform, TransformContext, traverseNode } from '../transform'
import { processExpression } from './transformExpression'
import { rewriteExpression } from './transformIdentifier'
import { rewriteExpression } from './utils'
interface IfOptions {
name: string
condition?: string
......
......@@ -7,5 +7,6 @@ export function createApp(rootComponent: unknown, rootProps = null) {
}
export const createSSRApp = createApp
export * from './helpers'
export { hyphenate } from '@vue/shared'
// @ts-ignore
export * from '../lib/vue.runtime.esm.js'
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册