关于webpack项目的基本打包体积优化
最近在搞一个项目的二次开发,但是发现页面加载速度异常的慢,所以记录一个webpack项目的基本打包优化
先看项目当前的打包分析结果
简单看来webpack 打包出的产物总大小大概为 43mb(未压缩)(参考值)
其中主要有以下
- app.js 项目主入口js文件,大约占到了总体积的三分之一,这里面问题很多
- ts.worker.js 它大概是 monaco 编辑器的语言支持文件,主要提供typescript语法的支持
- 3.js 这里面主要的内容是 antv(阿里可视化方向开源产品) 的相关文件
- 31.js 主要是dt-sql-parser 这个第三方库
- css.workar.js 同上应该是monaco编辑器的css语法支持文件
- json.worker.js 同上应该是monaco编辑器的css语法支持文件
知道了打包出的大概是什么东西,我们就应该改考虑如何优化产出物的体积
大概三个步骤
- 检查下有没有哪些产出是不必要的
- 如何合理分配优化产出
js
文件的大小,避免出现类似appjs
这样超大文件的情况 - 合并那些共有的依赖,例如a.js依赖了
lodash
,b.js依赖了lodash
。那么我们就应该酌情考虑是否将lodash
单独拆分处理
第一步
本项目是大数据方向的产品,所以需要 sql 编辑功能, monaco 没有必要对ts进行支持,因为项目中没有需要进行编辑ts的地方,而这个ts.worker文件又占用了相当大一部分空间,它必须被剔除掉,做到这一点我们需要使用 一个webpack 插件 monaco-editor-webpack-plugin
// 添加插件选项 languages 为支持的语言数组(具体语言查看官网)默认是支持所有语言的
// 配置此项应该只是去除一些语言的高级特性支持,
new MonacoWebpackPlugin({
languages:[]
})
试试再次查看分析结果
体积减少了10MB左右,效果立竿见影
第一步大概就是这样,除了ts语法支持文件还没有发现其他需要剔除的依赖
第二步
优化各个js文件大小,合理分配,这里的重点是处理appjs
可以看到app.js(入口文件)竟然达到了19MB(未压缩),从这一点足以看出这个项目在这方面的优化是一点没有做,大部分大型三方依赖都被打包进了appjs, 以及所有的svgIcon也被打包进了appjs, 项目本身的代码不算很多,这些不必要的代码占用绝大部分空间
这里我们做法是将这些三方库拆分出去作为单独的包让浏览器进行加载,使用webpack
的optimization
配置项的splitChunks
配置项
大概作用就是根据我们的配置将指定的chunks打包进单独文件
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
"monaco-editor": {
test: /[\\/]monaco-editor[\\/]/,
name: "monaco-editor",
enforce: true,
reuseExistingChunk: true
},
svgIcon: {
test: /[\\/]svgIcon[\\/]/,
name: "svgIcon",
enforce: true,
reuseExistingChunk: true
},
iview: {
test: /[\\/]iview[\\/]/,
name: "iview",
enforce: true,
reuseExistingChunk: true
},
"@antv": {
test: /[\\/]@antv[\\/]/,
name: "@antv",
enforce: true,
reuseExistingChunk: true
},
lodash: {
test: /[\\/]lodash[\\/]/,
name: "lodash",
enforce: true,
reuseExistingChunk: true
},
"dt-sql-parser": {
test: /[\\/]dt-sql-parser[\\/]/,
name: "dt-sql-parser",
enforce: true,
reuseExistingChunk: true
}
}
}
},
再来看结果
是不是他妈的整洁多了,appjs
也被缩小到了4MB左右,三方库也被单独拆分了出来,
这样也避免了 打包出的js文件包含相同第三方,避免了下面的情况
再进一步
这个项目其实是一个主子应用,一个主应用,下面有很多子应用,项目结构大概是这样的
那现在再看以下appjs
我们还是可以看到,所有的子应用都被打包进了appjs,这是不行的,我们希望的是将子应用也拆分出去,来进一步减少appjs的体积
appjs
只应该包含主应用的一些代码以及项目中通用的一些组件或代码,我们还是使用splitChunks
来实现这一点
项目中所有子应用信息是配置在一个config文件中的,那这样就简单了,我们只需要读取这个文件,然后根据json内容来生成配置即可
function formatSplitChunksSubApps(apps) {
let obj = {};
Object.entries(apps).forEach(([key, value]) => {
let routePath = value.routes.split("/");
let path = routePath.splice(0, routePath.length - 1).join("[\\\\/]");
obj[key] = {
test: new RegExp("[\\\\/]" + path + "[\\\\/]"),
name: key + "-app",
enforce: true,
reuseExistingChunk: true
};
});
return obj;
}
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
......,
...formatSplitChunksSubApps(apps)
}
}
}
这就很完美了嘛 我们的子应用也没打成了单独的包
实际效果
我们来通过 浏览器的devtool火焰图来分析下运行是性能
可以看到appjs从2.8秒左右请求完毕并开始执行
优化之后是用800毫秒左右开始执行的,效果还是很明显的
后续
这次的优化只是最基本的打包优化,而且优化的还不完善,肯定还有其他需要优化的地方没有发现(apps的执行耗时太长)
- svg图标的优化,这些svg代码都是被打包进js文件里的,有些svg文件就已经达到了1m多(svg图片非常精细,细节很多,所以导致文件体积很大)这种我们考虑使用png等格式图片代码,因为我们并不需要这么高的精细度,正常情况下不失真就可以,使用svg反而得不偿失
- 某些使用频率不高但体积很大的第三方库是否考虑自己实现或寻找相似的替代库,例如antv
- iview的按需加载,项目之前没有对ivew使用按需加载,所以这块改动量还挺大
- 冗余重复的代码等是否考虑剔除,目前来看代码中由很多重复代码
- 通用组件的按需加载,目前是统一注册全部通用组件
- 升级http协议到http2,http2的好处很多,该兴趣可以自行了解
- 子应用的按需加载,上面我们虽然吧每个子应用分离为了单独的文件,但是实际跑在浏览起的时候并没有被按需加载(项目中的关键代码导致的)
- 项目是否考虑重构为微前端的架构(较难实现,项目中的子模块之间耦合度比较高)