經由之前的三篇文章引見,AST
的CRUD
都已完成。下面重要經由過程vue
轉小順序
過程當中須要用到的部份關鍵技術來實戰。
下面的例子的中心代碼依然是最簡樸的一個vue
示例
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']
})
經由本文中的一些操縱,我們將取得終究的小順序代碼以下:
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,檢察其前後的組織對比。
vue
代碼轉小順序
對比文章一開始展現的兩份代碼,為了完成轉換,我們須要以下步驟:
- 將
data
函數轉data
屬性,然後刪除data
函數 - 將
methods
里的屬性提取出來,放到和data
統一層級中,methods
也要刪除 - 將一切的
this.[data member]
轉換為this.data.[data member]
。注重這裏只轉data
中的屬性 - 在變動
this.data
的下面,插進去this.setData
來觸發數據變動
下面將根據這一步驟,一步一步完成轉換,我以為看到每一步的代碼變化照樣很有成就感滴。
天生data
屬性
這一步,我們要先提取原data
函數中的return
的對象。連繫AST explorer,能夠很輕易的找到這一途徑。
const dataObject = ast.program.body[0].declaration.properties[0].body.body[0].argument
console.log(dataObject)
但是這段代碼的可讀性和魯棒性基本是0啊。它強依靠我們謄寫的data
函數是第一個屬性。所以這裏我們照樣重要運用traverse
來訪問節點:
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
,由於這裏組織是牢固的。
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
轉小順序
的部份代碼,今後能夠斟酌繼承引見其他模塊。