准备工作
此文前,需要先阅读
- 抽象语法树 AST 与 编译器 Compiler
- babel 7 全套
- Babel 6 插件手册 (PS:尽管大部分 API 已过时,但是大概的理念仍然适用)
概念
- 树形遍历
- Visitors(访问者)
- Paths(路径)
- State(状态)
- Scopes(作用域)
- Bindings(绑定) —— 或者叫变量引用?
- 转换操作(插件 / transform / 节点操作) —— 类似 jQuery
PS:如果不开发插件,其实看插件相关内容意义不大,但对于理解 babel 整套流程,还是有一定帮助。
@babel/helper-xxxx-xxxx
各种各样的辅助函数、方法 / 功能。例如:
- @babel/helper-plugin-utils index.js 辅助插件开发,但只是做了一层 wrapper
- @babel/helper-builder-react-jsx index.js 用于 @babel/plugin-transform-react-inline-elements index.js、@babel/plugin-transform-react-jsx-compat index.js 转换插件
- 等等 ...
@babel/plugin-xxxx-xxxx
为什么会有 plugin-syntax-xxxx-xxxx
和 plugin-transform-xxxx-xxxx
插件? —— Transform plugin vs Syntax plugin in Babel
即 plugin-syntax-xxxx-xxxx
是 plugin-transform-xxxx-xxxx
的前提,或者说,就是用来给 plugin-transform-xxxx-xxxx
、plugin-proposal-xxxx-xxxx
继承用的。
需要有 plugin-syntax-xxxx-xxxx
,设定语法解析的方式,对应的语法,才能被 @babel/parser
正确地处理,类似的: 如果不设定解析 generator 函数,function*(x) { }
肯定是报错的
再例如:
- @babel/plugin-proposal-function-bind index.js 作为转换插件,其继承了
- @babel/plugin-syntax-function-bind index.js,而 @babel/plugin-syntax-function-bind index.js 内部,通过
parserOpts.plugins.push("functionBind");
设定了functionBind
的解析 - parser 阶段,根据
functionBind
的设定,进行不同的 词法分析(tokenizer/index.js) (其他插件可能也会有不同的语法分析),最终生成 AST @babel/plugin-proposal-function-bind
对 AST 进行转换
所以,如果我们想编写 babel 插件来将 @@@hello
转换为 world.hello
是做不到的,因为 @@@hello
在 babel 的 parser 上不支持。其内置的,可以进行配置的额外插件见:@babel/parser/typings/babel-parser.d.ts
而如果我们想编写一些插件,又依赖一些语法(可能该新语法未启用),需要进行:
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
开发个最简单的插件
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,
]
};
}
其他
- babel-plugin-tester —— 插件测试工具