babel 修改抽象語法樹——入門與實踐
問題由來
最近有個想法,之前用Angular2.x 的時候,官方提供了ng-cli 可以一鍵生成component、service、directive 等程式碼檔案,並且還可以修改對應的routes 配置檔案,使得元件自動加入app 的前端路由中(一鍵生成或修改數個檔案)。這使得前端開發效率大為提高,我們不必再手動去建立那麼多資料夾、檔案,並且手動修改route 配置。但是vue-cli 沒有提供這種功能,所以我們想要寫個node.js 指令碼去做這個工作。除了用正則替換的解決辦法,更科學的實際上就需要用到修改程式碼抽象語法樹的方法。
babel 編譯過程
我們通常用 babel 去編譯ES6/ES7 為ES5,以便於 js 指令碼執行在各種瀏覽器上。這個編譯的過程實際上是語法轉換的過程,比如箭頭函式轉為函式表示式,this 的顯式繫結等等。那麼babel 在做這個工作的時候實際上經歷了幾個步驟,parse => transform (AST) => generate
所以要想完成這幾個步驟,babel 提供了幾個實用工具(Babylon,babel-traverse,babel-generator),我們的思路就是找到route 配置表中該插入新路由的地方,插入新路由並且儲存檔案。
babel.transform 核心函式接受原始碼字串和options 作為輸入,返回一個Object 包含幾個屬性:新的程式碼字串,sourcemap,ast 語法樹物件。
babel.transform("code();", options, function(err, result) {
result.code;
result.map;
result.ast;
});
實踐新增一個route物件
以下是等待修改的路由配置檔案,
// './src/router.ts'
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
// to be append new route.
]
})
以下是通過 babel 修改route 配置檔案的過程
var fs = require('fs');
let babel = require('babel-core');
let t = require('babel-types');
let template = require('@babel/template');
// 讀取需要修改的原始碼內容
var content = fs.readFileSync('./src/router.ts').toString();
const newRoute = {
path: '/list',
name: 'list'
// component: ListComponent
};
// 定義一個 babel 外掛,攔截並修改 routes 的陣列表示式
let visitor = {
ArrayExpression(path) {
const elements = path.node.elements;
console.warn(`routes number: ${elements.length}`);
// 新增一個構建出來的 route 物件
elements.push(t.objectExpression([
t.objectProperty(t.identifier('path'), t.stringLiteral(newRoute.path)),
t.objectProperty(t.identifier('name'), t.stringLiteral(newRoute.name)),
t.objectProperty(t.identifier('component'), t.identifier('ListComponent'))
]));
}
}
// 通過 plugin 轉換原始碼 parse 出來的AST 抽象語法樹,並且返回結果
let result= babel.transform(content, {
plugins: [
{ visitor }
]
});
console.warn(`res: ${result.code}`);
// 把新程式碼寫入新檔案.
fs.writeFileSync('newRoute.ts', result.code);
比較關鍵的部分就是在visitor 這個自定義的外掛中,攔截ArrayExpression,這是routes: [] 對應的路由陣列。而這個陣列表示式包含了一個elements 陣列,每個物件在AST 中都是ObjectExpression 型別。不論是陣列表示式,還是物件表示式,都是對應 babel-types 中不同的節點(node)型別。所以我們在構建新的 AST 節點時,可以參考AST explorer 中已有的節點型別。
例如這裡我們要新增一個route 物件,則是用babel-types 中的 types.objectExpression(objectProperty[]) 生成一個,根據智慧提示傳入引數,要求是objectProperty 陣列,那麼我們又利用 types.objectProperty(key: identifier, value: string) 生成一個。
t.objectExpression([
// 物件中的第一個屬性 path: string;
t.objectProperty(t.identifier('path'), t.stringLiteral(newRoute.path)),
// ...
]);
根據官方docs,也可以利用 AST explorer 去尋找對應關係,結合 IDE 的智慧提示來構建你所需要的 AST 語法樹,就可以自動轉換成你想要的程式碼了。實際上,搞懂了babel ,就可以做出ng-cli generate 這樣智慧高效的功能了。
參考文章:
https://www.sitepoint.com/understanding-asts-building-babel-plugin/
http://welefen.com/post/understanding-asts-by-building-your-own-babel-plugin.html
babel 官方文件
AST Explorer
相關文章
- babel外掛入門-AST(抽象語法樹)BabelAST抽象語法樹
- 從Babel開始認識AST抽象語法樹BabelAST抽象語法樹
- Javascript與抽象語法樹JavaScript抽象語法樹
- 13 個示例快速入門 JS 抽象語法樹JS抽象語法樹
- AST抽象語法樹AST抽象語法樹
- Go 抽象語法樹Go抽象語法樹
- 通過開發 Babel 外掛來理解什麼是抽象語法樹(AST)Babel抽象語法樹AST
- Javascrip—AST抽象語法樹(8)JavaAST抽象語法樹
- 抽象語法樹 Abstract syntax tree抽象語法樹
- 「譯」什麼是抽象語法樹抽象語法樹
- 前端進階之 JS 抽象語法樹前端JS抽象語法樹
- 前端進階之 Javascript 抽象語法樹前端JavaScript抽象語法樹
- Kafka 入門與實踐Kafka
- TypeScript入門與實踐TypeScript
- Docker 入門與實踐Docker
- locsut 入門與實踐
- 前端碼農之蛻變 — AST(抽象語法樹)前端AST抽象語法樹
- 高階前端基礎-JavaScript抽象語法樹AST前端JavaScript抽象語法樹AST
- 以 Golang 為例詳解 AST 抽象語法樹GolangAST抽象語法樹
- Flex佈局語法與實踐Flex
- GitOps快速入門與實踐Git
- 《Terraform 101 從入門到實踐》 第五章 HCL語法ORM
- Go編譯原理系列5(抽象語法樹構建)Go編譯原理抽象語法樹
- SQL 抽象語法樹及改寫場景應用SQL抽象語法樹
- SQL抽象語法樹及改寫場景應用SQL抽象語法樹
- C語言開發入門與程式設計實踐pdfC語言程式設計
- Git與Github入門實踐(上)Github
- thymeleaf語法入門
- LaTeX語法入門
- Markdown語法入門
- NLP實踐!文字語法糾錯模型實戰,搭建你的貼身語法修改小助手 ⛵模型
- [原始碼-webpack01-前置知識] AST抽象語法樹原始碼WebAST抽象語法樹
- python語音識別入門及實踐Python
- 微信小程式入門與實踐微信小程式
- [實踐系列]Babel原理Babel
- go 語法快速入門Go
- yaml語法快速入門YAML
- python3入門與實踐(六):函數語言程式設計Python函數程式設計