提交 0de82f65 编写于 作者: fxy060608's avatar fxy060608

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

上级 f209ff01
import { assert } from './testUtils'
describe('compiler: transform class', () => {
test(`static class`, () => {
assert(
`<view class="foo"/>`,
`<view class="foo"/>`,
`(_ctx, _cache) => {
return {}
}`
)
assert(
`<view class="foo bar"/>`,
`<view class="foo bar"/>`,
`(_ctx, _cache) => {
return {}
}`
)
assert(
`<view class="foo bar"/>`,
`<view class="foo bar"/>`,
`(_ctx, _cache) => {
return {}
}`
)
})
test('v-bind:class basic', () => {
assert(
`<view :class="foo"/>`,
`<view class="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.foo }
}`
)
assert(
`<view :class="foo | bar"/>`,
`<view class="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.foo | _ctx.bar }
}`
)
})
test('v-bind:class object syntax', () => {
assert(
`<view :class="{ red: isRed }"/>`,
`<view class="{{[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 }"/>`,
`<view class="{{['a', 'c', a && 'g', b && 'h', c && 'i']}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 }
}`
)
})
test('v-bind:class array syntax', () => {})
})
......@@ -33,10 +33,10 @@ function assert(
describe('compiler', () => {
test('should wrap as function if expression is inline statement', () => {
assert(
`<div v-on:click.prevent />`,
`<view id="{{id}}"/>`,
`<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' ]}}"/>`,
`(_ctx, _cache) => {
return { a: _vOn(() => {}) }
return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 }
}`,
{}
)
......
......@@ -23,6 +23,18 @@ import {
Pattern,
RestElement,
ArrowFunctionExpression,
logicalExpression,
stringLiteral,
StringLiteral,
isIdentifier,
isStringLiteral,
isLiteral,
isBooleanLiteral,
isBigIntLiteral,
isDecimalLiteral,
Literal,
LogicalExpression,
isNullLiteral,
} from '@babel/types'
import {
createCompilerError,
......@@ -154,3 +166,61 @@ function createVForArrowFunctionExpression({
blockStatement([returnStatement(objectExpression(properties))])
)
}
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) {
if (isNullLiteral(expr)) {
return false
}
if (
isStringLiteral(expr) ||
isNumericLiteral(expr) ||
isBooleanLiteral(expr) ||
isBigIntLiteral(expr) ||
isDecimalLiteral(expr)
) {
return !!expr.value
}
return true
}
function parseStringLiteral(
expr: Expression | Identifier | StringLiteral | NumericLiteral
) {
if (isIdentifier(expr)) {
return stringLiteral(expr.name)
}
if (isStringLiteral(expr)) {
return stringLiteral(expr.value)
}
return stringLiteral('')
}
......@@ -13,6 +13,7 @@ import {
import { default as babelGenerate } from '@babel/generator'
import { CodegenOptions, CodegenScope } from './options'
import { createObjectExpression } from './ast'
import { Expression } from '@babel/types'
interface CodegenContext extends CodegenOptions {
code: string
......@@ -81,11 +82,7 @@ export function generate(
}
push(`return `)
push(
babelGenerate(createObjectExpression(scope.properties), {
concise: true,
}).code
)
push(genBabelExpr(createObjectExpression(scope.properties)))
if (useWithBlock) {
deindent()
push(`}`)
......@@ -208,6 +205,15 @@ function createGenNodeContext() {
return context
}
export function genBabelExpr(expr: Expression) {
return babelGenerate(expr, {
concise: true,
jsescOption: {
quotes: 'single',
},
}).code
}
export function genExpr(
node: CodegenNode | symbol | string,
context?: GenNodeContext
......
......@@ -106,13 +106,83 @@ function processProps(node: ElementNode, context: TransformContext) {
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
const { props } = directiveTransform(prop, node, context)
prop.exp = props[0].value as ExpressionNode
prop.exp = directiveTransform(prop, node, context).props[0]
.value as ExpressionNode
// const { arg } = prop
// if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && prop.exp) {
// const { content } = arg
// if (content === 'class') {
// hasClassBinding = true
// processClass(prop, props, context)
// } else if (content === 'style') {
// hasStyleBinding = true
// processStyle(prop, props, context)
// }
// }
}
}
}
// remove static class and static style
// if (hasClassBinding) {
// const staticClassPropIndex = findStaticClassIndex(props)
// if (staticClassPropIndex > -1) {
// props.splice(staticClassPropIndex, 1)
// }
// }
// if (hasStyleBinding) {
// const staticStylePropIndex = findStaticStyleIndex(props)
// if (staticStylePropIndex > -1) {
// props.splice(staticStylePropIndex, 1)
// }
// }
}
function isComponentTag(tag: string) {
return tag[0].toLowerCase() + tag.slice(1) === 'component'
}
// function findStaticClassIndex(props: (AttributeNode | DirectiveNode)[]) {
// return props.findIndex((prop) => prop.name === 'class')
// }
// function findStaticStyleIndex(props: (AttributeNode | DirectiveNode)[]) {
// return props.findIndex((prop) => prop.name === 'style')
// }
// function processClass(
// classBindingProp: DirectiveNode,
// props: (AttributeNode | DirectiveNode)[],
// context: TransformContext
// ) {
// if (!classBindingProp.exp) {
// return
// }
// const staticClassPropIndex = findStaticClassIndex(props)
// const staticClass =
// staticClassPropIndex > -1
// ? (props[staticClassPropIndex] as AttributeNode).value
// : ''
// const expr = parseExpr(classBindingProp.exp, context)
// if (!expr) {
// return
// }
// console.log(staticClass)
// if (isObjectExpression(expr)) {
// classBindingProp.exp = createSimpleExpression(
// genBabelExpr(createClassBindingArrayExpression(expr))
// )
// }
// }
// function processStyle(
// styleBindingPropprop: DirectiveNode,
// props: (AttributeNode | DirectiveNode)[],
// context: TransformContext
// ) {
// const staticStylePropIndex = findStaticStyleIndex(props)
// const staticStyle =
// staticStylePropIndex > -1
// ? (props[staticStylePropIndex] as AttributeNode).value
// : ''
// if (staticStyle) {
// console.log(staticStyle)
// }
// }
import { BaseNode } from 'estree'
import { walk } from 'estree-walker'
import { Expression, isIdentifier, isReferenced } from '@babel/types'
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 { createObjectProperty, parseExpr } from '../ast'
import { genExpr } from '../codegen'
import {
createClassBindingArrayExpression,
createObjectProperty,
isUndefined,
parseExpr,
} from '../ast'
import { genBabelExpr, genExpr } from '../codegen'
import { CodegenScope, CodegenVForScope } from '../options'
import {
isRootScope,
......@@ -19,7 +36,7 @@ import {
NodeTransform,
TransformContext,
} from '../transform'
import { isForElementNode } from './vFor'
import { ForElementNode, isForElementNode } from './vFor'
export const transformIdentifier: NodeTransform = (node, context) => {
return () => {
......@@ -34,32 +51,28 @@ export const transformIdentifier: NodeTransform = (node, context) => {
)
} else if (node.type === NodeTypes.ELEMENT) {
const vFor = isForElementNode(node) && node.vFor
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
const { props } = node
for (let i = 0; i < props.length; i++) {
const dir = props[i]
if (dir.type === NodeTypes.DIRECTIVE) {
const arg = dir.arg
if (arg) {
// TODO 指令暂不不支持动态参数,v-bind:[arg] v-on:[event]
if (!(arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic)) {
node.props.splice(i, 1)
props.splice(i, 1)
i--
continue
}
}
const exp = dir.exp
if (exp) {
if (
vFor &&
arg &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.content === 'key' &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
exp.content === vFor.valueAlias
) {
exp.content = '*this'
continue
if (isSelfKey(dir, vFor)) {
rewriteSelfKey(dir)
} else if (isClassBinding(dir)) {
rewriteClass(i, dir, props, context)
} else {
dir.exp = rewriteExpression(exp, context)
}
dir.exp = rewriteExpression(exp, context)
}
}
}
......@@ -67,6 +80,95 @@ export const transformIdentifier: NodeTransform = (node, 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
}
}
})
}
export function rewriteExpression(
node: ExpressionNode,
context: TransformContext,
......@@ -83,6 +185,9 @@ export function rewriteExpression(
return createSimpleExpression(code)
}
}
if (isUndefined(babelNode)) {
return createSimpleExpression('undefined', false, node.loc)
}
scope = findScope(babelNode, scope)!
const id = scope.id.next()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册