babel 7 插件开发相关

准备工作

此文前,需要先阅读

  1. 抽象语法树 AST 与 编译器 Compiler
  2. babel 7 全套
  3. Babel 6 插件手册 (PS:尽管大部分 API 已过时,但是大概的理念仍然适用)

概念

  • 树形遍历
  • Visitors(访问者)
  • Paths(路径)
  • State(状态)
  • Scopes(作用域)
  • Bindings(绑定) —— 或者叫变量引用?
  • 转换操作(插件 / transform / 节点操作) —— 类似 jQuery

PS:如果不开发插件,其实看插件相关内容意义不大,但对于理解 babel 整套流程,还是有一定帮助。

@babel/helper-xxxx-xxxx

各种各样的辅助函数、方法 / 功能。例如:

  1. @babel/helper-plugin-utils index.js 辅助插件开发,但只是做了一层 wrapper
  2. @babel/helper-builder-react-jsx index.js 用于 @babel/plugin-transform-react-inline-elements index.js@babel/plugin-transform-react-jsx-compat index.js 转换插件
  3. 等等 …

@babel/plugin-xxxx-xxxx

为什么会有 plugin-syntax-xxxx-xxxxplugin-transform-xxxx-xxxx 插件? —— Transform plugin vs Syntax plugin in Babel

plugin-syntax-xxxx-xxxxplugin-transform-xxxx-xxxx 的前提,或者说,就是用来给 plugin-transform-xxxx-xxxxplugin-proposal-xxxx-xxxx 继承用的。

需要有 plugin-syntax-xxxx-xxxx,设定语法解析的方式,对应的语法,才能被 @babel/parser 正确地处理,类似的: 如果不设定解析 generator 函数,function*(x) { } 肯定是报错的

再例如:

  1. @babel/plugin-proposal-function-bind index.js 作为转换插件,其继承了
  2. @babel/plugin-syntax-function-bind index.js,而 @babel/plugin-syntax-function-bind index.js 内部,通过 parserOpts.plugins.push("functionBind"); 设定了 functionBind 的解析
  3. parser 阶段,根据 functionBind 的设定,进行不同的 词法分析(tokenizer/index.js) (其他插件可能也会有不同的语法分析),最终生成 AST
  4. @babel/plugin-proposal-function-bind 对 AST 进行转换

所以,如果我们想编写 babel 插件来将 @@@hello 转换为 world.hello 是做不到的,因为 @@@hello 在 babel 的 parser 上不支持。其内置的,可以进行配置的额外插件见:@babel/parser/typings/babel-parser.d.ts

而如果我们想编写一些插件,又依赖一些语法(可能该新语法未启用),需要进行:

1
2
3
4
manipulateOptions(opts, parserOpts) {
// 类似的 parser 插件处理
parserOpts.plugins.push("functionBind");
}

@babel/plugin-proposal-xxxx-xxxx

@babel/plugin-transform-xxxx-xxxxx,只是一些未确定落地的草案 AST transform 实现。

@babel/types

包含功能

  • definitions —— 定义 (包括一些节点名的别名)
  • builders —— 节点生成工具
  • validators —— 节点判断工具
  • asserts —— 节点断言工具,就是 validators 的包装,如果判断不通过,会报错
  • converters
  • modifications

用于生成 AST、AST 节点类型检查等等,类似 lodash 的工具库

详细内容见:@babel/types docs

@babel/template

在编写 babel 插件时,如果涉及到大规模代码变动 / 转换,如果只使用 @babel/types,大概率会累死

此工具使用 @babel/parser 将字符串处理成 AST,并替换掉占位符内容

例如:@babel/helper-function-name

开发个最简单的插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const pluginUtils = require('@babel/helper-plugin-utils')
const babel = require('@babel/core')
const t = babel.types

const pluginDemo = pluginUtils.declare(api => {
api.assertVersion(7);

return {
name: "plugin-demo",
visitor: {
BinaryExpression(path) {
if (path.node.operator !== "===") {
return;
}
path.node.left = t.identifier("sebmck");
path.node.right = t.identifier("dork");
},
}
};
});

// config
module.exports = function (api) {
api.cache(true)

const presets = [
['@babel/env', {
useBuiltIns: 'usage',
corejs: 3,
}]
]

return {
presets,
plugins: [
['@babel/transform-runtime', {
corejs: 3,
}],
pluginDemo,
]
};
}

其他