本文深入探讨了基于vue-cli的webpack打包优化实践。通过speed-measure-webpack-plugin和webpack-bundle-analyzer进行测速与体积分析,尝试了缩小文件查找范围、使用HappyPack多线程打包以及DllPlugin提取第三方库等策略。其中,HappyPack因与vue-loader不兼容导致失败,而DllPlugin通过分离大型第三方库显著将打包时间从30秒降至20秒左右,为前端构建优化提供了具体参考。
时间进入2019年,短短三四年间,webpack打包工具已在前端开发中成为必备技能。过去面试常问页面优化手段,如缓存、文件压缩、CSS雪碧图及CDN部署等,如今问题更多转向webpack的打包原理与优化方法。笔者出于兴趣研究webpack打包优化,虽市面上已有类似文章,但仍希望提供新的视角与启发。
准备工作:测速与分析bundle
1、准备工作:测速与分析bundle
优化webpack打包前,需先对bundle文件进行分析,明确各模块大小及打包耗时环节。主要依赖两个webpack插件:speed-measure-webpack-plugin用于测速,webpack-bundle-analyzer用于分析bundle文件内容。
具体配置
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const smp = new SpeedMeasurePlugin({
outputFormat:"human",
});
module.exports = {
configureWebpack: smp.wrap({
plugins: [
new webpack.ProvidePlugin({
$: "zepto",
Zepto: "zepto",
}),
new BundleAnalyzerPlugin(),
],
optimization: {
splitChunks: {
cacheGroups: {
echarts: {
name: "chunk-echarts",
test: /[\\/]node_modules[\\/]echarts[\\/]/,
chunks: "all",
priority: 10,
reuseExistingChunk: true,
enforce: true,
},
demo: {
name: "chunk-demo",
test: /[\\/]src[\\/]views[\\/]demo[\\/]/,
chunks: "all",
priority: 20,
reuseExistingChunk: true,
enforce: true,
},
page: {
name: "chunk-page",
test: /[\\/]src[\\/]/,
chunks: "all",
priority: 10,
reuseExistingChunk: true,
enforce: true,
},
vendors: {
name: "chunk-vendors",
test: /[\\/]node_modules[\\/]/,
chunks: "all",
priority: 5,
reuseExistingChunk: true,
enforce: true,
},
},
},
},
})
}
基于vue-cli脚手架的项目已内置部分优化,如配置splitchunk进行代码分割。这在大型项目中至关重要,避免用户加载过大的JS文件(如5MB)导致阻塞,因此代码分割和懒加载是必要的。
在配置中,需用smp对配置进行包裹,因为SpeedMeasurePlugin会包裹其他Plugin对象以记录其开始和结束时间。
其次,BundleAnalyzerPlugin作为普通plugin,加载在plugins数组的后面即可。
接下来我们看一下最初的打包时间以及包内容分析:


从分析结果可见,项目中体积较大的三个包中,有两个属于第三方依赖,如three.js、lottie、lodash、echarts等。
开始逐步优化
2、开始逐步优化
2.1缩小文件查找和处理范围
这是webpack优化的常规操作,旨在优化模块和文件查找,减少loader对不必要模块的处理。vue-cli未暴露内置loader供直接优化,但其配置项已对loader查找路径进行了优化。若项目使用vue-cli,可通过命令行查看现有配置文件:
npx vue-cli-service inspect > output.js
具体可查阅vuecli官方文档。
resolve:{
modules: [path.resolve(__dirname, 'node_modules')],
alias:{
'three':path.resolve(__dirname, './node_modules/three/build/three.min.js'),
'zepto$':path.resolve(__dirname, './node_modules/zepto/dist/zepto.min.js'),
'swiper$':path.resolve(__dirname, './node_modules/swiper/dist/js/swiper.min.js'),
'lottie-web$':path.resolve(__dirname, './node_modules/lottie-web/build/player/lottie.min.js'),
'lodash$':path.resolve(__dirname, './node_modules/lodash/lodash.min.js'),
}
},
module:{
noParse:/^(vue|vue-router|vuex|vuex-router-sync|three|zepto|swiper|lottie-web|lodash)$/
},
- 通过modules指定查找第三方模块的路径。
- 通过alias指定直接查找到打包构建好的压缩js文件。
- 通过module指定noparse,对第三方模块不再进行依赖分析。
优化效果:2s?

结果显示打包时间减少了两三秒,波动在30s左右,感觉差异不大。
2.2尝试使用happypack
在优化前查阅了大量webpack优化文章,笔者尝试使用happypack优化打包时间。关于happypack的效果主要有两种说法:
1、webpack4中已默认支持多线程打包,因此happypack效果不明显;
2、vue不支持happypack打包,需设置thread-loader。
尽管如此,笔者仍决定尝试,至少只对JS和CSS文件设置happypack。
然而问题随之而来,vue-cli内置封装了loader,如何获取其配置并改写其中的loader配置成为难题。
通过查阅vue-cli官方文档,可以看到以下使用介绍:
configureWebpack Type: Object | Function 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中。 如果这个值是一个函数,则会接收被解析的配置作为参数。该函数及可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。
为此,笔者特地调试进了vue-cli的源码一探究竟:
流程介绍:
执行命令行vue-cli-service build时,系统先去node_modules的.bin文件夹下查找相应的可执行文件,.bin下的vue-cli-service会映射到相应的第三方库内的执行文件。
因此我们可以找到这个可执行文件的地址:
/node_modules/@vue/cli-service/bin/vue-cli-service.js
找到入口后,若想进入nodejs调试,以往通过node --inspect app.js启动后台服务,然后在谷歌浏览器里进入调试界面(F12选择绿色的那个小按钮)。
但此处遇到难题,因为打包构建是一次性执行,不同于实时监听的后台服务。查阅后发现,调试普通nodejs文件需通过以下方式:
node --inspect-brk=9229 app.js
因此,为了强行进入vue-cli源码调试,查看其处理流程,需输入以下命令行:
node --inspect-brk=9229 node_modules/@vue/cli-service/bin/vue-cli-service.js build
上述命令行等价于vue-cli-service build。
通过这种方式,终于进入了vue-cli源码,查看其执行流程,可在对应位置打下断点,查看此时作用域内的变量数据。

可以看到vue-cli源码中的这一段操作,会执行传入的函数,根据函数是否有返回值决定是否merge进其内部配置的config。
通过这段代码可以看出,若将configWepack配置为函数,通过参数形式获取config配置项(本身是对象,保留引用),直接修改传入的config对象即可实现目标:修改vue-cli内置的loader!
当然,除了断点查看配置,也可通过命令行输出为output文件查看现有配置。
这里给大家截图看一下vue-cli内部的配置:

可能有些啰嗦,但通过断点方式可以看到,vue-cli其实已对js文件设置了exclude,同时也配置好了cache-loader,意味着webpack常规优化方式之一的cache-loader缓存也已被处理。
回到最初目标,处理JS和CSS的loader,模仿多数配置进行了以下修改:
configureWebpack:(config)=>{
console.log("webpack config start");
let originCssRuleLoader = config.module.rules[6].oneOf[0].use;
let newCssRuleLoader = 'happypack/loader?id=css';
config.module.rules[6].oneOf[0].use = newCssRuleLoader
config.module.rules[6].oneOf[1].use = newCssRuleLoader
config.module.rules[6].oneOf[2].use = newCssRuleLoader
config.module.rules[6].oneOf[3].use = newCssRuleLoader
...//other code
}
尝试对css的loader配置进行修改。之后对plugins进行一下配置:
plugins: [
new HappyPack({
id: 'css',
threads: 4,
loaders: originCssRuleLoader
}),
],
本以为这样就OK了,但很遗憾地告诉大家,报错了...

可以看到报错内容,是在处理vue文件时出了错误。
如何解决
笔者通过百度和谷歌搜索,大抵说法是happypack不支持vue-loader。同时根据报错查询处理方案,设置parallel参数也无效。
笔者甚至怀疑配置有误,于是将配置原样移植到另一个非vue项目中,一切运行正常。 答案:此题无解~
原因分析:
由于vue文件中含有CSS,vue-loader会提取其中的css交给其他loader处理,vue-loader-plugin通过在vue文件后加查询字符串告知其他loader对该文件进行处理。这意味着vue-loader处理文件时通知其他loader,但此时loader配置已被改写为happypack,而vue与happypack不兼容,最终导致报错。很遗憾地告诉大家,vue-cli接入happypack--失败。
(注:这一部分主要是笔者在webpack优化过程中的探索,虽然最终未能很好地优化webpack打包,但在此探索过程中也能学到很多~包括 vue-cli对配置对象的处理?如何调试普通文件nodejs代码?vue-loader中对vue文件的处理流程?vue-loader-plugin帮我们做了什么事?而这些都是要自己慢慢翻阅,慢慢踩坑去了解的~)
使用dllplugin
2.3使用dllplugin
与大多数webpack优化教程一样,笔者也尝试利用dllplugin进行优化。该插件本质是提取常用的第三方模块,单独打成一个文件包,之后插入到html页面中。这样每次打包无需再处理第三方模块,毕竟第三方模块动辄成千上万行。
流程介绍:
1、配置webpack.dll.js针对第三方库打包
2、vue.config.js中配置plugin
3、html中引入dll打包出来的js文件。(一般采用部署CDN的方式)
由于项目中有很多大型第三方库,如three、echart等,因此进行了以下配置:(webpack.dll.js)
const webpack = require("webpack")
const path = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
vuebundle: [
'vue',
'vue-router',
'vuex',
],
utils:[
'lodash',
'swiper',
'lottie-web',
'three',
],
echarts:[
'echarts/lib/echarts',
"echarts/lib/chart/bar",
"echarts/lib/chart/line",
"echarts/lib/component/tooltip",
"echarts/lib/component/title",
"echarts/lib/component/legend",
]
},
output: {
path: path.resolve(__dirname, './static/'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'build', '[name]-manifest.json'),
name: '[name]_library'
})
]
}
针对不同库的大小进行划分,打了三个包。为何不打成一个包?一个包会太大,用户加载大型JS文件包会阻塞,影响页面性能。
接下来是vue.config.js的配置:
plugins: [
new webpack.ProvidePlugin({
$: "zepto",
Zepto: "zepto",
}),
new DllReferencePlugin({
manifest: require('./build/echarts-manifest.json'),
}),
new DllReferencePlugin({
manifest: require('./build/utils-manifest.json'),
}),
new DllReferencePlugin({
manifest: require('./build/vuebundle-manifest.json'),
}),
new BundleAnalyzerPlugin(),
]
引入了DllPlugin。接下来配置HTML:
(由于笔者没将DLL打包出来的js文件上传到CDN,所以只能本地自己起个node服务器返回静态资源了)
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="text/javascript" src="http://localhost:3000/echarts.dll.js"></script>
<script type="text/javascript" src="http://localhost:3000/utils.dll.js"></script>
<script type="text/javascript" src="http://localhost:3000/vuebundle.dll.js"></script>
</body>
然后npm run serve,开始页面调试和开发~
舒服~
优化结果:

由于少了大型第三方库,时间控制在了20s左右。优化相对比较明显~
优化与探索总结
3、优化与探索总结
优化至此基本结束。
webpack常见优化方式包括优化路径查找、设置缓存、happypack以及dllplugin。前两项vue-cli已做部分处理,happypack因与vue不兼容无法接入,dllplugin通过单独提取第三方库取得了明显优化。 此外,笔者也尝试剔除项目中无用代码,但效果不痛不痒。
webpack优化方式总结:
1、优化模块查找路径
2、剔除不必要的无用的模块
3、设置缓存:缓存loader的执行结果(cacheDirectory/cache-loader)
4、设置多线程:HappyPack/thread-loader
5、dllplugin提取第三方库
当然,这是针对开发的优化。若针对部署优化,可设置splitchunk、按需加载、部署CDN等,此处不展开。
最后
希望这篇文章能对大家有所收获~ webpack已是前端必备技能~有空钻研webpack配置和原理,会有所收获!谢谢观看~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。