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插件中文开发文档提供了不少详细资料。