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