前言
不知道各位同学在开发的过程中有没有遇到这样的一种情况:就是跟你一起合作做项目的同学调试的时候打了不少 console
,然后提交代码的时候没删。
打印得多了,可能就长成下面的样子。特别是有一些在公共的数据变化时打印,就更加惨不忍睹。

这就给我们自己调试的时候,想在控制台找到自己打印的东西比较麻烦,虽然说花点心思找一下或者搜一下也能找到,但是我为啥要花时间花心思在这上面呢?而且这么多打印的东西看着就烦。

今天,我们就写一个 vite
插件,来去掉同事写的 console.log
,当然,要保留我们自己写的。
vite插件初体验
首先新建一个 remove-console-plugin
目录,在这个目录下新建一个 index.js
文件夹。在 vite
插件体系中,修改输出的代码用的是 transform
这个钩子:
js
export default function myPlugin() {
return {
name: "remove-console-plugin",
transform(code, id) {
console.log("code", code);
return code
},
};
}
然后我们可以先随便改点什么东西,比如说我在 js
文件的最后加一行注释:
js
transform(code, id) {
const url = id;
if (url.includes("/src/") && /\.([tj]sx?|js)$/.test(url)) {
return code + `\n` + "// 一行注释";
}
return code;
},

那么可以看到,请求回来的文件已经带上了我们加的内容。
但这里有一个问题, transform
钩子调用时,代码已经被预处理(例如通过 ESBuild
或者 TypeScript
编译器)过了。
后面我们需要分析这行代码是谁写的,因为我们做的是一个去除别人代码 console
的一个插件,所以我们需要拿到源文件的内容。
拿到源文件内容的话就要使用到 load
这个钩子函数:
js
load(id) {
const url = id;
if (url.includes("/src/") && /\.([tj]sx?|js)$/.test(url)) {
let originalContent = fs.readFileSync(id, "utf-8");
return originalContent;
}
}
控制台打印一下,我们确实已经能拿到源文件的内容:

AST去console
然后,转换代码我们需要用到 AST(抽象语法树)
。可以使用 Babel
来实现,首先安装一下必要的依赖:
bash
npm install @babel/parser @babel/traverse @babel/generator
比如我写了一个这样的代码
js
console.log(123)
我们可以在这个 AST工具网站 中看到它转换成 AST
之后是一个怎样的结构:

AST
的使用流程主打一个解析-转换-生成
- 解析:把我们的源代码解析成
AST
结构,可以理解为一颗JSON
树,然后通过traverse
这个库的访问者模式,它可以访问这棵树的每一个节点 - 转换:找到你需要对它操作的节点,使用
AST
的相关操作,改变这个节点的内容。比如下面的path.remove()
,就是把这个节点删掉。 - 生成:把
AST
再次转回源代码
写出下面的代码,找出 src
文件夹下所有 console.log
的位置,且只在开发模式下生效:
js
import fs from "fs";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generator from "@babel/generator";
let isDev = false;
export default function myPlugin() {
return {
name: "remove-console-plugin",
config(config, ctx) {
isDev = ctx.mode === "development";
},
load(id) {
const url = id;
if (url.includes("/src/") && /\.([tj]sx?|js)$/.test(url) && isDev) {
let originalContent = fs.readFileSync(id, "utf-8");
const ast = parse(originalContent, {
sourceType: "module",
plugins: ["jsx", "typescript"],
});
traverse.default(ast, {
CallExpression(path) {
if (
path.node.callee.type === "MemberExpression" &&
path.node.callee.property.name === "log"
) {
console.log(
`我在文件${id}的第${path.node.loc.start.line}行找到了console.log。`
);
path.remove(); // 删除这个节点
}
},
});
const { code } = generator.default(ast);
return code;
}
},
};
}

这跟我们对应的文件中 console.log
的位置是一致的:

可以看到我们调用 path.remove()
之后,控制台一片清净,大块大块的 console.log
已经没有了。

git接入
当然,这样做是不行的,这样把我们自己打的 console
也去掉了。我们是想去掉别人打的 console
。
那怎么知道这行是谁打的呢?换个问题就是,我怎么知道这行代码是谁写的?
那当然是依赖代码管理工具咯,我们最常用的始终是 git
,所以这里是以 git
为依托。
这里封装一个执行系统命令的函数,方便我们之后调用各种系统命令。
js
const execCommand = (command) => {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (err) {
reject(err);
return;
}
if (stderr) {
reject(new Error(stderr));
return;
}
resolve(stdout.trim());
});
});
};
然后执行 git config user.name
来获取用户名
js
if (!username) {
username = await execCommand("git config user.name");
}
获取到用户名之后,我们可以使用 git blame 文件路径 | nl -n ln
这个命令去对每一个文件进行分析,它打印的结果如下

可以看到这里包含了行号、提交人。
我们就可以对所有需要处理的文件进行分析,并组装成一个 map
, key
是行号, value
是提交人
js
const blameOutput = await execCommand(`git blame ${id} | nl -n ln`);
let map = blameOutput
.trim()
.split("\n")
.reduce((acc, line) => {
let [numStr, hash, author, ...rest] = line.split(/\s+/);
let num = parseInt(numStr, 10);
acc[num] = author.replace("(", "").replace(")", "");
return acc;
}, {});
console.log("map", map);
可以得到这样的一个 map

最后就可以通过 console.log
的行号与提交的信息去匹配,如果这条 console
不是我提交的、或者不是未提交的,那么这条打印信息我们就应该去掉。

js
const logLine = path.node.loc.start.line;
const commiter = map[logLine];
if (commiter !== username && commiter !== "Not") {
path.remove();
}
我们就可以看到,别人打印的都已经去掉了,留下来的都是我们自己打印的,整个世界清净了,舒服了。



最后
按理来说, console.log
就不应该被提交到代码仓库中。如果你的项目中有很严格的各种规范校验,那么还是很舒服的,不需要被这种琐碎的事情所打扰。
如果没有,那么就想个办法去解决他吧!
以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~