同事写的console.log太多令人烦恼?来手撕一个vite插件去掉它

简介: 同事写的console.log太多令人烦恼?来手撕一个vite插件去掉它

前言

不知道各位同学在开发的过程中有没有遇到这样的一种情况:就是跟你一起合作做项目的同学调试的时候打了不少 console ,然后提交代码的时候没删。

打印得多了,可能就长成下面的样子。特别是有一些在公共的数据变化时打印,就更加惨不忍睹。

image.png

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

image.png

今天,我们就写一个 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;
},

image.png

那么可以看到,请求回来的文件已经带上了我们加的内容。

但这里有一个问题, 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;
  }
}

控制台打印一下,我们确实已经能拿到源文件的内容:

image.png

AST去console

然后,转换代码我们需要用到 AST(抽象语法树) 。可以使用 Babel 来实现,首先安装一下必要的依赖:

npm install @babel/parser @babel/traverse @babel/generator

比如我写了一个这样的代码

console.log(123)

我们可以在这个 AST工具网站 中看到它转换成 AST 之后是一个怎样的结构:

image.png

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;
      }
    },
  };
}

image.png

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

image.png

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

image.png

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 这个命令去对每一个文件进行分析,它打印的结果如下

image.png

可以看到这里包含了行号、提交人。

我们就可以对所有需要处理的文件进行分析,并组装成一个 mapkey 是行号, 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

image.png

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

image.png

const logLine = path.node.loc.start.line;
const commiter = map[logLine];
if (commiter !== username && commiter !== "Not") {
  path.remove();
}

我们就可以看到,别人打印的都已经去掉了,留下来的都是我们自己打印的,整个世界清净了,舒服了。

image.png

image.png

image.png

最后

按理来说, console.log 就不应该被提交到代码仓库中。如果你的项目中有很严格的各种规范校验,那么还是很舒服的,不需要被这种琐碎的事情所打扰。

如果没有,那么就想个办法去解决他吧!

以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
4月前
|
XML 开发框架 .NET
ASP.NET COR3.1 集成日志插件NLog
ASP.NET COR3.1 集成日志插件NLog
36 0
|
5月前
|
Kubernetes 容器
在Kubernetes(k8s)中部署Higress时,查看Wasm插件日志的方法如下
在Kubernetes(k8s)中部署Higress时,查看Wasm插件日志的方法如下
92 1
|
6月前
|
SQL IDE Java
IDEA控制台如何查看格式化的SQL(MyBatis Log插件)
IDEA控制台如何查看格式化的SQL(MyBatis Log插件)
342 0
|
7月前
|
Kubernetes 索引 容器
使用日志上下文聚合插件使能上下文查询及Livetail
本文介绍如何使用日志上下文聚合插件保持日志的上下文,以及如何在控制台查询上下文
154 0
使用日志上下文聚合插件使能上下文查询及Livetail
|
4月前
|
JSON NoSQL 网络安全
业务服务器免装插件,使用rsync+nxlog同步+采集应用日志并接入到GrayLog5.1
业务服务器免装插件,使用rsync+nxlog同步+采集应用日志并接入到GrayLog5.1
45 0
|
Java 调度 Spring
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「SpringAOP 整合篇」
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「SpringAOP 整合篇」
292 0
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「SpringAOP 整合篇」
|
存储 安全 Java
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」
549 0
【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」
|
SQL Java 数据库连接
IDEA优秀插件 之 Mybatis-Log-Plugin(上)
IDEA优秀插件 之 Mybatis-Log-Plugin
511 0
IDEA优秀插件 之 Mybatis-Log-Plugin(上)
|
监控 应用服务中间件 nginx
ELK日志分析系统&Sentil插件邮件报警
ELK日志分析系统&Sentil插件邮件报警
ELK日志分析系统&Sentil插件邮件报警
|
存储 缓存 监控
近期业务大量突增微服务性能优化总结-2.开发日志输出异常堆栈的过滤插件
近期业务大量突增微服务性能优化总结-2.开发日志输出异常堆栈的过滤插件
近期业务大量突增微服务性能优化总结-2.开发日志输出异常堆栈的过滤插件