100行代码删除前端项目中的无用文件
一 背景
最近在做项目工程治理, 其中有一项是要清除项目中的无用文件, 因为项目经过发展迭代, 无数人的接手, 累积了许多用不到的文件, 这些文件平时没人会去主动删除, 久而久之累积起来, 对项目开发造成一些影响. 这次工程治理刚好集中清理一波.
二 思路分析
当着手做这件事的时候, 一开始的思路还是比较容易想到的, 主要分为三步:
- 整理出打包所依赖的文件列表
- 整理出项目中所有文件的列表
- 两者比较, 即可得出未使用文件列表
接下来是实现分析:
- 第二步可以利用
node
遍历出项目中所有的文件即可实现 - 第三步也比较简单, 操作两个数组比较
- 重要的是第二步, 如何获取依赖文件列表.
如何获取依赖文件列表:
我们知道打包是从入口文件开始一步一步分析依赖的, 如果我们要自己实现这个过程, 就需要实现模块遍历器, 而这是比较花功夫的, 由于这次的项目是基于 webpack
构建的, 而 webpack
已经实现了依赖分析, 接下来只需要知道怎么利用 webpack
去得到依赖列表即可.
同时还可以实现成一个 webpack 插件, 这样其他项目也可以使用.
接下来就是 webpack
插件的一些知识了, webpack plugin
的实现是利用 webpack
提供的钩子函数. 具体可参考 编写一个插件
在插件中, 最重要的就是 Compiler
和 Compilation
两个对象了, 我们所需要的依赖文件列表, 最终也是在 Compilation
中. 通过查看 webpack 文档, 最终找到 compilation.fileDependencies 这个对象
三 代码
定义插件基本结构和解析fileDependencies
首先我们实现一个简单的插件, 先把 fileDependencies
打印出来看一下
// unusedFilePingin.js
const fs = require('fs');
const path = require('path');
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
class UnusedFilePingin {
constructor(options = {}) {
const defaultOptions = {};
// 加载配置
this.options = {};
}
apply(compiler) {
const {} = this.options;
// 注意此处是webpack5的插件写法, 4版本请自行查阅实现
compiler.hooks.afterEmit.tap("UnusedFilePingin", (compilation) => {
compilation.fileDependencies.forEach((filepath) => {
console.log(filepath);
});
});
}
}
module.exports = UnusedFilePingin;
webpack中引入一下
// webpack.config.js
const UnusedFilePingin = require("./unusedFilePingin");
...
plugins: [
new UnusedFilePingin(),
],
...
运行 build
或 start
, 可以看到打印出来一些文件信息, 这就是webpack分析出来的路径依赖
这里稍微解释一下fileDependencies
对象
点进去fileDependencies
源码, 可以看到它是一个webpack
中的LazySet
对象, 而LazySet
中实现了遍历的方法和迭代器, 所以我们可以直接利用forEach
遍历, 这样我们就得到依赖文件列表啦!
过滤fileDependencies
和解析全部文件列表
最重要的部分已经得到, 但其实仔细观察fileDependencies
可能还是不太符合我们的需求, 需要做一定的过滤. 比如最基本的node_modules
我们肯定是不需要的. 需要排除一下.
我们把上面的打印fileDependencies
的代码改造一下, 加上去重, 打印或者输出看看
compiler.hooks.afterEmit.tap("UnusedFilePingin", (compilation) => {
// fileDependencies 利用set做一遍去重
const fileDependenciesList = [...new Set(compilation.fileDependencies)];
// 排除掉 node_module
const filterFileDependenciesList = fileDependenciesList.filter(
(file) => !file.includes(NODE_MODULES)
);
// console.log(filterFileDependenciesList);
});
对比会发现node_modules
没了, 同时如果有重复的依赖, 也会去重
接下来就是得到全部文件列表, 其实就是一个文件遍历的递归操作
// 获取指定目录下所有文件
function getAllFiles(dirPath) {
const allFiles = [];
function fn(dirPath) {
const files = fs.readdirSync(dirPath);
files.forEach((item) => {
const fp = path.resolve(dirPath, `./${item}`);
const tmp = fs.lstatSync(fp);
if (tmp.isDirectory()) {
fn(fp);
} else {
allFiles.push(fp);
}
});
}
fn(dirPath);
return allFiles;
}
也有可能我们需要排除掉某个文件目录不过滤, 所以增加exclude
, 或者过滤某种文件类型, 增加excludeSuffix
, includeSuffix
, 将上面得到的全部文件列表传入下面的filterFiles
函数得到 过滤后文件列表
function filterFiles(option) {
const {
allFiles = [],
exclude = [],
excludeSuffix = [],
includeSuffix = [],
} = option;
// 过滤 exclude
const allFileList1 = allFiles.filter((item) => {
return !exclude.find((ex) => {
const reg = new RegExp(ex);
return reg.test(item);
});
});
// 过滤 excludeSuffix
const allFileList2 = allFileList1.filter((item) => {
return excludeSuffix.indexOf(path.extname(item)) < 0;
});
// 过滤 includeSuffix
const allFileList3 =
includeSuffix.length > 0
? allFileList2.filter((item) => {
return includeSuffix.indexOf(path.extname(item)) >= 0;
})
: allFileList2;
return allFileList3;
}
对比全部文件列表
compiler.hooks.afterEmit.tap("UnusedFilePingin", (compilation) => {
// fileDependencies 利用set做一遍去重
const fileDependenciesList = [...new Set(compilation.fileDependencies)];
// 排除掉 node_module
const filterFileDependenciesList = fileDependenciesList.filter(
(file) => !file.includes(NODE_MODULES)
);
// 扫描全部文件列表
// 扫描规则, exclude, excludeSuffix, includeSuffix
const allFileList = getAllFiles(resolveApp(root));
const allFilterFiles = filterFiles({
allFiles: allFileList,
exclude,
excludeSuffix,
includeSuffix,
});
// console.log(allFilterFiles);
// 对比两个文件列表
const unUsedFiles = allFilterFiles.filter(
(item) => !filterFileDependenciesList.includes(item)
);
});
最后得到的unUsedFiles
就是未使用文件啦! 最后可以将unUsedFiles输出到json
文件中, 还可以添加删除的功能, 就不在这里展示了, 完整代码可以参考unused-file-pligun
四 总结
最后总结一下整个过程:
- 根据
compilation.fileDependencies
经过去重, 过滤node_modules
之后得到依赖文件列表 - 利用
node
扫描目录, 得到所有文件列表, 经过exclude
,excludeSuffix
,includeSuffix
过滤后得到目标文件列表 - 最终将两个列表对比, 得到的就是无用文件列表
- 最后输出无用文件列表, 删除无用文件
npm包在此: unused-file-pligun github代码在此: unused-file-pligun
至此, 这一个清理项目无用文件的webpack的插件就完成了, 瑕疵之处, 欢迎评论区交流!
参考
转载自:https://juejin.cn/post/7137639757898743821