1.背景
一些老项目可能会遗留很多不需要的功能。如果可以知晓每个js文件的代码使用情况,就可以针对性的删除无用代码,减少项目体积&提升代码可读性。本文会简单实现一个函数使用情况插件供自己学习和大家参考~
大致的效果如下(红色字体即为未使用的函数):

简单点说,只要每次执行react组件
、js函数
时进行埋点,将代码块的行数上传,之后就可以汇总并显示函数使用情况了。只不过人为埋点很麻烦,因此这一步可以通过babel插件代替。
3.Coding
3.1 项目初始化
最近rsbuild貌似有点🔥,本着学习的态度,我就选择它来初始化项目。
主要的目录结构如下
主要目录结构
rsbuild-playground/
├── plugins/
│ ├── codeCoverage.js # 代码覆盖率插件
│ └── inject.js # 需要注入的js脚本
├── src/
│ ├── tool.ts # 测试函数
│ ├── index.tsx # 入口文件
│ └── App.tsx # 根组件
└── rsbuild.config.js # rsbuild 配置文件
tool.ts中简单写三个函数用于测试代码覆盖率
之后,在App.tsx中引用前两个函数

3.2 插件初始化
首先我们先在codeCoverage.js
里打印几个console.log
看看,不熟悉babel插件的小伙伴可以先去学习下编写插件的格式
之后,去rsbuild.config.ts配置文件引入插件

执行一下打包命令npm run build
可以看到目前插件已经把项目里所有的函数代码块范围都打印出来了,分别是
App.tsx
里的<App />
和useEffect
以及tool.ts
里的三个测试函数
3.3 自动埋点
通过上一步,我们已经可以获取到函数的基本信息了,接下来则可以通过埋点对函数的执行次数进行简单的计数,就可以知道这函数到底还有没有在使用 & 可视化出来。
3.3.1 埋点函数 inject.js
埋点的函数就是项目目录下的inject.js
,它的简易代码如下:
直接把
inject
函数添加到window
上,这样在其他函数内部就可以直接调用了。当然,你也可以把inject
函数export
出去,之后在每个的js文件顶部通过babel
插入import inject
函数的代码。我这就简单的插入到window
上了。
3.3.2 引入 inject.js
由于inject.js
目前是在/plugin
目录下的,在rsbuild
打包时没法直接引入,因此我们需要在打包时把inject.js
复制到/src
目录下,并且在/src/index.tsx
中import
。
这部分操作可由codeCoverage.js
插件实现,修改插件的Program
方法如下:

这里通过babel
提供的importDeclaration
创建了一个import
语法,同时用unshift
方法将这个语法插入到了/src/index.tsx
文件的顶部。
这段语法其实就等价于:
arduino
import '【绝对路径】/src/inject.js'
// 如果你的 inject.js 是 export const inject = 这种形式
// 这里就需要把import语句改写外 import { inject } from '...'
// 而直接 window.inject = xxx 的话就相对简单
最后,还把buildInfo
插入到了state
对象上,这样在函数访问入口FunctionDeclaration
和ArrowFunctionExpression
的state
参数上就可以获得当前文件的信息了。
无论是箭头函数还是普通函数,插入inject
的代码都差不多,这里我就展示一下普通函数的例子,修改FunctionDeclaration
如下:

这里解释下什么叫函数比所在文件更先访问:
假设此时babel
正在解析App.tsx
文件中的useEffect
函数,而该函数内部调用了test2
。在这种情况下,test2
的FunctionDeclaration
会被触发。但是,由于此时tool.ts
的Program
入口函数尚未触发,因此test2
的FunctionDeclaration
的state
参数中还未插入buildInfo
信息。
所以,遇到上面这情况需要return
。等babel
解析到tool.ts
的时候在插入inject
函数即可。
整个FunctionDeclaration
的作用就等价于:
php
export function test2() {
// 给每个函数的顶部添加 inject()
inject({ position: xxx, filePath: xxx, cacheKey: xxx, ... })
// 函数原本的代码
console.log('test2')
}
ArrowFunctionExpression
也是类似的,可以直接复制上面的代码。
3.3.3 测试一下
执行npm run build
打包出dist
文件看看效果
可以看到每个函数(包含
react组件
)执行的时候都触发了插入的inject
方法,同时tool.ts
里的test3
函数由于没被调用,因此没有触发相应的inject
方法。
至此,查看JS函数的使用情况已经完成了一大半了,剩下的就是完善inject
函数的fetch
方法,并将test3
函数未被使用的情况可视化。
3.4 可视化
其实只要有了埋点数据后怎么可视化都行,无论是用富文本还是图片之类的。主要是找出对应js文件中,没有埋点信息的函数的行数,并高亮显示。
以tool.ts
为例,大致的流程为:
- 用
babel
插件为tool.ts
还没有id的函数生成id。在3.3.2里我们用getCacheKey
为被调用过的函数生成了id,但像test3
这种函数根本没引用过。所以并不会执行FunctionDeclaration
。因此需要用getCacheKey
生成一下。 - 当每个函数都有了id后就可以依次比对数据库的埋点信息了。如果没有埋点信息就说明这些函数最近一段时间都没触发过,这时可以记录下这些函数的行索引。
- 有了未执行函数的行索引后,就想怎么可视化就怎么可视化了。
这里作者懒得再搞后端和数据库了,直接用nodejs代码简单代替一下思路吧:
该代码最终的效果就如开头的背景所示:

最后
感谢观看~,本文的实现其实比较粗糙(还是太懒了=,=),主要还是记录下自己感兴趣的一些前端技术供大伙简单参考。