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