Babel 插件

Babel 是一个编译器,和其他编译器一样,编译过程分为三个阶段,分别是解析(parsing)、转换(transforming)和生成(generate)。其中分析和生成阶段由 Babel 核心完成,而转换阶段,则由Babel插件完成。所以如果我们要实现代码的转换,需要为 Babel 添加插件。Babel 也提供了很多的接口来供我们编写自身的插件来转换我们的实际代码。
插件的介绍
下面是一个典型的 Babel 插件结构:

export default function({ types: babelTypes }) { return { visitor: { Identifier(path, state) {}, ASTNodeTypeHere(path, state) {} } }; };

其中我们需要关注的内容如下:
  • babelType:类似 lodash 那样的工具集,主要用来操作 AST 节点,比如创建、校验、转变等。例如判断某个节点是不是标识符。
  • path:AST 中有很多节点,每个节点可能有不同的属性,并且节点之间可能存在关联。path 是个对象,它代表了两个节点之间的关联。我们可以在 path 上访问到节点的属性,也可以通过 path 来访问到关联的节点。
  • state:代表了插件的状态,我们可以通过 state 来访问插件的配置项。
  • visitor:Babel 采取递归的方式访问 AST 的每个节点,之所以叫做 visitor,只是因为有个类似的设计模式叫做访问者模式,不用在意背后的细节。
  • Identifier、ASTNodeTypeHere:AST 的每个节点,都有对应的节点类型,比如标识符、函数声明等,可以在 visitor 上声明同名的属性,当 Babel 遍历到相应类型的节点,属性对应的方法就会被调用,传入的参数就是 path、state。
插件的使用
例如我们来实现一个简单的插件,将所有名称为 hello 的标识符,转成 xkd。
首先要确保已经安装了 @babel/cli 依赖,如果没有可以执行下述命令:
npm install --save-dev @babel/cli

然后可以开始创建插件,判断标识符的名称是否是 a,如果是则替换成 xkd,plugin.js 文件内容如下所示:
module.exports = function({ types: babelTypes }) { return { name: "simple-plugin-replace", visitor: { Identifier(path, state) { if (path.node.name === 'a') { path.node.name = 'xkd'; } } } }; };

然后在 index.js 文件中编写源代码,例如:
var a = 10; function func(){ var a = 20; console.log(a); }

执行如下命令:
npx babel --plugins ./plugin.js index.js

输出的转码结果为:
"use strict"; var xkd = 10; function func() { var xkd = 20; console.log(xkd); }

可以看到,代码中的所有标识符 a 都被替换成了 xkd。
插件配置
插件可以有自己的配置项。我们可以修改前面的例子,看下在 Babel 插件中如何获取配置项。
示例:
例如我们修改 .babelrc 文件中的配置项:
{ "plugins": [ ["./plugin", { "a": "one", "b": "two" }] ] }

然后修改插件代码,从 state.opts 中获取到配置参数。
module.exports = function({ types: babelTypes }) { return { name: "simple-plugin-replace", visitor: { Identifier(path, state) { let name = path.node.name; if (state.opts[name]) { path.node.name = state.opts[name]; } } } }; };

在 index.js 文件中写入需要用到的测试代码:
let a = 10; let b = 20;

运行转码命令 npx babel index.js,输出转码后的结果如下:
let one = 10; let two = 20;

转译插件
插件可以分为两种,分是转译插件和语法插件,转译插件可以用于转译代码。同一类语法可能同时存在语法插件版本和转译插件版本,如果我们使用了转译插件,就不用再使用语法插件了,因为转换插件将启用相应的语法插件。
ES3
  • member-expression-literals
  • property-literals
  • reserved-words
ES5
  • property-mutators
ES2015
  • arrow-functions
  • block-scoped-functions
  • block-scoping
  • classes
  • computed-properties
  • destructuring
  • duplicate-keys
  • for-of
  • function-name
  • instanceof
  • literals
  • new-target
  • object-super`
  • parameters
  • shorthand-properties
  • spread
  • sticky-regex
  • template-literals
  • typeof-symbol
  • unicode-escapes
  • unicode-regex
ES2016
  • exponentiation-operator
ES2017
  • async-to-generator
ES2018
  • async-generator-functions
  • dotall-regex
  • named-capturing-groups-regex
  • object-rest-spread
  • optional-catch-binding
  • unicode-property-regex
Modules
  • modules-amd
  • modules-commonjs
  • modules-systemjs
  • modules-umd
Experimental
  • class-properties
  • decorators
  • do-expressions
  • export-default-from
  • export-namespace-from
  • function-bind
  • function-sent
  • logical-assignment-operators
  • nullish-coalescing-operator
  • numeric-separator
  • optional-chaining
  • partial-application
  • pipeline-operator
  • private-methods
  • throw-expressions
  • private-property-in-object
语法插件
当我们添加语法插件之后,在解析这一步就使得 Babel 能够解析特定类型的语法。
【Babel 插件】或者也可以从 Babel 解析器提供任何插件选项,例如 .babelrc 文件中可以像下面这样配置:
{ "parserOpts": { "plugins": ["jsx", "flow"] } }

    推荐阅读