前言
上期文章有个小伙伴提出了一个很有意思的提议

这提议很有创意,很有意思呀,那必须安排
需求描述
我们在本地开发的时候会打印一些console.log()
来方便调试,和打印执行结果
比如
js
console.log('你好');
现在我们要实现的结果是,在周四的时候,console.log变成如下
js
console.log('疯狂星期四,v我50🐶','你好');
实现思路拆解
1. 首先我们要在astexplorer
网站查看console.log的AST:

program
是代表整个程序的节点,它有body属性代表程序体,存放statement
(语句)数组,就是具体执行的语句的集合。因为一个程序就是由不同的语句组成的,所以statement
是一个数组。
CallExpression
(函数调用表达示):callee
属性是一个表达式节点,表示函数体,arguments
是一个数组,元素是表达式节点,表示函数参数列表.
其他的我们不用知道太多,知道太多也记不住,我们主要找我们想改变的东西所在的type
我们要做的是在遍历AST的时候对console.log
、console.info
等api
自动插入一些参数,也就是通过visitor
指定对callExpression
的AST做一些修改。
2. 打开transform
,开始编写插件

在这里我们穿插讲一下@babel/types
@babel/types
是做什么用的呢?
我们看一下文档上的解释
This module contains methods for building ASTs manually and for checking the types of AST nodes.(这个模块包含一些手动创建AST和检测AST类型的方法)
这个模块是非常常用的
比如我们想创建一个字符串,那么我们就可以使用
js
t.stringLiteral(value);
如果我们想判断是不是字符串 可以使用
js
t.isStringLiteral(node, opts)
还是很好记的
首先我们判断函数的类型是否是isMemberExpression
,然后判断函数体的名称等

现在我们实现如下,还是比较简单的,但是右侧显示我们输入的字符串都是unicode
形式,问题不大,稍后我们会配置,然后把写的plugin内容,放到我们项目中
./pluginDemo
js
module.exports = (babel) => {
const { types: t } = babel;
return {
name: "ast-transform", // not required
visitor: {
CallExpression(path) {
if (t.isMemberExpression(path.node.callee)
&& path.node.callee.object.name === 'console'
&& ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
) {
path.node.arguments.unshift(t.stringLiteral('疯狂星期四,v我50🐶'))
}
}
}
};
}
在.babelrc
中配置generatorOpts
,这个主要是解决显示中文问题的
js
{
"plugins": ["./pluginDemo.js"],
"generatorOpts" :{
"jsescOption": {
"minimal": true
}
}
}
测试一下,看一下dist
中的输出,没有问题

3.判断是否是周四
如果获取当前是否是周四呢?
js
const today = new Date().getDay(); // 获取当前星期几,0表示星期日,1表示星期一,以此类推
所以最后完整代码
js
module.exports = (babel) => {
const { types: t } = babel;
const today = new Date().getDay();
return {
name: "ast-transform", // not required
visitor: {
CallExpression(path) {
if (today !== 4) {
return;
}
if (t.isMemberExpression(path.node.callee)
&& path.node.callee.object.name === 'console'
&& ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
) {
path.node.arguments.unshift(t.stringLiteral('疯狂星期四,v我50🐶'))
}
}
}
};
}
需求变更
当我们这样子打印console.log
的时候,有时候因为console.log
的日志因为比较乱,反而影响观看,所以我们决定在consol.log
之前打印---疯狂星期四,v我50🐶
在讲解代码之前我们先来讲解一下@babel/template
@babel/template
通过 @babel/types
创建 AST 还是比较麻烦的,要一个个的创建然后组装,如果 AST 节点比较多的话需要写很多代码,这时候就可以使用 @babel/template
包来批量创建。
这个包有这些 api:
javascript
const ast = template(code, [opts])(args);
const ast = template.ast(code, [opts]);
const ast = template.program(code, [opts]);
比如我们要创建一个console.log(), 因为console.log是表达式,所以我们要创建一个expression
js
const string = "疯狂星期四,v我50🐶";
const newNode = template.expression(`console.log("${string}")`)();
添加console.log注意点
因为我们要往console.log
前面添加console.log
,而我们新添加的console.log
他也会遍历,这样就会出现一个无限循环的问题
如何解决呢?
我们可以在新添加的节点上加上一个标识,如何使新添加的console.log,那么就不用遍历
js
newNode.isNew = true;
最终我们的代码
js
const template = require('@babel/template');
module.exports = (babel) => {
const { types: t } = babel;
const today = new Date().getDay();
return {
name: "ast-transform", // not required
visitor: {
CallExpression(path) {
if (today !== 4) { return; }
if (
t.isMemberExpression(path.node.callee) &&
path.node.callee.object.name === "console" &&
["log", "info", "error", "debug"].includes(path.node.callee.property.name)
) {
if (path.node.isNew) {
return;
}
const string = "疯狂星期四,v我50🐶";
const newNode = template.expression(`console.log("${string}")`)();
newNode.isNew = true;
path.insertBefore(newNode);
}
}
}
};
}
总结
- 今天我们学习了
@babel/types
和@babel/template
的使用
@babel/types
主要用于创建AST节点和判断AST节点类型@babel/template
主要是因为babel/types
创建AST节点比较复杂,所以使用babel/template
来进行创建
- 然后我们学习了如何插入节点,如何防止死循环的问题