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