记一次Vue-CLI生产项目的打包优化
前言
此次是接手一个老项目(老项目大家都懂的,想哭😭);项目里面的依赖繁杂,而且引用也不太规范。就先吐槽吐槽项目,首先:项目里引用了三个UI组件库,Element
、view-design
、还有一个xxx-admin
的(不要问为什么);其次:样式里Less
、Sass
同时使用,混乱不堪;最后:一些非生产依赖通通安装 dependencies
下。吐槽先放放,下面就开始说说我对项目的优化。
项目初始体积和打包速度
第二张图是初次打包时间,3分多钟;第三张图是再次打包,1分20秒的样子,因为Vue CLI
本身对优化的已经做了,所以我们只需针对项目优化即可,因而后面打包时间相对第一次会减少一些。
查看分析打包可以使用CLI自带命令:"build": "vue-cli-service build --report"
,打包后 dist
目录会生成 report.html
文件,用来分析各文件的大小,或者安装插件 webpack-bundle-analyzer
。
# 安装插件
npm install webpack-bundle-analyzer -D
# 测量各个插件和 loader 所花费的时间插件
npm i speed-measure-webpack-plugin -D
# vue.config.js 中引用
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
module.exports = {
configureWebpack: smp.wrap({
plugins: [
new BundleAnalyzerPlugin()
]
})
}
组件库的按需引用
目前高版本的组件库基本默认支持基于 ES modules
的 tree shaking
,直接引入组件就会有按需加载的效果。而项目还是在 Vue2
的版本,针对组件库的按需加载,一般是借助依赖 babel-plugin-import
,将 .babel.config.js
修改为:
# Element 举例,各UI组件库都有对应文档,查看文档按照文档中配置即可
{
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
而我的项目因为历史原因,组件库这块不打算动它了,动了的痛想必各位小伙伴都能体会其中的痛😖。
减少 moment 插件体积
针对时间处理,项目引用的是 moment
,打包后有很多没有用到的语言包,而项目目前并没有多语言需求,所以可以删除多余语言包
# 安装依赖
npm install moment-locales-webpack-plugin -D
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new MomentLocalesPlugin({localesToKeep: ['zh-cn']})
]
}
}
前后对比:
打包前
打包后
从图片可以看出,前后直接减少有200KB
目前的打包时间为:
多线程打包
thread-loader
实现多线程打包,它把任务分解给多个子进程去并发的执行,来提升打包速度。
Vue-CLI
已经内置thread-loader
,thread-loader
会在多核 CPU 的机器上为 Babel/TypeScript
转译开启。
# 此配置不建议一开始就使用,适用于大项目里比较耗时的loader
module.exports = {
parallel: true,
}
经本项目多次打包构建测试,开启后平均时间为 50s
左右,偶发性一次在 33s
,不开启平均 45s
左右,因此视情况而定。
移除console
webpack v5 开箱即带有最新版本的 terser-webpack-plugin。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本
# 安装依赖
npm install terser-webpack-plugin --save-dev
# 使用
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
configureWebpack: {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
format: {
comments: false // 删除注释
},
compress: {
warnings: false,
drop_console: true, // 删除console
drop_debugger: true // 删除debugger
}
},
extractComments: false // 删除注释
})
]
}
}
}
Gzip压缩
# 安装依赖
npm install compression-webpack-plugin -D
module.exports = {
chainWebpack: config => {
// 开启js、css压缩,生成gz压缩文件
if (VUE_APP_ENV_PROD) {
config
.plugin('CompressionWebpackPlugin')
.use(require('compression-webpack-plugin'), [
{
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(js|json|css)$'),
threshold: 10240, // 资源文件大于10240B=10kB时会被压缩
minRatio: 0.8 // 最小压缩比达到0.8时才会被压缩
// deleteOriginalAssets: true // 删除原文件
}
])
.end()
}
},
}
生产配置CDN
关于配置CDN
,个人建议如果公司有自己的CDN库
,可以配置,如果没有而是引用第三方的,这个得酌情考虑了,因为我自己有遇见引用三方CDN
,出现挂了的情况,导致线上报错了。
第三方CDN地址:
vue.config.js
中配置:
module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
echarts: 'echarts',
}
}
- 在
index.html
中使用CDN
引入依赖
<body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3.1.6/dist/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.9.0/dist/echarts.min.js"></script>
</body>
- 以上手动配置不太方便,可借助
HtmlWebpackPlugin
插件来方便插入cdn
的引入
// 生产配置
const cdn_production = {
js: [
'//cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.min.js',
'//cdn.jsdelivr.net/npm/vue-router@3.1.6/dist/vue-router.min.js',
'//cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js',
'//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js',
'//cdn.jsdelivr.net/npm/echarts@4.9.0/dist/echarts.min.js'
]
};
module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
echarts: 'echarts',
},
},
chainWebpack: config => {
if (VUE_APP_ENV_PROD) {
config.plugin('html').tap(args => {
args[0].cdn = cdn_production
return args
})
}
}
};
# index.html中添加:
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
配置CDN测试后,时间确实缩减明显:
图片优化
关于图片优化,有先推荐大家去小熊猫网址压缩,效果非常不错。地址:https://tinypng.com/。而在项目中,图片压缩可以使用 image-webpack-loader
,但是有可能安装失败,将源重新设置一下,如更换淘宝的。
# 安装
npm install image-webpack-loader --save-dev
# 配置
chainWebpack: config => {
config.module.rule('images')
.test(/\.(gif|png|jpe?g|svg)$/i)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 50 }, // 压缩JPEG图像
optipng: { enabled: true }, // 压缩PNG图像
pngquant: { quality: [0.5, 0.65], speed: 4 }, // 压缩PNG图像
gifsicle: { interlaced: false } // 压缩GIF图像
})
.end()
}
文件资源有减少,但是打包时间也确实增加了。前后对比:
使用前(21M
还是很多图片经过熊猫网站压缩后的结果)
使用后
去除生产环境sourceMap
sourceMap
资源映射文件,存的是打包前后的代码位置,方便开发使用,这个占用相当一部分空间。
map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错,有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
module.exports = {
productionSourceMap: false
}
DLL 动态链接库
但在 vue-cli 引入 webpack4 之后,移除了该包,"因为 Webpack 4 的打包性能足够好的,dll 没有在 Vue ClI 里继续维护的必要了。"
dll
option will be removed. Webpack 4 should provide good enough perf and the cost of maintaining DLL mode inside Vue CLI is no longer justified.
配置可参考webpack地址:https://webpack.docschina.org/plugins/dll-plugin#root
总结
从打包分析图看,大部分在node_modules
、地图资源的json
中,业务代码所占比例并不是非常大,而且可能也跟项目写的不太规范有关。而针对图片资源的优化,我觉得性价比是最高的,在经过自己压缩图片物理大小都有20M
,经压缩后才有6M
,图片的压缩率70%
;但是相对的时间确实也上去了,但是我觉得我是可以接受的。内容当然还是有其他一些细节是可以优化的,但是这个可能是需要后期边做边优化。
经过上面的针对性的优化,总结一下表格信息:
对比项 | 优化前 | 优化后 | 优化率 | 说明 |
---|---|---|---|---|
体积 | 15.27M | 12.46M | 近20% | 没想象中高 |
时间(未压缩图片) | 1分34秒 | 53秒 | 44% | 平均测试 |
时间(压缩图片) | 1分34秒 | 3分42秒 | -- | 平均2分47秒在图片压缩loader上,单看时间打包远远偏高了,无任何优化,但针对项目资源访问速度,那是远比之前提高了 |
这些也都是大家项目中可以使用的一些方式,针对项目合理使用可以有效地提升项目的性能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。