通过以前的三篇文章介绍,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,能够很方便的找到这一路径。bash
const dataObject = ast.program.body[0].declaration.properties[0].body.body[0].argument
console.log(dataObject)
复制代码
但是这段代码的可读性和鲁棒性基本是0啊。它强依赖咱们书写的data
函数是第一个属性。因此这里咱们仍是主要使用traverse
来访问节点:babel
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)
复制代码
程序输出:ide
export default {
data: (() => {
return {
message: 'hello vue',
count: 0
};
})(),
methods: {
add() {
++this.count;
},
minus() {
--this.count;
}
}
};
复制代码
methods
中的属性提高一级 这里遍历methods
中的属性没有再采用traverse
,由于这里结构是固定的。函数
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.io/2018/05/22/ast-create/Javascript-Babylon-AST-create/]操做,忘记的能够去复习下哦,下面咱们直接上代码,你们看这段代码必定要对照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
转小程序
的部分代码,之后能够考虑继续介绍其余模块。