Skip to content

Commit

Permalink
enhance: 优化 class、style 的正则转换为 ast 分析
Browse files Browse the repository at this point in the history
  • Loading branch information
anchengjian committed Mar 14, 2018
1 parent ae6e49d commit 896f3c5
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"nightwatch": "^0.9.16",
"nightwatch-helpers": "^1.2.0",
"phantomjs-prebuilt": "^2.1.14",
"prettier": "^1.11.1",
"resolve": "^1.3.3",
"rollup": "^0.45.1",
"rollup-plugin-alias": "^1.3.1",
Expand Down
45 changes: 45 additions & 0 deletions src/platforms/mp/compiler/codegen/babel-plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// babel-plugin-transform-object-to-ternary-operator.js

import * as t from 'babel-types'
import generate from 'babel-generator'
import template from 'babel-template'
import { hyphenate } from 'shared/util'

function getStrByNode (node, onlyStr = false) {
if (onlyStr) {
return node.value || node.name || ''
}
return node.type === 'StringLiteral' ? node : t.stringLiteral(node.name || '')
}

// 把 { key: value } 转换成 [ value ? 'key' : '' ]
const objectVisitor = {
ObjectExpression: function (path) {
const elements = path.node.properties.map(propertyItem => {
return t.conditionalExpression(propertyItem.value, getStrByNode(propertyItem.key), t.stringLiteral(''))
})
path.replaceWith(t.arrayExpression(elements))
}
}

export function transformObjectToTernaryOperator (babel) {
return { visitor: objectVisitor }
}

// 把 { key: value } 转换成 'key:' + value + ';'
const objectToStringVisitor = {
ObjectExpression: function (path) {
const expression = path.node.properties.map(propertyItem => {
const keyStr = getStrByNode(propertyItem.key, true)
const key = keyStr ? hyphenate(keyStr) : keyStr
const { code: val } = generate(t.ExpressionStatement(propertyItem.value))
return `'${key}:' + ${val.slice(0, -1)} + ';'`
}).join('+')

const p = template(expression)({})
path.replaceWith(p.expression)
}
}
export function transformObjectToString (babel) {
return { visitor: objectToStringVisitor }
}
53 changes: 39 additions & 14 deletions src/platforms/mp/compiler/codegen/convert/attrs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import wxmlDirectiveMap from '../config/wxmlDirectiveMap'
import utils from '../utils'
import tagConfig from '../config/config'

import babel from 'babel-core'
import prettier from 'prettier'

import { transformObjectToTernaryOperator, transformObjectToString } from '../babel-plugins'
function transformDynamicClass (staticClass = '', clsBinding) {
const result = babel.transform(`!${clsBinding}`, { plugins: [transformObjectToTernaryOperator] })
// 先实现功能,再优化代码
// https://github.com/babel/babel/issues/7138
const cls = prettier.format(result.code, { semi: false, singleQuote: true }).slice(1).slice(0, -1)
return `${staticClass} {{${cls}}}`
}

function transformDynamicStyle (staticStyle = '', styleBinding) {
const result = babel.transform(`!${styleBinding}`, { plugins: [transformObjectToString] })
const cls = prettier.format(result.code, { semi: false, singleQuote: true }).slice(2).slice(0, -2)
return `${staticStyle} {{${cls}}}`
}

export default {
format (attrs = {}) {
const obj = {}
Expand All @@ -17,7 +34,7 @@ export default {
convertAttr (ast, log) {
const { attrsMap = {}, tag, staticClass } = ast
let attrs = {}
const wxClass = this.styleObj(attrsMap['v-bind:class'], staticClass)
const wxClass = this.classObj(attrsMap['v-bind:class'], staticClass)
wxClass.length ? attrsMap['class'] = wxClass : ''
const wxStyle = this.styleObj(attrsMap['v-bind:style'], attrsMap['style'])
wxStyle.length ? attrsMap['style'] = wxStyle : ''
Expand Down Expand Up @@ -64,7 +81,7 @@ export default {
} else if (/^v\-/.test(key)) {
log(`不支持此属性-> ${key}="${val}"`, 'waring')
} else {
if ((tagConfig.virtualTag.indexOf(tag) > -1) && (key === 'class' || key === 'data-mpcomid')) {
if ((tagConfig.virtualTag.indexOf(tag) > -1) && (key === 'class' || key === 'style' || key === 'data-mpcomid')) {
if (key !== 'data-mpcomid') {
log(`template 不支持此属性-> ${key}="${val}"`, 'waring')
}
Expand Down Expand Up @@ -124,18 +141,26 @@ export default {
return attrs
},

styleObj (styleBinding = '', style) {
let _styleBinding = styleBinding.replace(/[\{\}]/g, '').replace(' ', '').replace(/\n/g, '')
_styleBinding = _styleBinding.split(',').map(v => {
return v.replace(/([^,^:]+)\:([^,]+)/g, (v, $1, $2) => {
const _$1 = utils.toLowerCase($1).replace(/^'|'$/g, '')
return `{{(${$2})\? '${_$1}' \: ' '}}`
})
}).join(' ')
if (_styleBinding.indexOf('{') === -1 && _styleBinding) {
_styleBinding = `{{${_styleBinding}}}`
classObj (clsBinding = '', staticCls) {
if (!clsBinding && !staticCls) {
return ''
}
if (!clsBinding && staticCls) {
return staticCls
}

return transformDynamicClass(staticCls, clsBinding)
},

styleObj (styleBinding = '', staticStyle) {
if (!styleBinding && !staticStyle) {
return ''
}
if (!styleBinding && staticStyle) {
return staticStyle
}
return `${style || ''} ${_styleBinding}`.trim()

return transformDynamicStyle(staticStyle, styleBinding)
},

model (key, val, attrs, tag) {
Expand Down
3 changes: 1 addition & 2 deletions src/platforms/mp/runtime/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export function initMP (mpType, next) {
}
})
} else if (mpType === 'component') {
const app = global.getApp()
global.Component({
// 页面的初始数据
data: {
Expand All @@ -107,7 +106,7 @@ export function initMP (mpType, next) {
methods: {
handleProxy (e) {
rootVueVM.$handleProxyWithVue(e)
},
}
},
// mp lifecycle for vue
// 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用 setData
Expand Down
88 changes: 85 additions & 3 deletions test/mp/compiler/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ const { compile, compileToWxml } = require('../../../packages/mpvue-template-com
// const { strToRegExp } = require('../helpers/index')

function assertCodegen (template, assertTemplate, options, parmas = {}) {
const { errors = [], mpErrors = [], slots = {}} = parmas
const { errors = [], mpErrors = [], slots = {}, mpTips = [] } = parmas
const compiled = compile(template, {})
const output = compileToWxml(compiled, options)
expect(output.compiled.mpErrors).toEqual(mpErrors)
expect(output.compiled.mpTips).toEqual(mpTips)
expect(output.compiled.errors).toEqual(errors)
// console.log(JSON.stringify(output.slots))
expect(JSON.stringify(output.slots)).toEqual(JSON.stringify(slots))
Expand Down Expand Up @@ -114,12 +115,12 @@ describe('指令', () => {
it('v-bind:class', () => {
assertCodegen(
`<div v-bind:class="{ active: isActive }"></div>`,
`<template name="a"><view class="_div {{( isActive )? 'active' : ' '}}"></view></template>`,
`<template name="a"><view class="_div {{[isActive ? 'active' : '']}}"></view></template>`,
{ name: 'a' }
)
assertCodegen(
`<div class="static" v-bind:class="{ active: isActive, 'textDanger': hasError }"></div>`,
`<template name="a"><view class="_div static {{( isActive)? 'active' : ' '}} {{( hasError )? 'text-danger' : ' '}}"></view></template>`,
`<template name="a"><view class="_div static {{[isActive ? 'active' : '', hasError ? 'textDanger' : '']}}"></view></template>`,
{ name: 'a' }
)
assertCodegen(
Expand All @@ -140,6 +141,87 @@ describe('指令', () => {
mpErrors: []
}
)

// object
assertCodegen(
`<div><p :class="{ active: isActive }">233</p></div>`,
`<template name="a"><view class="_div"><view class="_p {{[isActive ? 'active' : '']}}">233</view></view></template>`,
{ name: 'a' }
)
assertCodegen(
`<div><p class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }">233</p></div>`,
`<template name="a"><view class="_div"><view class="_p static {{[isActive ? 'active' : '', hasError ? 'text-danger' : '']}}">233</view></view></template>`,
{ name: 'a' }
)
// TODO, classObject 暂不支持
// assertCodegen(
// `<div><p class="static" v-bind:class="classObject">233</p></div>`,
// `<template name="a"><view class="_div static {{( isActive)? 'active' : ' '}} {{( hasError )? 'text-danger' : ' '}}"></view></template>`,
// { name: 'a' }
// )
// array
assertCodegen(
`<div><p class="static" :class="[activeClass, errorClass]">233</p></div>`,
`<template name="a"><view class="_div"><view class="_p static {{[activeClass, errorClass]}}">233</view></view></template>`,
{ name: 'a' }
)
assertCodegen(
`<div><p class="static" v-bind:class="[isActive ? activeClass : '', errorClass]">233</p></div>`,
`<template name="a"><view class="_div"><view class="_p static {{[isActive ? activeClass : '', errorClass]}}">233</view></view></template>`,
{ name: 'a' }
)
assertCodegen(
`<div><p class="static" v-bind:class="[{ active: isActive }, errorClass]">233</p></div>`,
`<template name="a"><view class="_div"><view class="_p static {{[[isActive ? 'active' : ''], errorClass]}}">233</view></view></template>`,
{ name: 'a' }
)
})

it('v-bind:style', () => {
assertCodegen(
`<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">111</div>`,
`<template name="a"><view class="_div" style=" {{'color:' + activeColor + ';' + 'font-size:' + fontSize + 'px' + ';'}}">111</view></template>`,
{ name: 'a' }
)
assertCodegen(
`<div v-bind:style="[{ color: activeColor, fontSize: fontSize + 'px' }]">111</div>`,
`<template name="a"><view class="_div" style=" {{'color:' + activeColor + ';' + 'font-size:' + fontSize + 'px' + ';'}}">111</view></template>`,
{ name: 'a' }
)
// TODO, 等微信支持了再支持
// assertCodegen(
// `<div v-bind:style="styleObject">222</div>`,
// `<template name="a"><view class="_div" style=" {{tyleObjec}}">222</view></template>`,
// { name: 'a' }
// )
// assertCodegen(
// `<div v-bind:style="[baseStyles, overridingStyles]">333</div>`,
// `<template name="a"><view class="_div" style=" {{baseStyles, overridingStyles}}">333</view></template>`,
// { name: 'a' }
// )
// assertCodegen(
// `<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">444</div>`,
// `<template name="a"><view class="_div" style=" {{'display:' + ['-webkit-box', '-ms-flexbox', 'flex'] + ';'}}">444</view></template>`,
// { name: 'a' }
// )
assertCodegen(
`<my-component style="color: red;"></my-component>`,
`<import src="/components/card" /><template name="a"><template data="{{...$root[$kk+'0'], $root}}" is="my-component"></template></template>`,
{
name: 'a',
components: {
'my-component': {
name: 'my-component',
src: '/components/card'
}
},
moduleId: 'hashValue'
},
{
mpTips: ['template 不支持此属性-> style="color: red;"'],
mpErrors: []
}
)
})

it('v-text', () => {
Expand Down

0 comments on commit 896f3c5

Please sign in to comment.