From 0de82f656666b37c7d9c4916865eede527f2af22 Mon Sep 17 00:00:00 2001 From: fxy060608 Date: Wed, 13 Oct 2021 21:36:14 +0800 Subject: [PATCH] wip(mp): v-bind:class object syntax --- .../uni-mp-compiler/__tests__/class.spec.ts | 60 ++++++++ .../uni-mp-compiler/__tests__/test.spec.ts | 6 +- packages/uni-mp-compiler/src/ast.ts | 70 +++++++++ packages/uni-mp-compiler/src/codegen.ts | 16 +- .../src/transforms/transformElement.ts | 74 ++++++++- .../src/transforms/transformIdentifier.ts | 141 +++++++++++++++--- 6 files changed, 339 insertions(+), 28 deletions(-) create mode 100644 packages/uni-mp-compiler/__tests__/class.spec.ts diff --git a/packages/uni-mp-compiler/__tests__/class.spec.ts b/packages/uni-mp-compiler/__tests__/class.spec.ts new file mode 100644 index 000000000..fc2c9f033 --- /dev/null +++ b/packages/uni-mp-compiler/__tests__/class.spec.ts @@ -0,0 +1,60 @@ +import { assert } from './testUtils' + +describe('compiler: transform class', () => { + test(`static class`, () => { + assert( + ``, + ``, + `(_ctx, _cache) => { + return {} +}` + ) + assert( + ``, + ``, + `(_ctx, _cache) => { + return {} +}` + ) + assert( + ``, + ``, + `(_ctx, _cache) => { + return {} +}` + ) + }) + test('v-bind:class basic', () => { + assert( + ``, + ``, + `(_ctx, _cache) => { + return { a: _ctx.foo } +}` + ) + assert( + ``, + ``, + `(_ctx, _cache) => { + return { a: _ctx.foo | _ctx.bar } +}` + ) + }) + test('v-bind:class object syntax', () => { + assert( + ``, + ``, + `(_ctx, _cache) => { + return { a: _ctx.isRed } +}` + ) + assert( + ``, + ``, + `(_ctx, _cache) => { + return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 } +}` + ) + }) + test('v-bind:class array syntax', () => {}) +}) diff --git a/packages/uni-mp-compiler/__tests__/test.spec.ts b/packages/uni-mp-compiler/__tests__/test.spec.ts index 894a21f5f..87e0bab99 100644 --- a/packages/uni-mp-compiler/__tests__/test.spec.ts +++ b/packages/uni-mp-compiler/__tests__/test.spec.ts @@ -33,10 +33,10 @@ function assert( describe('compiler', () => { test('should wrap as function if expression is inline statement', () => { assert( - `
`, - ``, + ``, + ``, `(_ctx, _cache) => { - return { a: _vOn(() => {}) } + return { a: _ctx.ok, b: _ctx.handle(_ctx.ok), c: _ctx.ok > 1 } }`, {} ) diff --git a/packages/uni-mp-compiler/src/ast.ts b/packages/uni-mp-compiler/src/ast.ts index a724dbc4c..59876eacc 100644 --- a/packages/uni-mp-compiler/src/ast.ts +++ b/packages/uni-mp-compiler/src/ast.ts @@ -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('') +} diff --git a/packages/uni-mp-compiler/src/codegen.ts b/packages/uni-mp-compiler/src/codegen.ts index 1ca73d0db..4c0d904d9 100644 --- a/packages/uni-mp-compiler/src/codegen.ts +++ b/packages/uni-mp-compiler/src/codegen.ts @@ -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 diff --git a/packages/uni-mp-compiler/src/transforms/transformElement.ts b/packages/uni-mp-compiler/src/transforms/transformElement.ts index 48d9b6f7d..a7e55b289 100644 --- a/packages/uni-mp-compiler/src/transforms/transformElement.ts +++ b/packages/uni-mp-compiler/src/transforms/transformElement.ts @@ -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) +// } +// } diff --git a/packages/uni-mp-compiler/src/transforms/transformIdentifier.ts b/packages/uni-mp-compiler/src/transforms/transformIdentifier.ts index 80a4250d7..dbf2484fd 100644 --- a/packages/uni-mp-compiler/src/transforms/transformIdentifier.ts +++ b/packages/uni-mp-compiler/src/transforms/transformIdentifier.ts @@ -1,16 +1,33 @@ 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() -- GitLab