现在做的产品是历经两个产品线的合并,n代开发经手的代码,项目文件很多,找起来很不方便,而且刚接手不熟悉逻辑的时候,上来一搜很多重复代码,向上查找引用发现根本没有用到这个文件。。。。所以就打算做个一键删除废弃代码的工具。。。
原理就是依赖于webpack的stats.json文件
stats.json
通过命令将依赖关系输出到stats.json中
webpack --env staging --config webpack.config.js --json > stats.json
复制代码
来看一下输出到stats.json的内容,主要用到的分为三个模块assets,chunks,modules
asset
可以看到asset里是打出来的静态资源,只包含输出的文件名称,而像图片这些路径都是../
, 有的图片甚至是../images
,这是由于webpack的配置问题,将静态资源打到了我们的配置文件/assets/images
文件夹中
modules
chunks
可以看圈出来的几块位置,chunks和module是有关联关系的。而且modules里的name是带有代码路径的
代码实现
本着宁愿不删,也不错删的原则下,有了下面版本的代码
bin目录下的入口文件,拿到用户传入的参数
#!/usr/bin/env node
const program = require('commander');
const UnusedFinder=require('../lib/UnusedFinder');
program.option('-f, --file','生成unused文件名')
program.option('-r, --remove','删除无用的文件');
program.parse(process.argv);
const args = process.argv.slice(2);
let fileName= '';
let remove = false
let arg;
while(args.length){
arg=args.shift();
switch(arg){
case '-f':
case '--file':
fileName=args.shift()|| '';
break;
case '-r':
case '--remove':
remove=true;
break;
}
}
const finder = new UnusedFinder();
finder.start({fileName, remove});
复制代码
unusedFinder实现
// 删除文件的时候,可以自定义忽略删除的目录,毕竟有些公共的hooks啥的现在没有引用可能也不想删除
function ask() {
return inquirer.prompt([{
type: "input",
name: "ignore",
message: "请输入忽略删除的文件路径,以逗号分割"
}])
.then(asw => asw);
}
class UnusedFinder {
constructor(config = {}) {
this.statPath = path.join(process.cwd(), './stats.json');
this.usedFile = new Set();
this.assetsFile = new Set() // 存放静态资源
this.allFiles = [];
this.unUsedFile=[];
// 默认删src文件夹底下的,也可以自己配置
this.pattern = config.pattern || './src/**';
}
hasStats = () => {
if (!existsSync(this.statPath)) {
throw new Error("请检查在项目根目录执行,并生成stats.json文件");
}
}
removeFiles = () => {
ask().then(answer=> {
let ignoreFile = []
const {ignore} = answer
if(ignore){
ignoreFile = ignore.split(',').filter(Boolean)
}
const task = []
spinner.start('文件删除中......');
this.unUsedFile.forEach(item => {
// 根目录下的不删除
if(item.split('/').length <= 2) return
// style里import的style没有在stats.json里体现,所以暂时不删除
if(/^\.\/styles/.test(item)) return;
// md文档不删除
if(item.substr(item.length - 3, 3) === '.md') return
// 自定义忽略删除的文件夹
if(ignoreFile.some(fileName => item.includes(`/${fileName}`))) return
const url = item.replace('./', './src/')
if(!existsSync(url)) return spinner.warn(`${url}文件不存在`)
const promise = rm(url, val=>val)
task.push(promise)
})
Promise.all(task)
.then(() => {
spinner.succeed('文件删除成功')
}).catch((err)=>{
spinner.fail('存在文件删除失败')
err.forEach(item => {
error(item)
})
})
})
}
findUsedModule = () => {
// 格式化stats.json文件
const statsData = JSON.parse(readFileSync(this.statPath));
const chunks = statsData.chunks;
const modules=statsData.modules;
const assets=statsData.assets;
chunks.forEach(chunk => {
chunk.modules.forEach(value => {
// name会有 + 1 modules的情况,为了避免取错,还是加下替换
// ../node_modules/@antv/g-canvas/esm/canvas.js + 1 modules
const name = value.name.replace(/ \+ [0-9]* modules/g, '').replace(/\?.+=./,'');
// node_modules没必要放入
if (name.indexOf("node_modules") === -1) {
this.usedFile.add(name);
}
value.modules && value.modules.forEach(subModule => {
if (subModule) {
const name = subModule.name.replace(/ \+ [0-9]* modules/g, '').replace(/\?.+=./,'');
if (name.indexOf("node_modules") === -1) {
this.usedFile.add(name);
}
}
})
})
})
// 生成的模块
modules.forEach(value=>{
const name = value.name.replace(/ \+ [0-9]* modules/g, '').replace(/\?.+=.+/,'');
if (name.indexOf("node_modules") === -1) {
this.usedFile.add(name);
}
});
// 生成的静态资源
assets.forEach(value=>{
const name = value.name.split('/')
// 因为静态资源是按照打包完后的dist文件的路径做的展示,所以我们只匹配文件名称
if (name.indexOf("node_modules") === -1) {
this.assetsFile.add(name[name.length - 1]);
}
});
}
findAllFiles = () => {
const files=glob.sync(this.pattern, {
nodir: true
});
this.allFiles = files.map(item => {
return item.replace('./src', '.');
})
}
findUnusedFile=()=>{
this.unUsedFile=this.allFiles.filter(item=>!this.usedFile.has(item));
this.unUsedFile = this.unUsedFile.filter(item => {
const name = item.split('/')
// 如果包含静态资源的名称,则去掉文件,当然也会存在重名的情况
if(this.assetsFile.has(name[name.length - 1])){
return false
}
return true
})
}
start = ({fileName, remove}) => {
this.hasStats();
this.findUsedModule();
this.findAllFiles();
this.findUnusedFile();
if(fileName){
writeFileSync(fileName,JSON.stringify(this.unUsedFile))
}else{
warn(`未被使用的文件:\n${this.unUsedFile.join('\n')}`)
}
if(remove){
this.removeFiles()
}
}
}
module.exports=UnusedFinder;
复制代码
在现阶段的工具下,一键删除了将近500个无用的业务文件。当然先阶段的代码还是存在一定的问题,像静态资源,样式文件等都还没有找到好的方法去做匹配,只能通过文件名称去做模糊匹配,这样就可能存在有的无用同名文件被过滤而没有被删除,如果有更好的办法,希望大家可以在评论区交流一下。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END