1.babel 简介
Babel是现代JavaScript语法转换器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中,包括不限于: eslint jsx vue-template等等。他能为你做的:语法转换、通过 Polyfill 方式在目标环境中添加缺失的特性、源码转换。可以说,通过babel,我们可以使用最新的语法或者特性专注的开发业务,而不用将精力花在代码的兼容上
2.babel的解析流程
2.1词法解析
首先由入口文件@babel/core
引入各种配置文件以及解析的模块,其中负责解析基本语法的为parse
模块,其中就定义了词法解析器Tokenizer
,可以看出初始化时会有一个tokens
数组,在这个阶段会把字符串形式的代码转换为一个数组,tokens可以视为由拆分为各个片段组成的数组
。
class Tokenizer extends LocationParser {
constructor(options, input) {
super();
this.tokens = [];
this.state = new State();
this.state.init(options);
this.input = input;
this.length = input.length;
this.isLookahead = false;
}
....
}
在Tokenizer
定义中还有一些其它的方法,他们会对各种语法字符串进行一个遍历过滤,比如跳过空格字符串,比如跳过注释等等
skipSpace() {
loop: while (this.state.pos < this.length) {
const ch = this.input.charCodeAt(this.state.pos);
.
.
.
}
}
最后会把符合规则的语法字符串加入到tokens
数组中,也就生成代码拆分为各个片段组成的数组
pushToken(token) {
this.tokens.length = this.state.tokensLength;
this.tokens.push(token);
++this.state.tokensLength;
}
如下所示:
console.log('我是一段语法') => ['console', 'log', '(', '"我是一段语法"', ')']
2.2语法解析
当生成完tokens
,则进入到语法解析阶段,同样可以看见在parse
模块中定一个了一个Node
对象,这个对象定义了一些属性,当然还有个私有的_clone
方法,用于表示将要生成的AST
中节点的位置、名称、类型等等信息
class Node {
constructor(parser, pos, loc) {
this.type = "";
this.start = pos;
this.end = 0;
this.loc = new SourceLocation(loc);
if (parser && parser.options.ranges) this.range = [pos, 0];
if (parser && parser.filename) this.loc.filename = parser.filename;
}
__clone() {
.
.
.
}
}
然后会定义相应的Parse
,遍历处理不同的tokens
字段,比如ImportDeclaration
用于import语法,导入模块;VariableDeclarator
用于处理表示是什么类型的变量声明,比如var、let、const
;FunctionDeclaration
用于处理函数声明,非函数表达式,其它的就不多赘述。最后则会将这个词法数组转换为AST(抽象语法树)
var add = function(a, b) {
return a + b
}
最后生成的AST
简易版本如下
{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"identifierName": "add",
"init": {
"type": "ArrowFunctionExpression",
"params": [{
"type": "identifier",
"identifierName": "a"
},
{
"type": "identifier",
"identifierName": "b"
}
],
"body": {
"type": "BinaryExpression",
"left": {
"type": "identifier",
"identifierName": "a"
},
"operator": "+",
"right": {
"type": "identifier",
"identifierName": "b"
}
}
}
}]
}
可以看出这个语法树包含了整个语法的一个层级关系,并且标注了他们的名称、类型以及位置
2.3Traverser
Traverser
会对生成好AST
进行一个遍历,在此过程中对节点进行添加、删除等等操作,这也是其他插件将要处理的地方,通过这些插件,也可以处理不同的AST
语法树,然后转换符合相应规则的代码,比如Taro
,就可以视为将React
代码转换成小程序对应代码的一个插件。Traverser
中会引入visitors
,用它来进行一个深度遍历操作;Path
用于关联各个节点,这样使得节点操作简单,例如:
例如,如果有下面这样一个节点及其子节点︰
{
type: "ArrowFunctionExpression",
id: {
type: "Identifier",
name: "a"
},
...
}
将子节点 Identifier
表示为一个路径(Path)的话,看起来是这样的:
{
"parent": {
"type": "ArrowFunctionExpression",
"id": {...},
....
},
"node": {
"type": "Identifier",
"name": "a"
}
}
Scope
用来表示树中各个节点的一个作用域关系,就好像js
语法中,创建变量、函数时所呈现的一个作用域效果,在babel
中,新添加的引用或者变量名,Scope
表示的时候需要体现出节点的路径,节点间的关系,如以下形式:
{
path: path,
block: path.node,
parentBlock: path.parent,
parent: parentScope,
bindings: [...]
.
.
.
}
还有个很重要的概念Bindings
,Bindings
可以获取当前作用域下所有的标识符,其返回的信息包括节点的标识符
、scope
、path
、引用
等等信息,通过对这些属性的操作,达到对节点信息的一个更改
export class Binding {
identifier: t.Identifier;
scope: Scope;
path: NodePath;
kind: "var" | "let" | "const" | "module";
referenced: boolean;
references: number; // 被引用的数量
referencePaths: NodePath[]; // 获取所有应用该标识符的节点路径
constant: boolean; // 是否是常量
constantViolations: NodePath[];
}
2.4Transform
transform
会对抽象语法树进行又一次遍历,针对已经处理好的AST
做进一步处理,如对代码的更改,对节点的操作、节点的增删改查、压缩代码、删除注释等等。得到最终的一个AST
2.5生成代码
得到上一步生成的AST
之后,会调用对应的代码生成器代码,通过递归遍历生成最终的代码,其中遇到不同的节点类型时,会做不同的处理,比如函数类型、参数定义类型、代码块类型等等
3.babel里面的一些依赖包
3.1@babel/core
@babel/core
整个babel
的一个核心,也算是babel
的一个入口,它会加载和处理用户定义的配置,加载各种各样的插件;调用Parser
进行语法解析,生成Tokens
以及AST
;调用Traverser
遍历AST
,进行一个转换;最后generator
生成源代码
3.2插件
语法插件:babel
有很多语法插件,用于支持JavaScript
的各种语法特性,在解析的时候会用的到,通常其形式为@babel/plugin-syntax-*
,比如babel-plugin-syntax-dynamic-import
就是用来处理import
语法的一个插件
转换插件: 用于对 AST
进行转换, 实现转换为ES5
代码、压缩、功能增强等目的,babel
仓库将转换插件划分为两种(只是命名上的区别):
如@babel/plugin-transform-*
: babel
中的一个转换插件
预定义的插件:插件集合或者分组,可设置项目内所用插件的使用场景,主要方便用户对插件进行管理和使用。
@babel/preset-env
:preset-env
是ES语法插件的合集,在根目录下创建.babelrc
配置文件
{
"presets": [
["@babel/preset-env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
},
"useBuiltIns": false,
"corejs": false
}]
]
}
targets
生成指定环境的代码,useBuiltIns
配合@babel/polyfill
使用,当然最新版的babel
可直接使用corejs
,填上对应的数值就行,modules
表示是否将代码ES6的模块语法转换为另一种类型或标准,比如amd
、commonjs
等
@babel/polyfill
顾名思义,在babel
转化的过程中,对于一些无法处理的特性或者属性,使用@babel/polyfill
来对这些功能进行处理;
@babel/runtime
一般应用于两种场景:开发类库/工具(生成不污染全局空间和内置对象原型的代码)、借助 @babel/runtime
中帮助函数(helper function)移除冗余工具函数,在最新的版本中,完全可以用@babel/runtime
代替@babel/polyfill
3.3辅助开发工具
@babel/template
: 某些场景直接操作AST
太麻烦,就比如我们直接操作DOM
一样,所以babel
实现了这么一个简单的模板引擎,可以将字符串代码转换为AST
。比如在生成一些辅助代码(helper)
时会用到这个库
@babel/types
: 主要用途是在创建AST
的过程中判断各种语法的类型。
@babel/helper-*
: 一些辅助器,用于辅助插件开发,例如简化AST
操作
@babel/helper
: 辅助代码,单纯的语法转换可能无法让代码运行起来,比如低版本浏览器无法识别class
关键字,这时候需要添加辅助代码,对class
进行模拟。
4.参考链接
2.babel中文网