通过以前的三篇文章介绍,AST
的CRUD
都已经完成。下面主要经过vue
转小程序
过程当中须要用到的部分关键技术来实战。vue
下面的例子的核心代码依然是最简单的一个vue
示例node
const babylon = require('babylon') const t = require('@babel/types') const generate = require('@babel/generator').default const traverse = require('@babel/traverse').default const code = ` export default { data() { return { message: 'hello vue', count: 0 } }, methods: { add() { ++this.count }, minus() { --this.count } } } ` const ast = babylon.parse(code, { sourceType: 'module', plugins: ['flow'] })
通过本文中的一些操做,咱们将得到最终的小程序代码以下:git
Page({ data: (() => { return { message: 'hello vue', count: 0 } })(), add() { ++this.data.count this.setData({ count: this.data.count }) }, minus() { --this.data.count this.setData({ count: this.data.count }) } })
注意:,跟咱们以前介绍的一致,为了完成上述转换,要把输入和输出均放入AST explorer,查看其前后的结构对比。github
vue
代码转小程序对比文章一开始展现的两份代码,为了实现转换,咱们须要如下步骤:express
data
函数转data
属性,而后删除data
函数methods
里的属性提取出来,放到和data
同一层级中,methods
也要删除this.[data member]
转换为this.data.[data member]
。注意这里只转data
中的属性this.data
的下面,插入this.setData
来触发数据变动下面将按照这一步骤,一步一步完成转换,我以为看到每一步的代码变化仍是颇有成就感滴。小程序
data
属性 这一步,咱们要先提取原data
函数中的return
的对象。结合AST explorer,能够很方便的找到这一路径。babel
const dataObject = ast.program.body[0].declaration.properties[0].body.body[0].argument console.log(dataObject)
但是这段代码的可读性和鲁棒性基本是0啊。它强依赖咱们书写的data
函数是第一个属性。因此这里咱们仍是主要使用traverse
来访问节点:ide
traverse(ast, { ObjectMethod(path) { if (path.node.key.name === 'data') { // 获取第一级的 BlockStatement,也就是data函数体 let blockStatement = null path.traverse({ //将traverse合并的写法 BlockStatement(p) { blockStatement = p.node } }) // 用blockStatement生成ArrowFunctionExpression const arrowFunctionExpression = t.arrowFunctionExpression([], blockStatement) // 生成CallExpression const callExpression = t.callExpression(arrowFunctionExpression, []) // 生成data property const dataProperty = t.objectProperty(t.identifier('data'), callExpression) // 插入到原data函数下方 path.insertAfter(dataProperty) // 删除原data函数 path.remove() // console.log(arrowFunctionExpression) } } }) console.log(generate(ast, {}, code).code)
程序输出:函数
export default { data: (() => { return { message: 'hello vue', count: 0 }; })(), methods: { add() { ++this.count; }, minus() { --this.count; } } };
methods
中的属性提高一级 这里遍历methods
中的属性没有再采用traverse
,由于这里结构是固定的。ui
traverse(ast, { ObjectProperty(path) { if (path.node.key.name === 'methods') { // 遍历属性并插入到原methods以后 path.node.value.properties.forEach(property => { path.insertAfter(property) }) // 删除原methods path.remove() } } })
程序输出:
export default { data: (() => { return { message: 'hello vue', count: 0 }; })(), minus() { --this.count; }, add() { ++this.count; } };
this.member
转为this.data.member
这一步,首先要从data
属性中提取数据属性。这个有些依赖data
中的函数到底写成怎么样,若是写成:
data: (() => { const obj = {} obj.message = 'hello vue' obj.count = 0 return obj })(),
这将不符合咱们这里的转化方法。固然咱们能够经过求值来获取最终的对象,但这里也有缺陷。另外一个思路是遍历其余成员函数,使用排除法。
总之,咱们须要一个方法来获取this.data
中的属性。本文将继续以代码中的例子,经过data
中的return
方法来获取。
// 获取`this.data`中的属性 const datas = [] traverse(ast, { ObjectProperty(path) { if (path.node.key.name === 'data') { path.traverse({ ReturnStatement(path) { path.traverse({ ObjectProperty(path) { datas.push(path.node.key.name) path.skip() } }) path.skip() } }) } path.skip() } }) console.log(datas)
程序输出:
[ 'message', 'count' ]
修改数据属性至this.data.
traverse(ast, { MemberExpression(path) { if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) { path.get('object').replaceWithSourceString('this.data') } } })
至此程序输出:
export default { data: (() => { return { message: 'hello vue', count: 0 }; })(), minus() { --this.data.count; }, add() { ++this.data.count; } };
this.setData
方法 要想在变动this.data
的下面,插入this.setData
,咱们首先要找到它插入的位置,即this.data
的父节点,因此这就是咱们的第一步操做:(MemberExpression
就是上一步的,由于这一步的path与上一步相同)
traverse(ast, { MemberExpression(path) { if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) { path.get('object').replaceWithSourceString('this.data') } } const expressionStatement = path.findParent((parent) => parent.isExpressionStatement() ) })
找到插入的位置后,咱们就要构造要插入的函数,这时就用到了咱们在这个系列第一篇文章中介绍的(Create
)[https://summerrouxin.github.i...]操做,忘记的能够去复习下哦,下面咱们直接上代码,你们看这段代码必定要对照AST explorerh和babel-types
的API
,而后找到从外向内一层一层的对照。这段代码的逻辑大概以下:
this.member
的父结点traverse(ast, { MemberExpression(path) { if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) { path.get('object').replaceWithSourceString('this.data') //必定要判断一下是否是赋值操做 if( (t.isAssignmentExpression(path.parentPath) && path.parentPath.get('left') === path) || t.isUpdateExpression(path.parentPath) ) { // findParent const expressionStatement = path.findParent((parent) => parent.isExpressionStatement() ) // create if(expressionStatement) { const finalExpStatement = t.expressionStatement( t.callExpression( t.memberExpression(t.thisExpression(), t.identifier('setData')), [t.objectExpression([t.objectProperty( t.identifier(propertyName), t.identifier(`this.data.${propertyName}`) )])] ) ) expressionStatement.insertAfter(finalExpStatement) } } } } })
程序输出:
export default { data: (() => { return { message: 'hello vue', count: 0 }; })(), minus() { --this.count; this.setData({ count: this.data.count }) }, add() { ++this.count; this.setData({ count: this.data.count }) } };
以上就是咱们实战介绍,这边只涉及到vue
转小程序
的部分代码,之后能够考虑继续介绍其余模块。