背景
SCRM
项目需要交接给另外一个部门。领导出于一些考虑,需要把对方只需要的功能保留,其他功能删除,然后把代码上传到新的仓库地址,再作交接。
和产品经理沟通之后,明确了以下需求:
- 以页面(功能)为单位,保留或者删除
- 个别页面需要删除一些功能
问题分析
以页面(功能)为单位,保留或者删除。也就是说,按照粒度从大到细,一个路由对应着一个页面,一个页面可能包含多个tab,一个页面有多个资源(导入进来的组件、api文件、图片、utils方法等等),而它们也分全局和局部的。
粒度越大,人工删除会比较很方便。粒度越小,如果是人工删除,需要先确认这个资源是否别其他页面引用,是的话则需要保留,否的话可以删除。
碍于人工删除细粒度的资源需要比较耗费时间和精神。希望写一个脚本来实现这个功能。想起文件依赖,就很容易联想到 webpack
原理的 构建阶段
对 module
的依赖分析。
webpack
在初始化编译环境之后:
- 内置插件
EntryPlugin
根据entry
配置找到入口main.js
文件,调用compilation.addEntry
函数触发构建流程 - 调用对应的
loaders
转译成javascript
文本 - 再通过
acorn
解析成AST
树,进行遍历AST
树,监听import
对应的钩子,得到对应的资源依赖,调用module
的addDependency
将依赖添加到当前module
的依赖列表 - 对于新增的依赖,回到第 2 步继续处理
如果我们可以拿到 webpack
帮我们做好的 依赖文件列表
,再对比 src
目录下面的文件,如果文件不在 依赖文件列表
,就收集起来,然后删除掉。
代码实现
const glob = require('glob');
const path = require('path');
const fs = require('fs')
class FileShaking {
constructor(options) {
this.options = {
excludeRegex: [
/readme\.md/i, // 不删除readme文件
/utils/ // 不删除工具方法目录下的文件
],
delete: false,
...options
};
this.fileDependencies = [];
this.srcFiles = [];
this.toDelFiles = [];
}
apply (compiler) {
compiler.hooks.afterEmit.tap("FileShaking", (compilation) => {
this.fileDependencies = Array.from(compilation.fileDependencies).filter(path => !path.includes('node_modules'));
this.deleteIndependence();
});
}
async deleteIndependence () {
this.srcFiles = await this.getSrcFiles();
this.srcFiles.forEach(filePath => {
if (!this.fileDependencies.includes(filePath) && !this.matchExclude(filePath)) {
this.toDelFiles.push(filePath)
}
})
if (this.options.delete) {
this.delFiles();
this.delEmptyDir('./src', (err) => {
if (err) {
console.log(err)
} else {
console.log('删除空文件夹DONE')
}
});
}
}
getSrcFiles () {
return new Promise((resolve, reject) => {
glob('./src/**/*', {
nodir: true
}, (err, files) => {
if (err) {
reject(err)
} else {
let out = files.map(file => {
let tmpFilePath = path.resolve(file);
return tmpFilePath.slice(0, 1).toUpperCase() + tmpFilePath.slice(1);
});
resolve(out)
}
})
})
}
matchExclude (pathname) {
let matchResult = false;
if (this.options.excludeRegex.length) {
for (let i = 0; i < this.options.excludeRegex.length; i++) {
if (matchResult = this.options.excludeRegex[i].test(pathname)) {
return matchResult
}
}
}
return matchResult;
}
delEmptyDir (dir, cb) {
fs.stat(dir, (err, stat) => {
if (err) {
cb(err)
return;
}
if (stat.isDirectory()) {
fs.readdir(dir, (err, objs) => {
objs = objs.map(item=>path.join(dir,item));
if (err) {
cb(err)
return
}
if (objs.length === 0) {
return fs.rmdir(dir, cb)
} else {
let count = 0
function done(...rest) {
count++;
if (count === objs.length) {
cb(...rest);
}
}
objs.forEach(obj => {
this.delEmptyDir(obj, done)
})
}
})
}
})
}
delFiles () {
this.toDelFiles.forEach(item => {
fs.unlink(item, (err) => {
console.log(err)
});
})
console.log('删除文件DONE')
}
}
module.exports = FileShaking;
以上代码已放到我的github: https://github.com/Rockergmai...
结果
- 在删除路由文件之后,跑一遍,得到结果:删除162个文件
剩余的需求点,人工调整即可。
Reference
https://segmentfault.com/a/11...
https://github.com/Viyozc/use...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。