社區裏面有不少關於Babel的文章,有些寫的很好,我本身也受這些文章啓發很大。但我發現一個問題就是,這類文章一進來就講了不少babel底層的概念,說實話對基礎不深的一些童鞋來講,看完以後理解起來仍是有必定難度的,最重要的是看完了以後,本身並不知道如何去寫一個Babel插件,於是這促使了如何從0到1完成一個babel插件這篇文章的編寫,學習完本篇文章,指望是你們能對Babel有一個總體的認識,知道Babel是什麼?Babel是如何運做的?而且本身能實現一個簡單的Babel插件。vue
Babel是一個JavaScript編譯器,意思就是說你爲Babel提供一些代碼,Babel作一些轉換,給你返回一些新的代碼。好比,咱們常見的將ES5+的代碼轉換成ES5+以前的一些代碼。node
如圖,Babel通過3個處理步驟,分別爲解析(parse),轉換(transform),生成(generate)。react
解析又通過詞法分析,語法分析兩個步驟,將輸入的代碼生成抽象語法數(AST),AST能夠理解爲就是描述一段代碼的節點樹,看以下這個例子:git
咱們輸入github
const a = 1
複製代碼
通過解析(parse),生成以下結構的節點樹(爲了方便觀看,去掉了一些代表節點位置信息的屬性),詳細的能夠經過這個工具查看json
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal",
"value": 1,
"rawValue": 1,
"raw": "1"
}
}
],
"kind": "const"
}
複製代碼
每個{"type":""}包裹的內容均可以視爲一個節點(Node)緩存
獲得了AST抽象語法樹,本質就是一個用來描述代碼的節點樹(Node),咱們就能夠經過 樹形遍歷來遍歷它,從而進行代碼轉換(對節點添加、更新及移除等操做),也就是Babel插件真正處理的地方bash
通過轉換以後的AST仍是AST,因此咱們還須要將AST生成字符串形式的代碼babel
Babel的基礎知識還有不少,我以爲一開始瞭解這麼多就夠了,咱們如今開始開發一個簡單的Babel轉換。工具
如前面所說Babel的3個步驟,解析,轉換,生成,Babel都提供了對應的方法,分別以下:
parse
traverse
generate
咱們要實現一個插件,將整個引入組件的代碼
import { Select as MySelect, Pagination } from 'UI';
// import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
複製代碼
處理爲以下按需處理的形式
import MySelect from "/MySelect/MySelect.js";
import Pagination from "/Pagination/Pagination.js"; // import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
複製代碼
這裏我使用了codesandbox在線編寫的方式,訪問這裏,將須要的依賴包引進來。
const parse = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');
複製代碼
其中,@babel/types
是用於 AST 節點的 Lodash 式工具庫, 它包含了構造、驗證以及變換 AST 節點的方法。 該工具庫包含考慮周到的工具方法,對編寫處理AST邏輯很是有用。
const code = `import { Select as MySelect, Pagination } from ''; // import UI2 from 'xxx-ui'; import * as UI from 'xxx-ui'; `;
const ast = parse(code);
複製代碼
這步很關鍵,咱們的轉換處理都在這一步
traverse(ast, {
ImportDeclaration(path) {
// 獲取本來組件名
const source = path.node.source.value;
// 獲取Select as MySelect , Pagination兩個節點
const specifiers = path.node.specifiers;
// import specifiers有3種形式,ImportSpecifier ,ImportNamespaceSpecifier,ImportDefaultSpecifier
// 獲取specifiers類型是不是 命名空間類型,相似 import * as UI from 'xxx-ui' 這種
const isImportNamespaceSpecifier = t.isImportNamespaceSpecifier(
specifiers[0]
);
// 獲取specifiers類型是不是 默認導出類型,相似 import UI2 from 'xxx-ui' 這種
const isImportDefaultSpecifier = t.isImportDefaultSpecifier(specifiers[0]);
if (!isImportNamespaceSpecifier && !isImportDefaultSpecifier) {
const declarations = specifiers.map(specifier => {
// 緩存單個組件名
let localName = specifier.local.name;
// 拼接引入路徑
let newSource = `${source}/${localName}/${localName}.js`;
// 構造新的ImportDeclaration節點
return t.importDeclaration(
[t.importDefaultSpecifier(specifier.local)],
t.stringLiteral(newSource)
);
});
// 將構造好的新AST替換原來的AST
path.replaceWithMultiple(declarations);
}
}
});
複製代碼
traverse
方法第二個參數傳入的就是咱們對具體節點遍歷的處理方法,這裏有個概念須要明確的是,當咱們以訪問者身份遍歷節點的時候,咱們其實訪問的是路徑path,而非具體某個節點,因此示例中咱們咱們有2個ImportDeclaration
節點,但咱們只寫了一個處理方法,由於這裏這個方法會被執行2次。
最後,咱們須要將轉換後的AST從新生成代碼
let newCode = generate(ast).code;
console.log(newCode);
複製代碼
終端輸入
node ./src/index.js
複製代碼
能夠看到最終咱們生成的代碼
import MySelect from "/MySelect/MySelect.js";
import Pagination from "/Pagination/Pagination.js"; // import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
複製代碼
咱們完成了一個babel插件,那在項目中如何引入呢?其實,上述所述的步驟代碼只是從內部剖析了下Babel插件的處理原理,真正咱們在項目中只須要對外暴露一個方法,裏面返回一個包含visitor
屬性的對象。
visitor訪問者是一個對象,定義了一系列訪問樹形結構中節點的方法
// myPlugin.js
const babel = require(@babel/core');
const t = require('@babel/types');
export default function() {
return {
visitor: {
ImportDeclaration(path, state) {
//轉換邏輯
},
}
};
};
複製代碼
而後在babel-loader
的plugin引入
options:{
plugins:[
["myPlugin"]
]
}
複製代碼
原理是啥呢?是由於經過babel-loader
引入,Babel裏面core模塊提供了transform
方法,具體APi能夠查看這裏,只須要傳入visitor對象,該方法據此默認會去作解析,轉換,生成工做,內部處理邏輯以下:
const visitor = require('visitor.js');
const babel = require('@babel/core');
const result = babel.transform(code, {
plugins: [visitor],
});
複製代碼
這裏提供一個簡化版的vue
轉react
的示例,有興趣的能夠學習下,地址。
最後,若是你對多端開發有興趣,咱們微店有個小組,從事多端統一開發研究,有興趣的童鞋也能夠加入進來看看,裏面有不少Babel相關實例和文章,訪問地址