Skip to content

Commit

Permalink
feat(weex): support parse object literal in binding attrs and styles (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanks10100 authored and yyx990803 committed Dec 27, 2017
1 parent f8cb3a2 commit ff8fcd2
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 7 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"devDependencies": {
"@types/node": "^8.0.33",
"@types/webpack": "^3.0.13",
"acorn": "^5.2.1",
"babel-core": "^6.25.0",
"babel-eslint": "^8.0.3",
"babel-helper-vue-jsx-merge-props": "^2.0.2",
Expand All @@ -79,6 +80,7 @@
"cz-conventional-changelog": "^2.0.0",
"de-indent": "^1.0.2",
"es6-promise": "^4.1.0",
"escodegen": "^1.8.1",
"eslint": "^4.13.1",
"eslint-loader": "^1.7.1",
"eslint-plugin-flowtype": "^2.34.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/weex-template-compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/weex-template-compiler#readme",
"dependencies": {
"acorn": "^5.2.1",
"escodegen": "^1.8.1",
"he": "^1.1.0"
}
}
5 changes: 2 additions & 3 deletions src/platforms/weex/compiler/modules/recycle-list/v-bind.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @flow */

import { camelize } from 'shared/util'
import { generateBinding } from 'weex/util/parser'
import { bindRE } from 'compiler/parser/index'
import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'

Expand All @@ -12,9 +13,7 @@ export function preTransformVBind (el: ASTElement, options: WeexCompilerOptions)
for (const attr in el.attrsMap) {
if (bindRE.test(attr)) {
const name: string = parseAttrName(attr)
const value = {
'@binding': getAndRemoveAttr(el, attr)
}
const value = generateBinding(getAndRemoveAttr(el, attr))
delete el.attrsMap[attr]
addRawAttr(el, name, value)
}
Expand Down
10 changes: 7 additions & 3 deletions src/platforms/weex/compiler/modules/style.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* @flow */

import { cached, camelize } from 'shared/util'
import { cached, camelize, isPlainObject } from 'shared/util'
import { parseText } from 'compiler/parser/text-parser'
import {
getAndRemoveAttr,
Expand All @@ -10,7 +10,7 @@ import {

type StaticStyleResult = {
dynamic: boolean,
styleResult: string
styleResult: string | Object | void
};

const normalize = cached(camelize)
Expand All @@ -27,12 +27,14 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
)
}
if (!dynamic && styleResult) {
// $flow-disable-line
el.staticStyle = styleResult
}
const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
if (styleBinding) {
el.styleBinding = styleBinding
} else if (dynamic) {
// $flow-disable-line
el.styleBinding = styleResult
}
}
Expand All @@ -53,7 +55,7 @@ function parseStaticStyle (staticStyle: ?string, options: CompilerOptions): Stat
// "width: 200px; height: {{y}}" -> {width: 200, height: y}
let dynamic = false
let styleResult = ''
if (staticStyle) {
if (typeof staticStyle === 'string') {
const styleList = staticStyle.trim().split(';').map(style => {
const result = style.trim().split(':')
if (result.length !== 2) {
Expand All @@ -71,6 +73,8 @@ function parseStaticStyle (staticStyle: ?string, options: CompilerOptions): Stat
if (styleList.length) {
styleResult = '{' + styleList.join(',') + '}'
}
} else if (isPlainObject(staticStyle)) {
styleResult = JSON.stringify(staticStyle) || ''
}
return { dynamic, styleResult }
}
Expand Down
60 changes: 60 additions & 0 deletions src/platforms/weex/util/parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* @flow */

// import { warn } from 'core/util/index'

// this will be preserved during build
// $flow-disable-line
const acorn = require('acorn') // $flow-disable-line
const walk = require('acorn/dist/walk') // $flow-disable-line
const escodegen = require('escodegen')

export function nodeToBinding (node: Object): any {
switch (node.type) {
case 'Literal': return node.value
case 'Identifier':
case 'UnaryExpression':
case 'BinaryExpression':
case 'LogicalExpression':
case 'ConditionalExpression':
case 'MemberExpression': return { '@binding': escodegen.generate(node) }
case 'ArrayExpression': return node.elements.map(_ => nodeToBinding(_))
case 'ObjectExpression': {
const object = {}
node.properties.forEach(prop => {
if (!prop.key || prop.key.type !== 'Identifier') {
return
}
const key = escodegen.generate(prop.key)
const value = nodeToBinding(prop.value)
if (key && value) {
object[key] = value
}
})
return object
}
default: {
// warn(`Not support ${node.type}: "${escodegen.generate(node)}"`)
return ''
}
}
}

export function generateBinding (exp: ?string): any {
if (exp && typeof exp === 'string') {
let ast = null
try {
ast = acorn.parse(`(${exp})`)
} catch (e) {
// warn(`Failed to parse the expression: "${exp}"`)
return ''
}

let output = ''
walk.simple(ast, {
Expression (node) {
output = nodeToBinding(node)
}
})
return output
}
}
2 changes: 1 addition & 1 deletion test/weex/cases/cases.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('Usage', () => {
it('text node', createRenderTestCase('recycle-list/text-node'))
it('attributes', createRenderTestCase('recycle-list/attrs'))
// it('class name', createRenderTestCase('recycle-list/classname'))
// it('inline style', createRenderTestCase('recycle-list/inline-style'))
it('inline style', createRenderTestCase('recycle-list/inline-style'))
it('v-if', createRenderTestCase('recycle-list/v-if'))
it('v-else', createRenderTestCase('recycle-list/v-else'))
it('v-else-if', createRenderTestCase('recycle-list/v-else-if'))
Expand Down
105 changes: 105 additions & 0 deletions test/weex/compiler/parser.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { generateBinding } from '../../../src/platforms/weex/util/parser'

describe('expression parser', () => {
describe('generateBinding', () => {
it('primitive literal', () => {
expect(generateBinding('15')).toEqual(15)
expect(generateBinding('"xxx"')).toEqual('xxx')
})

it('identifiers', () => {
expect(generateBinding('x')).toEqual({ '@binding': 'x' })
expect(generateBinding('x.y')).toEqual({ '@binding': 'x.y' })
expect(generateBinding(`x.y['z']`)).toEqual({ '@binding': `x.y['z']` })
})

it('object literal', () => {
expect(generateBinding('{}')).toEqual({})
expect(generateBinding('{ abc: 25 }')).toEqual({ abc: 25 })
expect(generateBinding('{ abc: 25, def: "xxx" }')).toEqual({ abc: 25, def: 'xxx' })
expect(generateBinding('{ a: 3, b: { bb: "bb", bbb: { bbc: "BBC" } } }'))
.toEqual({ a: 3, b: { bb: 'bb', bbb: { bbc: 'BBC' }}})
})

it('array literal', () => {
expect(generateBinding('[]')).toEqual([])
expect(generateBinding('[{ abc: 25 }]')).toEqual([{ abc: 25 }])
expect(generateBinding('[{ abc: 25, def: ["xxx"] }]')).toEqual([{ abc: 25, def: ['xxx'] }])
expect(generateBinding('{ a: [3,16], b: [{ bb: ["aa","bb"], bbb: [{bbc:"BBC"}] }] }'))
.toEqual({ a: [3, 16], b: [{ bb: ['aa', 'bb'], bbb: [{ bbc: 'BBC' }] }] })
})

it('expressions', () => {
expect(generateBinding(`3 + 5`)).toEqual({ '@binding': `3 + 5` })
expect(generateBinding(`'x' + 2`)).toEqual({ '@binding': `'x' + 2` })
expect(generateBinding(`\`xx\` + 2`)).toEqual({ '@binding': `\`xx\` + 2` })
expect(generateBinding(`item.size * 23 + 'px'`)).toEqual({ '@binding': `item.size * 23 + 'px'` })
})

it('object bindings', () => {
expect(generateBinding(`{ color: textColor }`)).toEqual({
color: { '@binding': 'textColor' }
})
expect(generateBinding(`{ color: '#FF' + 66 * 100, fontSize: item.size }`)).toEqual({
color: { '@binding': `'#FF' + 66 * 100` },
fontSize: { '@binding': 'item.size' }
})
expect(generateBinding(`{
x: { xx: obj, xy: -2 + 5 },
y: {
yy: { yyy: obj.y || yy },
yz: typeof object.yz === 'string' ? object.yz : ''
}
}`)).toEqual({
x: { xx: { '@binding': 'obj' }, xy: { '@binding': '-2 + 5' }},
y: {
yy: { yyy: { '@binding': 'obj.y || yy' }},
yz: { '@binding': `typeof object.yz === 'string' ? object.yz : ''` }
}
})
})

it('array bindings', () => {
expect(generateBinding(`[textColor, 3 + 5, 'string']`)).toEqual([
{ '@binding': 'textColor' },
{ '@binding': '3 + 5' },
'string'
])
expect(generateBinding(`[
{ color: '#FF' + 66 * -100 },
item && item.style,
{ fontSize: item.size | 0 }
]`)).toEqual([
{ color: { '@binding': `'#FF' + 66 * -100` }},
{ '@binding': 'item && item.style' },
{ fontSize: { '@binding': 'item.size | 0' }}
])
expect(generateBinding(`[{
x: [{ xx: [fn instanceof Function ? 'function' : '' , 25] }],
y: {
yy: [{ yyy: [obj.yy.y, obj.y.yy] }],
yz: [object.yz, void 0]
}
}]`)).toEqual([{
x: [{ xx: [{ '@binding': `fn instanceof Function ? 'function' : ''` }, 25] }],
y: {
yy: [{ yyy: [{ '@binding': 'obj.yy.y' }, { '@binding': 'obj.y.yy' }] }],
yz: [{ '@binding': 'object.yz' }, { '@binding': 'void 0' }]
}
}])
})

it('unsupported bindings', () => {
expect(generateBinding('() => {}')).toEqual('')
expect(generateBinding('function(){}')).toEqual('')
expect(generateBinding('(function(){})()')).toEqual('')
expect(generateBinding('var abc = 35')).toEqual('')
expect(generateBinding('abc++')).toEqual('')
expect(generateBinding('x.y(0)')).toEqual('')
expect(generateBinding('class X {}')).toEqual('')
expect(generateBinding('if (typeof x == null) { 35 }')).toEqual('')
expect(generateBinding('while (x == null)')).toEqual('')
expect(generateBinding('new Function()')).toEqual('')
})
})
})

0 comments on commit ff8fcd2

Please sign in to comment.