前言
不知道各位同学在开发的过程中有没有遇到这样的一种情况:就是跟你一起合作做项目的同学调试的时候打了不少 console
,然后提交代码的时候没删。
打印得多了,可能就长成下面的样子。特别是有一些在公共的数据变化时打印,就更加惨不忍睹。
这就给我们自己调试的时候,想在控制台找到自己打印的东西比较麻烦,虽然说花点心思找一下或者搜一下也能找到,但是我为啥要花时间花心思在这上面呢?而且这么多打印的东西看着就烦。
今天,我们就写一个 vite
插件,来去掉同事写的 console.log
,当然,要保留我们自己写的。
vite插件初体验
首先新建一个 remove-console-plugin
目录,在这个目录下新建一个 index.js
文件夹。在 vite
插件体系中,修改输出的代码用的是 transform
这个钩子:
export default function myPlugin() { return { name: "remove-console-plugin", transform(code, id) { console.log("code", code); return code }, }; }
然后我们可以先随便改点什么东西,比如说我在 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
这个钩子函数:
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
来实现,首先安装一下必要的依赖:
npm install @babel/parser @babel/traverse @babel/generator
比如我写了一个这样的代码
console.log(123)
我们可以在这个 AST工具网站 中看到它转换成 AST
之后是一个怎样的结构:
AST
的使用流程主打一个解析-转换-生成
- 解析:把我们的源代码解析成
AST
结构,可以理解为一颗JSON
树,然后通过traverse
这个库的访问者模式,它可以访问这棵树的每一个节点 - 转换:找到你需要对它操作的节点,使用
AST
的相关操作,改变这个节点的内容。比如下面的path.remove()
,就是把这个节点删掉。 - 生成:把
AST
再次转回源代码
写出下面的代码,找出 src
文件夹下所有 console.log
的位置,且只在开发模式下生效:
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
为依托。
这里封装一个执行系统命令的函数,方便我们之后调用各种系统命令。
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
来获取用户名
if (!username) { username = await execCommand("git config user.name"); }
获取到用户名之后,我们可以使用 git blame 文件路径 | nl -n ln
这个命令去对每一个文件进行分析,它打印的结果如下
可以看到这里包含了行号、提交人。
我们就可以对所有需要处理的文件进行分析,并组装成一个 map
, key
是行号, value
是提交人
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
不是我提交的、或者不是未提交的,那么这条打印信息我们就应该去掉。
const logLine = path.node.loc.start.line; const commiter = map[logLine]; if (commiter !== username && commiter !== "Not") { path.remove(); }
我们就可以看到,别人打印的都已经去掉了,留下来的都是我们自己打印的,整个世界清净了,舒服了。
最后
按理来说, console.log
就不应该被提交到代码仓库中。如果你的项目中有很严格的各种规范校验,那么还是很舒服的,不需要被这种琐碎的事情所打扰。
如果没有,那么就想个办法去解决他吧!
以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~