https://astexplorer.net/ 查看AST。
1、抽象语法树 AST
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中,这个处理过程中的每一步都涉及到创建或是操作抽象语法树,亦称 AST。
function square(n) {
return n * n;
}
// 转换成下面的样子
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
params: [{
type: "Identifier",
name: "n"
}],
body: {
type: "BlockStatement",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
operator: "*",
left: {
type: "Identifier",
name: "n"
},
right: {
type: "Identifier",
name: "n"
}
}
}]
}
}
每一层都有相同的结构:
{
type: "FunctionDeclaration",
id: {...},
params: [...],
body: {...}
}
{
type: "Identifier",
name: ...
}
{
type: "BinaryExpression",
operator: ...,
left: {...},
right: {...}
}
这样的每一层结构也被叫做 节点(Node)。 一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。
字符串形式的 type 字段表示节点的类型(如: “FunctionDeclaration”,”Identifier”,或 “BinaryExpression”)。 每一种类型的节点定义了一些附加属性用来进一步描述该节点类型。
Babel 还为每个节点额外生成了一些属性,用于描述该节点在原始代码中的位置。比如 start end
2、Babel 的处理步骤
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。
2.1 解析
解析步骤接收代码并输出 AST。
- 词法分析:
- 词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。
- 你可以把令牌看作是一个扁平的语法片段数组:n * n
- 转换成tokens是这样的:
[
{ type: { … }, value: “n”, start: 0, end: 1, loc: { … } },
{ type: { … }, value: “*”, start: 2, end: 3, loc: { … } },
{ type: { … }, value: “n”, start: 4, end: 5, loc: { … } },
…
]
- 语法分析
- 语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作。
2.2 转换
转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程 同时也是插件将要介入工作的部分。
2.3 生成
代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。
代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。
3、简单写一个babel插件
yarn add @babel/core
yarn add babel-template
const template = require('babel-template');
const temp = template("var b = 1")
module.exports = function ({
types: t
}) {
// 插件内容
return {
visitor: {
// 接收两个参数path, state
VariableDeclaration(path, state) {
// 找到AST节点
const node = path.node;
// 判断节点类型 是否是变量节点, 申明方式是const
if (t.isVariableDeclaration(node, {
kind: "const"
})) {
// 将const 声明编译为let
node.kind = "let";
// var b = 1 的AST节点
const insertNode = temp();
// 插入一行代码var b = 1
path.insertBefore(insertNode);
}
}
}
}
}
使用插件:
const myPlugin = require('./plugin')
const babel = require('@babel/core');
const content = 'const name = xiaoming';
// 通过你编写的插件输出的代码
const {
code
} = babel.transform(content, {
plugins: [
myPlugin
]
});
console.log(code);
// var b = 1
// let name = 'xiaoming'