背景
我一直在思考一个问题,为什么线上出现问题时总是难以找到原因呢?每一次都是遇到问题之后,开始排查问题,开始根据报错信息直接去翻看源码(我相信一般的公司应该不会把 sourcemap 打一份放到内网里面),于是翻来覆去一顿操作之后找到了问题,然后紧急发布解决问题;然后每一次出现线上故障都是这样的操作,非常之低效;
既然要快速定位线上问题,那就必须对线上代码有所了解;可以说大部分同学都只了解自己所写代码,而并不了解打包之后代码成了什么样子(由于浏览器兼容性问题,打包之后代码可能会面目全非)。我不想成为他们之中的一员,所以一直在看打包之后的代码,以及编译器 babel,即使现在新出了 swc、esbuild,babel 仍然是主流。最近我在看装饰器的编译时犯了难,这里面用到了@babel/types 这个库,当我去查看文档时,它的文档是这样的:
没有示例,也没有解释这个表达式的含义,只给了一些参数,总之这个文档太水了。现在我要让你们忘记这个文档,不要再看这个文档,回想一下第一次学 js 的场景。
js 数据类型
第一天学的一定是 js 的数据类型:
- Null
- Undefined
- Boolean
- Number
- String
- Symbol
- Object
用 babel 如何生成这些类型的值呢?请大家先准备好 node 环境,安装如下依赖:
- @babel/generator
- @babel/types或者@babel/core(我这里用的@babel/core)
大功告成之后,先定义一个函数,将 ast 转换为代码,方便后续打印值;
js
const generate = require("@babel/generator").default;
function log(node) {
const code = generate(node).code;
console.log("code:", code);
}
创建简单类型值
js
const { parse, types } = require("@babel/core");
const _null = types.nullLiteral();
log(_null); // code: null
const boolean = types.booleanLiteral(true);
log(boolean); // code: true
const number = types.numericLiteral(42);
log(number); // code: 42
const string = types.stringLiteral("Hello, World!");
log(string); // code: Hello, World!
总结一下简单数据类型如何创建 ast:
- Null:types.nullLiteral()
- Boolean:types.booleanLiteral(true)
- Number:types.numericLiteral(42)
- String:types.stringLiteral("Hello, World!")
- Undefined和 Symbol 类型都没有办法创建
创建Array
object 类型,又分为 Array、Object、Function等等,数组类型最为简单,先实现一个简单的数组吧!实现数据使用:arrayExpression
js
const string = types.stringLiteral("Hello, World!");
const number = types.numericLiteral(42);
const boolean = types.booleanLiteral(true);
const array = types.arrayExpression([string, number, boolean]);
log(array); // code: ["Hello, World!",42,true]
创建 Object
对象类型比较复杂,首先要执行objectExpression
创建对象,然后objectProperty
声明属性:
js
const object = types.objectExpression([
types.objectProperty(types.stringLiteral("name"), types.stringLiteral("John")),
types.objectProperty(types.stringLiteral("age"), types.numericLiteral(30)),
]);
log(object); // {name:"John",age:30}
创建函数
函数声明包含函数名、参数、函数体,另外一个函数还需要区分是否为 generator 函数,是否为 async 函数,创建函数需要借助functionExpression
,有五个参数:
- id: types.Identifier, 函数名
- params: (types.Identifier | types.RestElement | types.Pattern)[], 函数参数
- body: types.BlockStatement, 函数体
- generator?: boolean, 是否为generator函数
- async?: boolean,是否为异步函数
函数名和参数名都属于标识符,函数体属于块声明,需要blockStatement创建,执行函数需要callExpression创建,表达式则为expressionStatement
接下来实现一个打印其参数的函数:
js
const func = types.functionExpression(
types.identifier("func"),
[types.identifier("params")],
types.blockStatement([
types.expressionStatement(types.callExpression(types.identifier("console.log"), [types.identifier("params")])),
]),
false,
false
);
log(func);
// code:function func(params) {
// console.log(params);
// }
总结一下创建复杂数据类型都用到了哪些方法:
- Array:types.arrayExpression()
- Object:types.objectExpression()、types.objectProperty()
- Function:types.functionExpression()、types.blockStatement()、types.expressionStatement()、types.callExpression()
js 语法
变量声明variableDeclaration
js
const res = types.variableDeclaration("const", [
types.variableDeclarator(types.identifier("age"), types.numericLiteral(42)),
]);
log(res); // code: const age = 42;
赋值表达式assignmentExpression
赋值表达式首先需要传入操作符,然后就是左值和右值,但是这里如果没有声明则是全局变量
js
log(types.assignmentExpression("=", types.identifier("age"), types.numericLiteral(42)));
// code: age = 42
序列表达式sequenceExpression
序列表达式其实就是逗号操作符,比如:(1,2),利用sequenceExpression创建
js
log(types.sequenceExpression([types.numericLiteral(1), types.numericLiteral(2)]));
// 2
条件语句ifStatement
js
const ifstatement = types.ifStatement(
types.identifier("name"),
types.blockStatement([
types.expressionStatement(
types.assignmentExpression("=", types.identifier("name.this"), types.stringLiteral("John"))
),
])
);
log(ifstatement);
// code: if (name) {
// name.this = "John";
// }
循环语句forStatement
js
const forstatement = types.forStatement(
types.variableDeclaration("let", [types.variableDeclarator(types.identifier("i"), types.numericLiteral(0))]),
types.binaryExpression(">", types.identifier("i"), types.numericLiteral(10)),
types.assignmentExpression(
"=",
types.identifier("i"),
types.binaryExpression("+", types.identifier("i"), types.numericLiteral(1))
),
types.blockStatement([
types.expressionStatement(types.callExpression(types.identifier("console.log"), [types.identifier("i")])),
])
);
log(forstatement);
// code: for (let i = 0; i > 10; i = i + 1) {
// console.log(i);
// }
到这里为止就实现了一些常用数据类型和语法,为以后写 babel 插件打下基础