背景
在项目开发中,经常会遇到通过搜索关键词字快速定位对应代码文件,结果往往会出现多个对应文件,其中就充斥着部分废弃无依赖的文件影响开发者判断,降低了开发效率。
人为在上千个依赖文件中找出这些废弃代码文件是不现实的,这时我们就可以借助工具自动化处理。
先睹为快
使用node.js、webpack能力统计文件并自动删除无依赖文件,同时生成统计结果文件。
设计思路
所有文件:开发者在src目录下创建的文件
依赖文件:项目打包后实际用到的文件
无用文件 = 所有文件 - 依赖文件
如何获取所有文件
我们可以使用node.js的能力,通过node递归读取项目入口目录下所有的文件。
如何获取依赖文件
webpack插件允许用户直接介入编译过程,只需开发一个自定义插件,在合适的编译阶段执行就可以了。
在webpack编译完成后,即将退出的时候通过compiler的done钩子拿到最终打包文件的信息。
如何获取无用文件
所有文件减去依赖文件就拿到了无用文件并生成一个统计结果文件。
具体代码实现
dependencyAnalysisPlugin.js
const fs = require('fs');
const path = require('path');
class DependencyAnalysisPlugin {
constructor(options = {}) {
this.options = options;
this.entry = options.entry || 'src'; // 入口
this.include = options.include || ''; // 包含哪些文件'.vue|.js'
this.exclude = options.exclude || ''; // 排除哪些文件夹 ['src/assets', 'src/views']
this.isDelete = options.isDelete || false; // 是否主动删除文件
this.originFile = []; // node读取的源文件目录 处理过include及exclude 后的数据 最全的数据
this.dependenciesFile = []; // webpack依赖数据 处理过include及exclude 后的数据 依赖数据
this.noUseFile = []; // 最全的数据 减去 依赖数据 可删除的数据
this.init(); // 初始化
}
apply(compiler) {
compiler.hooks.done.tapAsync('DependencyAnalysisPlugin', (factory, cb) => {
// 获取依赖资产
let curFile = [];
factory.compilation.fileDependencies.forEach((item) => {
curFile.push(item);
});
// 排除node_modules 与 确认入口文件
curFile = curFile.filter((item) => {
if (item.indexOf('node_modules') == -1 && item.indexOf(this.resolve(this.entry)) > -1) {
return item;
}
});
// 处理include规则
const includeFile = this.includeHandle(curFile);
// 处理exclude规则
const excludeFile = this.excludeHandle(includeFile);
this.dependenciesFile = excludeFile;
// 从 originFile 及 dependenciesFile 数据中分析出 未被使用的数据
this.originFile.forEach((item) => {
if (this.dependenciesFile.findIndex((el) => el == item) == -1) {
this.noUseFile.push(item);
}
});
// 处理资源 写入文件
this.writeDirPathHandle();
cb();
});
}
// 初始化
init() {
console.log('[dependency] ##启动依赖分析功能');
console.log('[dependency] ##是否自动删除文件', this.isDelete);
// 读取指定node文件
this.readOriginFile();
}
// 转换路径
resolve(pathname = '') {
return path.join(path.resolve('./'), pathname);
}
// 读取源文件目录
readOriginFile() {
const files = this.readFiles(this.entry);
// 处理include规则
const includeFile = this.includeHandle(files);
// 处理exclude规则
const excludeFile = this.excludeHandle(includeFile);
this.originFile = excludeFile;
}
// 读取指定目录文件
readFiles(path) {
let allFile = [];
const curPath = this.resolve(path);
const files = fs.readdirSync(curPath);
for (const file of files) {
const obj = fs.statSync(this.resolve(`${path}/${file}`));
if (obj.isDirectory()) {
allFile = [...allFile, ...this.readFiles(`${path}/${file}`)];
} else {
// 排除 .gitkeep 等隐藏文件
const isHideFile = new RegExp(/^./).test(file);
// 排除 md文件
const isMdFile = new RegExp(/.md$/).test(file);
if (!isHideFile && !isMdFile) {
allFile.push(this.resolve(`${path}/${file}`));
}
}
}
return allFile;
}
// 处理规则
includeHandle(list) {
if (!this.include) {
return list;
}
// 指定类型的文件
const includeArr = this.include.split('|');
const filterFile = list.filter((item) => {
const index = includeArr.findIndex((el) => item.indexOf(el) > -1);
if (index > -1) {
return item;
}
});
return filterFile;
}
// 处理规则
excludeHandle(list) {
if (!this.exclude) {
return list;
}
// 过滤指定文件夹
const excludeList = [];
this.exclude.forEach((item) => {
excludeList.push(this.resolve(item));
});
const filterFile = list.filter((item) => {
const index = excludeList.findIndex((el) => item.indexOf(el) > -1);
if (index == -1) {
return item;
}
});
return filterFile;
}
// 写入文件
writeDirPathHandle() {
let content = `所有文件-length[${this.originFile.length}]、依赖文件-length[${this.dependenciesFile.length}]、无用文件-length[${this.noUseFile.length}]`;
content += `\r\n##########所有文件-length[${this.originFile.length}]##########\r\n${this.originFile.join('\n')}\r\n`;
content += `\r\n##########依赖文件-length[${this.dependenciesFile.length}]##########\r\n${this.dependenciesFile.join('\n')}\r\n`;
content += `\r\n##########无用文件-length[${this.noUseFile.length}]##########\r\n${this.noUseFile.join('\n')}\r\n`;
fs.writeFile('dependency.txt', content, (err) => {
if (err) {
console.error(err);
return;
}
console.log('[dependency] ## 文件已写入dependency.txt');
// 判断是否执行删除
if (this.isDelete) {
this.deleteFileHandle();
}
});
}
// 删除文件
deleteFileHandle() {
this.noUseFile.forEach((item) => {
fs.unlink(item, (err) => {
if (err) throw err;
console.log(`[dependency] ## 已删除文件:${item}`);
});
});
}
}
module.exports = DependencyAnalysisPlugin;
使用步骤
1.项目中新建自定义插件 dependencyAnalysisPlugin.js 文件
2.在webpack配置文件中引入文件 dependencyAnalysisPlugin.js 并配置参数
3.执行代码构建即可
const dependencyAnalysisPlugin = require('../dependencyAnalysisPlugin')
plugins:[
new dependencyAnalysisPlugin() //isDelete 可配置是否主动删除文件
]
项目构建完成后会生成 dependency.txt 文件,里面记录的文件使用情况,也可通过配置isDelete参数实现自动删除无依赖文件。
Api
| 属性 | 说明 | 类型 | 可选值 | 默认值 | ||
|---|---|---|---|---|---|---|
| entry | 入口 | String | 'src' | |||
| include | 包含哪些文件类型 | String | '.js | .vue | .jpg' | |
| exclude | 排除哪些文件夹 | Array | 例:['src/assets', 'src/views'] | |||
| isDelete | 是否主动删除无依赖文件 | boolean | true false | false |