Babel-工具集

Babel 实际上是一组模块的集合。这里将探索一些主要的模块,解释它们是做什么的以及如何使用它们。

##@babel/parser

定义了把代码解析成AST的一套规范

语法

1
2
3
4
5
6
7
8
9
10
require("@babel/parser").parse("code", {
// 将会在严格模式下解析并且允许模块定义,script 模式不允许import、export,会报错
sourceType: "module", // default: "script"

plugins: [ // default: "script"
// enable jsx and flow syntax
"jsx",
"flow",
],
});

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import * as babelParser from "@babel/parser";
const code = `function square(n) {
return n * n;
}`;

const ast = babelParser.parse(code);
// Node {
// type: "File",
// start: 0,
// end: 42,
// loc: SourceLocation {...},
// errors: [],
// program: Node {...},
// comments: []
// }

@babel/traverse

Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点。

之前也说到,path参数里面的属性和方法都是在babel-traverse里面定义的。这里还是引用一个例子,将babel-traverse和Babylon一起使用来遍历和更新节点:

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
npm install --save @babel/traverse
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";

const code = `function square(n) {
return n * n;
}`;

const ast = parser.parse(code);

traverse(ast, {
enter(path) {
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x";
}
},
});
// 或者指定节点类型
traverse(ast, {
FunctionDeclaration: function(path) {
path.node.id.name = "x";
},
});

例子

一个坑:尝试这个例子时,使用的是 webpack5,发现会报 [Uncaught ReferenceError: process is not defined]。谷歌的答案是定义环境,但尝试无果,直接把 webpack 降级到 4,同时还需要将 html-webpack-plugin 也降级到 4

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
import * as babelParser from "@babel/parser";
import traverse from "@babel/traverse";

const code = `function square(n) {
return n * n;
}`;

const ast = babelParser.parse(code);

traverse(ast, {
enter(path) {
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x";
}
}
});

console.log(ast) // 此时 AST 里面的 n 已经换成 x
// "params": [
// {
// "type": "Identifier",
// "start": 16,
// "end": 17,
// "loc": {
// "start": {
// "line": 1,
// "column": 16
// },
// "end": {
// "line": 1,
// "column": 17
// },
// "identifierName": "n"
// },
// "name": "x" // 已经变化
// }
// ],

@babel/types

一个强大的用于处理AST节点的工具库,它包含了构造、验证以及变换AST节点的方法。该工具库包含考虑周到的工具方法,对编写处理AST逻辑非常有用。

语法

1
2
3
4
5
6
7
8
9
10
11
12
npm install --save-dev @babel/types

import traverse from "babel-traverse";
import * as t from "babel-types";

traverse(ast, {
enter(path) {
if (t.isIdentifier(path.node, { name: "n" })) {
path.node.name = "x";
}
}
});

例子

判断import导入是什么类型的导入

1
2
3
import { Ajax } from '../lib/utils';
import utils from '../lib/utils';
import * as utils from '../lib/utils';

在AST中用于表示上面导入的三个变量的节点是不同的,分别叫做ImportSpecifier、ImportDefaultSpecifier和ImportNamespaceSpecifier。如果我们只对导入指定变量的import命令语句做处理,那么我们的babel插件就可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import traverse from "babel-traverse";
import * as t from "babel-types";

const code = `import { Ajax } from '../lib/utils';
import utils from '../lib/utils';
import * as utils1 from '../lib/utils';`

const ast = babelParser.parse(code, { sourceType: "module" });
traverse(ast, {
ImportDeclaration(path, state) {
const specifiers = path.node.specifiers;
console.log('==', specifiers)
// [{ "type": "ImportSpecifier",...}]
// [{ "type": "ImportDefaultSpecifier",...}]
// [{ "type": "ImportNamespaceSpecifier",...}]

specifiers.forEach((specifier) => {
if (!t.isImportDefaultSpecifier(specifier) && !t.isImportNamespaceSpecifier(specifier)) {
console.log('do something') // "ImportSpecifier" 时输出
}
})
}
});

Definitions(定义)

Babel Types模块拥有每一个单一类型节点的定义,包括节点包含哪些属性,什么是合法值,如何构建节点、遍历节点,以及节点的别名等信息。

单一节点类型的定义形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"]
});

Builders(构建器)

你会注意到上面的 BinaryExpression 定义有一个 builder 字段。.

1
builder: ["operator", "left", "right"]

这是由于每一个节点类型都有构造器方法builder,按类似下面的方式使用:

1
t.binaryExpression("*", t.identifier("a"), t.identifier("b"));

可以创建如下所示的 AST:

1
2
3
4
5
6
7
8
9
10
11
12
{
type: "BinaryExpression",
operator: "*",
left: {
type: "Identifier",
name: "a"
},
right: {
type: "Identifier",
name: "b"
}
}

当打印出来之后是这样的:

1
a * b

构造器还会验证自身创建的节点,并在错误使用的情形下会抛出描述性错误,这就引出了下一个方法类型。

Validators(验证器)

BinaryExpression 的定义还包含了节点的字段 fields 信息,以及如何验证这些字段。

1
2
3
4
5
6
7
8
9
10
11
fields: {
operator: {
validate: assertValueType("string")
},
left: {
validate: assertNodeType("Expression")
},
right: {
validate: assertNodeType("Expression")
}
}

可以创建两种验证方法。第一种是 isX。.

1
t.isBinaryExpression(maybeBinaryExpressionNode);

这个测试用来确保节点是一个二进制表达式,另外你也可以传入第二个参数来确保节点包含特定的属性和值。

1
t.isBinaryExpression(maybeBinaryExpressionNode, { operator: "*" });

这些方法还有一种断言式的版本,会抛出异常而不是返回 truefalse。.

1
2
3
t.assertBinaryExpression(maybeBinaryExpressionNode);
t.assertBinaryExpression(maybeBinaryExpressionNode, { operator: "*" });
// Error: Expected type "BinaryExpression" with option { "operator": "*" }

@babel/generator

Babel 的代码生成器,它读取AST并将其转换为代码和源码映射(sourcemaps)

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
npm install --save-dev @babel/generator

import { parse } from "@babel/parser";
import generate from "@babel/generator";

const code = "class Example {}";
const ast = parse(code);

const output = generate(
ast,
{
/* options */
},
code
);

例子

沿用前面的例子,将代码的 n 变量换成 x

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
import * as babelParser from "@babel/parser";
import traverse from "@babel/traverse";
import * as t from "babel-types";
import generate from "@babel/generator";

const code = `function square(n) {
return n * n;
}`;

const ast = babelParser.parse(code);

traverse(ast, {
enter(path) {
if (t.isIdentifier(path.node, { name: "n" })) {
path.node.name = "x";
}
}
});

const result = generate(ast, {/* options */}, code);
console.log(result)
// {
// "code": "function square(x) {\n return x * x;\n}",
// "map": null
// }

@babel/template

虽然很小但却非常有用的模块。 它能让你编写字符串形式且带有占位符的代码来代替手动编码, 尤其是生成的大规模 AST的时候。 在计算机科学中,这种能力被称为准引用(quasiquotes)。

语法

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
npm install --save-dev @babel/template

import template from "@babel/template";
import generate from "@babel/generator";
import * as t from "@babel/types";

// 第一种写法
const buildRequire = template(`
var %%importName%% = require(%%source%%);
`);

const ast = buildRequire({
importName: t.identifier("myModule"),
source: t.stringLiteral("my-module"),
});

// 第二种写法
const buildRequire = template(`
var IMPORT_NAME = require(SOURCE);
`);

const ast = buildRequire({
IMPORT_NAME: t.identifier("myModule"),
SOURCE: t.stringLiteral("my-module"),
});

// 第三种写法
const name = "my-module";
const mod = "myModule";

const ast = template.ast`
var ${mod} = require("${name}");
`;

console.log(generate(ast).code); // const myModule = require("my-module");

参考文章

Babel 插件手册

仓库