来源:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
Babel 的使用就是使用一些预设配置。预设就是一组插件,比如env, stage-3 flow react typescript预设。
Babel的核心就是插件,插件就是基于核心函数,对AST树的一些操作
Babel 核心的核心就是一组函数
【重要函数】 parser traverse generator core.parser core.transorm
【辅助函数】 code-frame helpers template types
Babel 插件手册是官网编写的插件指南!涉及基本概念,API,转换操作,构建节点,最佳实践等部分
一、基本概念
AST抽象语法树
所有类型节点都有Node 接口以及其它参数,比如代码的起始位置,参数,左右节点等等:
interface Node { type: string; }
Babel处理步骤
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。.
遍历AST之访问者模式:
通过访问者模式,访问每一个节点! 深度递归遍历整个树。
1、访问者名字:
最普通是用Node 的 type 名为访问者的名字, 它是一个函数,但有几种高级用法 ,见下面示例
const MyVisitor = {
// 1、所有节点类型进入 参见:https://www.babeljs.cn/docs/babel-traverse#usage
enter(path){}
// 2、某类型的进入或退出
Identifier: {
enter() {
console.log("Entered!");
},
exit() {
console.log("Exited!");
}
}
// 3、通过某个类型名称
MemberExpression(){ }
// 4、通过helps中定义 的 别名
Function(path) {}
// 5、同时匹配几种类型
"ExportNamedDeclaration|Flow"(path) {}
}
babel-types中已定义一组别名,比如Function 可以代表多种Node类型, FunctionDeclaration
, FunctionExpression
, ArrowFunctionExpression
, ObjectMethod
and ClassMethod
。参见下面源码
2、访问者的参数--path:
第一感觉,这个参数应该是Node,但通过前面代码可以发现它叫:path。
其实参数是Path对象,它是表示两个节点之间连接的对象。 在巨大的AST树中,用来表示节点的位置的对象,Path对象的node才是节点本身,parent是父节点对象。 还有其它很多相关属性, 恩,想想也满合理的设计,为插件编写者省了不少力!
{ "parent": {...}, "node": {...}, "hub": {...}, "contexts": [], "data": {}, "shouldSkip": false, "shouldStop": false, "removed": false, "state": null, "opts": null, "skipKeys": null, "parentPath": null, "context": null, "container": null, "listKey": null, "inList": false, "parentKey": null, "key": null, "scope": null, "type": null, "typeAnnotation": null }
3、执行访问者:
traverse(someAST , MyVisitor )
每一个path对象也可以traverse. path.traverse( SomeVisitor , contextObj) 。此时就不传入AST了,因为path已然包含AST信息, 第二个参数contextObj是表示传入SomeVisitor访问都的上下文,可以通过this.xxxValue来访问。
4、(访问者间的)States
官方的例子看的人难受, 我以为它是想说明这样一个问题:
所有的匹配到的访问者函数会被调用,在函数中,不应该使用全局变量(也访问者函数之外的变量),来维护某一个States。
如果我在某一个访问函数中,计算出一个值,想把这个值传递给下级的AST树,让下级节点处理某一个操作时, 应该在当前位置执行一个新的traverse, 此即相当于嵌套递归。 前面的计算值可以通过对象contextObj 传递给下级AST树 path.traverse( SomeVisitor , contextObj)
5、Scopes 和 Binding
说实话,真看不懂呀!Scope就是一个层层嵌套的作用域,也叫Block,它包含Path(比Path还大一级的东西 ) Binding则用于从Scope中去访问变量!
二 重要的API
这部分直接看Babel官方文档更好理解,https://www.babeljs.cn/docs/babel-parser
1、babylon( 相当于Babel)
babylon引入Parse方法,解析code字符串。 但在2018年,babylon就废弃了,变成Babel.parse了。 从这个角度说明,这份中文的Babel插件文档已经2年未更新了,内容过时了。 去官网文档英文版查找追踪过来,内容和中文版一致。
2、traverse
Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点。
3、Babel Types
Types包含一组函数,每个节点类型的定义,节点构造器,节点验证器,Converters(变换器)!
其中Converters(变换器)的内容未编写
由于以下内容,简短且重要的概念,转引过来!
Babel Types模块是一个用于 AST 节点的工具库, 它包含了构造、验证以及变换 AST 节点的方法。 该工具库包含考虑周到的工具方法,对编写处理AST逻辑非常有用。 它还包含Definitions ,拥有每一个单一类型节点的定义,包括节点包含哪些属性,什么是合法值,如何构建节点、遍历节点,以及节点的别名等信息。
单一节点类型的定义Definitions 形式如下:
defineType("BinaryExpression", { builder: ["operator", "left", "right"], fields: { operator: { validate: assertValueType("string") }, left: { validate: assertNodeType("Expression") }, right: { validate: assertNodeType("Expression") } }, visitor: ["left", "right"], aliases: ["Binary", "Expression"] });
你会注意到上面的 BinaryExpression
定义有一个 builder
字段。.
builder: ["operator", "left", "right"]
这是由于每一个节点类型都有构造器方法builder,按类似下面的方式使用:
t.binaryExpression("*", t.identifier("a"), t.identifier("b"));
可以创建如下所示的 AST:
{ type: "BinaryExpression", operator: "*", left: { type: "Identifier", name: "a" }, right: { type: "Identifier", name: "b" } }
当打印出来之后是这样的:
a * b
构造器还会验证自身创建的节点,并在错误使用的情形下会抛出描述性错误,这就引出了Validators(验证器)
BinaryExpression
的定义还包含了节点的字段 fields
信息,以及如何验证这些字段。
fields: { operator: { validate: assertValueType("string") }, left: { validate: assertNodeType("Expression") }, right: { validate: assertNodeType("Expression") } }
4、Babel Generator模块
Babel 的代码生成器,它读取AST并将其转换为代码和源码映射(sourcemaps)。
generate(ast, { retainLines: false, compact: "auto", concise: false, quotes: "double", // ... }, code);
这里想不明白的是,为什么第三个参数是code,原代码的原文??
5、babel-template
是另一个虽然很小但却非常有用的模块。 它能让你编写字符串形式且带有占位符的代码来代替手动编码, 尤其是生成的大规模 AST的时候。 在计算机科学中,这种能力被称为准引用(quasiquotes)。
三 编写Babel插件
由于插件是被Babel调用的工具链上的一个步骤,所以它只关注Ast 的编辑动作即可, AST的解析与代码生成它都不必关心!所以它接收一个Babel对象作为入参,返回一个Vistor对象!
export default function(babel) {
return {
visitor: {
Identifier(path, state) {}
}
};
};
四 转换操作
既然了解了基本概念,API和插件的写法, 那么知道我们在插件里,能怎么操作AST,怎么辗转腾挪就是重点,这些能力就决定了插件的能力!现在有很多UI框架可以适配多端平台,比如Taro, uni之类的框架,大概都是基于这种技术实现的。针对一个框架编写的代码如何生成多端适配的代码呢,那就看一下Babel的变换能力吧!