背景
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...