通過以前的三篇文章介紹,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
轉小程序
的部分代碼,之後能夠考慮繼續介紹其餘模塊。