背景
2019年的时候,项目这套前端框架,build常常达到900s以上,开发环境修改代码重新编译也需要10-20s左右,严重影响开发效率,优化刻不容缓。
环境
硬件:I5-6200U 4核 @2.3GHz, 8G内存,固态硬盘。
软件:win10,node v8.12.0, webpack v3.12.0,npm v6.2.0。
前端框架:dva + antd, dva v1.2.1,antd v2.13.13,roadhog v1.3.4。
现状
涉及模块108个,首次构建:87.1s,再次构建:43.3s,打包:760.8s,打包文件大小:90.7M。
改进过程
升级相关构建工具,比如node、webpack,或者roadhog和dva都会多少提升一部分速度,但是盲目升级后续导致的依赖错误排查,甚至相关组件功能不可用是非常令人头疼的,因此当系统已经稳定,并且各业务模块比较繁多的情况下,我们并没有考虑这种方案。
去掉多余模块和无效引用
当前项目是另一个老旧项目改造而来,功能模块上一开始有80%的相似,因此项目初期直接把老项目的前端框架拿来用了,因此存在着很多老项目的模块在当前项目下实际上是无用的;另外代码中充斥着大量无效引用,如下
import xx from 'xxx';
事实上业务实现并没有用到XX,必须及时删除已经停止使用的相关库,从源码中移除相关依赖。
通过去掉多余模块和无效引用,打包速度快了100多秒,上面的现状其实已经是经过该部分优化工作后的结果。
但是随着后续业务拓展和迭代,模块还是会越来越多,这种方式的优化还是比较局限。
webpack-visualizer-plugin分析
非常好用的插件,该插件可以让你清楚的看到代码的组成部分,细化到每个文件中,每个模块的百分比,以及在项目中可能存在的多版本引用的问题。
该插件roadhog已内置,只要在webpack中加入以下代码,构建的时候会生成一个stats.html文件。
const Visualizer = require('webpack-visualizer-plugin');
webpackConfig.plugins.push(
new Visualizer()
);
stats.html内容如下:
发现排第一的echarts占用了2M多,排第二的tf-ag-grid((公司内部使用的封装的一个表格组件))也占用了1.4M,其他的就是css-loader,lodash,moment,antd。
externals
简单来说 external 就是把我们的依赖申明为一个外部依赖,外部依赖通过
针对打包大小占第一的echarts,我们只是在首页用到了,完全可以从项目中剥离出来,采用外部引入的方式。
在 webpack.config.js 中,加入如下代码:
webpackConfig.externals = {
'echarts': 'echarts'
};
这样我们代码中import echarts from 'echarts'这种情况,echarts实际上是从window全局变量上拿,构建的时候也不会打包进去。当然对应的entry文件就要通过script方式引入。
打包:711.6s,打包文件大小:90M。变化不大。
DllPlugin和DllReferencePlugin
针对lodash和moment这种公共库,同样也可以用external的方式处理,但是我们项目中试了下,在stats.html中仍然赫然在列。经过分析以moment为例,是因为antd中又依赖了moment,导致构建的时候仍然包含进去了。这个时候就可以用DllPlugin的方式来处理。
用过 Windows 系统的人应该会经常看到以 .dll 为后缀的文件,这些文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据。我们把这种思路用到web构建中,为什么会大大提升构建速度呢? 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。 由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。
Webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:
- DllPlugin 插件:用于打包出一个个单独的动态链接库文件。
- DllReferencePlugin 插件:用于在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件。
这块内容的配置可以参考这里,配置也不算繁琐。简单来说 DllPlugin 的作用是预先编译一些模块,而 DllReferencePlugin 则是把这些预先编译好的模块引用起来。这边需要注意的是 DllPlugin 必须要在 DllReferencePlugin 执行前,执行过一次。
所幸的是低版本的roadhog已经支持了DllPlugin的模式,不过仅仅是对开发环境的支持,正式环境的打包仍然不支持。这边的配置主要参考了这个issue。
- 默认关闭
- 通过 .roadhogrc 的 dllPlugin 配置开启,可以配 exclude 和 include 的包
- 提供 roadhog buildDll 命令手动打 dll bundle
- 开启 dllPlugin 后,roadhog server 时检到没有 manifest.json,提醒执行 roadhog buildDll
- 然后用户还需手动在调试的 html 里引入 roadhog.dll.js
我试了一下,第2条里的exclude和include设置了并没有生效,要开启DllPlugin,只需在.roadhogrc.js里面增加dllPlugin: true即可;同时还得先运行roadhog buildDll脚本预先生成 roadhog.dll.js。
因此在package.json里script下添加如下命令:
"scripts": {
"dll": "roadhog buildDll"
}
执行npm run dll,生成roadhog.dll.js。
我们可以在node_modules\roadhog-dlls中看到roadhog.dll.js和roadhog.json,roadhog.json描述了动态链接库文件中包含哪些模块,其中就包含了lodash和moment。
并在entry文件index.ejs中引入,初次构建 62.3s,再次构建 27.5s。用webpack-visualizer-plugin分析了下,lodash和moment已经不参与构建了,但是antd仍然会参与构建,这点roadhog的作者也说了。dll的方式并不会影响生产打包,因此这种方式我们只针对开发环境,速度提升也是非常明显。对于生产打包,我们对几个打包大头文件采用commonChunkPlugin的方式。
commonChunkPlugin
看一下目前的打包文件大小,打包后js文件110个,将近 90M,有49个js文件达到1M以上。
我们通过stats.html分析看到,体积过大主要是重复打包了tf-ag-grid、moment、antd,因此我们的思路是通过CommonsChunkPlugin 将公用模块抽离到 vendor.js。我们在webpack.config.js中针对线上环境加入以下代码(开发环境已经用DllPlugin优化):
if (env === 'production') {
webpackConfig.entry = {
index: './src/index.js',
vendor: [
'moment',
'lodash',
'react',
'react-dom',
'tf-ag-grid'
]
}
webpackConfig.plugins.push(
// 抽取出通用的部分
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'],
filename: '[name].[hash].js',
minChunks: Infinity,
})
)
}
打包测试结果如下:
tf-ag-grid不参与公共提取打包:打包时间 656.4s,打包文件大小 66.6M,大部分文件体积瘦身 300-400KB;
tf-ag-grid参与公共提取打包:打包时间 355.0s,打包文件大小 34.0M,部分文件体积瘦身达1M以上。
可以看到tf-ag-grid参与公共打包后,打包效率有了巨大的飞跃,结果令人满意。当然如果把项目中antd里面使用频率较高的模块如果也单独抽离打包到antd.js中的话,效果肯定会更好,这里就不做展开,留待后续优化,可以参考这个issue 解决 roadhog 单页应用提取公共模块的问题。
测试结果
优化动作 | 初次构建时间(s) | 再次构建时间(s) | 打包时间(s) | 打包文件大小(M) | 未优化 | 87.1 | 43.3 | 760.8 | 90.7 | externals | 85.3 | 39.6 | 711.6 | 90 | DllPlugin | 62.3 | 27.5 | 720.1 | 90 | commonChunkPlugin(exclude tf-ag-grid) | 60.5 | 28.7 | 656.4 | 66.6 | commonChunkPlugin(include tf-ag-grid) | 62.8 | 29.6 | 355.0 | 34.0 |
---|
最终方案
- echarts配置externals,通过script引入地址;
- 开发环境配置DllPlugin,预编译常用模块;
- 线上环境通过commonChunkPlugin抽离公共模块,提升打包速度。
后续研究
- 此次打包速度研究,还尝试过happyPack、PrefetchPlugin、webpack-uglify-parallel,但是要么受限于roadhog,要么优化速度不明显,不知道是配置的问题还是其他,作为后续研究;
- 对于antd,后续可以用commonChunkPlugin抽离高频模块,继续提高打包速度。
最后
其实还有一个拖慢打包速度的元凶,这个幕后黑手不仅拖慢打包速度,还影响svn更新、npm install甚至文件复制速度,就是IT给我们安装的McAfee。当然这个我们也没有办法,毕竟公司安全需要。最终我们还是要就事论事,从我们的框架层面去优化。
参考
- [1] Boost webpack build performance | Optimising webpack build performance | Webpack 构建性能优化探索
- [2] roadhog 构建优化 - 狂流 - 博客园
- [3] 考虑集成 dll 功能用以提升调试效率 · Issue #2 · sorrycc/roadhog · GitHub
- [4] 4-2 使用 DllPlugin · 深入浅出 Webpack
- [5] 解决 roadhog 单页应用提取公共模块的问题
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!