前端项目如何识别和清理Dead Code提升打包效率
Dead Code 的识别工具,别瞎手动找
很多人一听到死代码清理就开始手动翻代码,这是最蠢的做法。我之前接手一个老项目,接手人告诉我有大量死代码,让我手动清理。折腾了两天发现根本找不完,而且还不敢删,万一哪里引用了呢?后来我直接上了工具,效果立竿见影。
我一般用两种工具:Webpack Bundle Analyzer 和 unimported。前者看打包结果,后者直接扫描整个项目。unimported 这个工具特别好用,一行命令就能扫出所有没被引用的文件:
npx unimported src/
扫出来的问题文件基本90%都是真的死代码,剩下10%可能是动态导入或者配置文件被误判。这里有个坑需要注意:如果用了动态路由 import,这些工具很容易误判。所以扫描结果出来后不能盲目删除,一定要人工确认一下。
我的写法,亲测靠谱
我处理死代码的流程是这样的:先用工具扫描,把可能的候选文件列出来,然后分批处理。比如我最近重构的一个电商项目的商品详情页,发现有好多以前的组件文件早就没人用了。
// 先用 unimported 扫描
// 找到这些文件可能有问题
// - components/OldProductCard.jsx (not imported)
// - utils/deprecated-helpers.js (not imported)
// - styles/legacy.css (not imported)
// 然后我写个小脚本批量检查
const fs = require('fs');
const path = require('path');
function checkFileUsage(filePath, searchDir) {
const content = fs.readFileSync(filePath, 'utf8');
let found = false;
// 递归搜索整个项目目录
function searchInDir(dir) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
if (fs.statSync(fullPath).isDirectory()) {
searchInDir(fullPath);
} else {
const fileContent = fs.readFileSync(fullPath, 'utf8');
if (fileContent.includes(path.basename(filePath))) {
console.log(Found reference in: ${fullPath});
found = true;
}
}
}
}
searchInDir(searchDir);
return found;
}
这种做法的好处是双重保险,工具扫描快速定位,手动检查避免误删。我一般先把疑似文件移动到专门的 dead-code-backup 目录,确认没问题后再彻底删除。这样即使删错了也能快速恢复。
这几种错误写法,别再踩坑了
最常见的错误就是看到没引用就删,我之前就踩过这个坑。有一次删除了一个看起来没人用的 CSS 文件,结果是某个第三方插件动态加载的样式,删了之后页面样式全乱了。现在我都会检查这个文件是否被动态引用:
- 动态 import:
import(${dynamicPath}) - 配置文件里的路径引用
- 某些库的约定式加载
- 构建工具的特殊处理
还有一个坑是只看当前分支,不考虑其他分支的代码。我见过有人删除了测试环境的代码,但那些代码在测试分支里是必需的。所以一定要全局搜索,不只是当前分支。
还有一种情况是条件编译,比如下面这种代码:
// 以前的老代码,现在可能永远不执行
if (process.env.NODE_ENV === 'legacy-mode') {
// 这些代码可能永远不会走
import('./legacy-logic.js');
}
这种代码看起来是死代码,但如果你不确定有没有地方设置了 legacy-mode,千万别急着删。我一般会先全局搜索这个环境变量,确认没有人设置后再删除。
React/Vue 项目特别注意
前端框架项目有特殊的死代码问题。React 里最容易出现的是未使用的组件导出,Vue 里则是未注册的组件定义。我用 ESLint 配合一些插件来检测:
{
"extends": [
"@typescript-eslint/recommended",
"plugin:react/recommended"
],
"plugins": [
"unused-imports",
"react-hooks"
],
"rules": {
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
}
}
但 ESList 也不是万能的,对于动态组件名还是检测不出来:
// 这种动态组件名 ESLint 检测不到
const ComponentName = 'OldComponent';
const Component = React.createElement(ComponentName);
所以框架相关的死代码还是要结合工具扫描来处理。
实际项目中的坑
大型项目最麻烦的是共享库的死代码判断。我负责的一个项目有十几个子应用共享一套组件库,某个组件在一个子应用里没用,不代表其他应用不用。这时候就需要跨项目关联分析,单纯的技术手段解决不了这个问题。
我当时的做法是建立一个依赖关系表,记录每个组件在哪些项目里被使用。每次删除前都要查表确认。虽然麻烦,但避免了很多问题。
还有国际化资源文件,很多翻译文本可能很久没更新了,但不代表没人用。我建议先用工具标记,然后让产品经理确认要不要删。毕竟有些功能可能下个版本才上线,现在删了后面又要重新加回来。
清理时机很重要
不要在发布前清理死代码,这是我的血泪教训。有一次我临发布前清理了代码,结果因为删除了一个被动态引用的配置文件,导致线上环境异常。现在我都在需求间隙期处理,给足够的时间验证。
另外建议写个简单的回归测试脚本,验证删除前后功能是否正常:
#!/bin/bash
# 删除前备份
cp -r src src-backup
# 执行构建
npm run build
# 运行基本测试
npm run test
# 如果有问题就恢复
if [ $? -ne 0 ]; then
echo "Build failed, restoring backup..."
rm -rf src
mv src-backup src
fi
虽然自动化程度不高,但至少有个安全网。
最后一点建议
死代码清理是个持续性工作,不要指望一次性清理完。我现在的做法是在代码审查时顺便看看是否有明显废弃的代码,发现就立即处理。这样积少成多,项目就会越来越清爽。
以上是我个人对这个 Dead Code 清理的完整讲解,有更优的实现方式欢迎评论区交流。

暂无评论