Babel是Javascript編譯器,是種代碼到代碼的編譯器,一般也叫作『轉換編譯器』。css
Babel的處理主要過程:解析(parse)、轉換(transform)、生成(generate)。node
因此咱們須要對AST有必定了解才能進行Babel插件開發。react
在這整個過程當中,都是圍繞着抽象語法樹(AST)來進行的。在Javascritp中,AST,簡單來講,就是一個記錄着代碼語法結構的Object。感興趣的同窗可到https://astexplorer.net/ 去深刻體驗
好比下面的代碼:git
import {Button} from 'antd'; import Card from 'antd/button/lib/index.js';
轉換成AST後以下,github
{ "type": "Program", "start": 0, "end": 253, "body": [ { "type": "ImportDeclaration", "start": 179, "end": 207, "specifiers": [ { "type": "ImportSpecifier", "start": 187, "end": 193, "imported": { "type": "Identifier", "start": 187, "end": 193, "name": "Button" }, "local": { "type": "Identifier", "start": 187, "end": 193, "name": "Button" } } ], "source": { "type": "Literal", "start": 200, "end": 206, "value": "antd", "raw": "'antd'" } }, { "type": "ImportDeclaration", "start": 209, "end": 253, "specifiers": [ { "type": "ImportDefaultSpecifier", "start": 216, "end": 220, "local": { "type": "Identifier", "start": 216, "end": 220, "name": "Card" } } ], "source": { "type": "Literal", "start": 226, "end": 252, "value": "antd/button/lib/index.js", "raw": "'antd/button/lib/index.js'" } } ], "sourceType": "module" }
插件必須是一個函數,根據官方文檔要求,形式以下:json
module.exports = function ({ types: t }) { return { visitor: { ImportDeclaration(path, source){ //todo }, FunctionDeclaration(path, source){ //todo }, } } }
types來自@babel/types工具類,主要用途是在建立AST的過程當中判斷各類語法的類型和節點構造。數組
不少同窗用過 babel-plugin-import ,它幫助咱們在使用一些JS類庫是達到按需加載。其實,該插件幫助咱們作了以下代碼轉換:babel
//from import {Button } from 'antd'; //to import Button from 'antd/es/button'; import 'antd/es/button/style.css';
咱們先看看二者的AST有何差異,以幫助咱們對轉換有個清晰的認識:antd
轉換前:函數
[{ "type": "ImportDeclaration", "start": 6, "end": 45, "specifiers": [ { "type": "ImportSpecifier", "start": 14, "end": 20, "imported": { "type": "Identifier", "start": 14, "end": 20, "name": "Button" }, "local": { "type": "Identifier", "start": 14, "end": 20, "name": "Button" } } ], "source": { "type": "Literal", "start": 28, "end": 44, "value": "antd/es/button", "raw": "'antd/es/button'" } }]
轉換後:
[{ "type": "ImportDeclaration", "start": 5, "end": 41, "specifiers": [ { "type": "ImportDefaultSpecifier", "start": 12, "end": 18, "local": { "type": "Identifier", "start": 12, "end": 18, "name": "Button" } } ], "source": { "type": "Literal", "start": 24, "end": 40, "value": "antd/es/button", "raw": "'antd/es/button'" } }, { "type": "ImportDeclaration", "start": 46, "end": 76, "specifiers": [], "source": { "type": "Literal", "start": 53, "end": 75, "value": "antd/es/button/style", "raw": "'antd/es/button/style'" } }]
對比兩棵樹,咱們應該有個大體的思路。在轉換過程當中,咱們還須要一些參數,這些參數在配置文件(package.json或者.babelrc)中,提供了一些自定義配置,好比antd的按需加載:
["import",{libraryName:"antd",libraryDireactory:"es","style":"css"}]
如今咱們開始嘗試實現這個插件吧:
module.exports = function ({ types: t }) { return { visitor: { ImportDeclaration(path, source) { //取出參數 const { opts: { libraryName, libraryDirectory='lib', style="css" } } = source; //拿到老的AST節點 let node = path.node if(node.source.value !== libraryName){ return; } //建立一個數組存入新生成AST let newImports = []; //構造新節點 path.node.specifiers.forEach(item => { newImports.push(t.importDeclaration([t.importDefaultSpecifier(item.local)], t.stringLiteral(`${libraryName}/${libraryDirectory}/${item.local.name}`))); newImports.push(t.importDeclaration([], t.stringLiteral(`${libraryName}/${libraryDirectory}/style.${style}`))) }); //替換原節點 path.replaceWithMultiple(newImports); } } }
}
如今,簡單版本的@babel-plugin-import的babel插件咱們已經完成了。
若感興趣瞭解更多內容,babel插件中文開發文檔提供了不少詳細資料。