分析性能
在进行具体的优化之前,我们需要借助工具,分析一下性能。以下操作推荐在浏览器无痕模式下进行,可以多测几次,取一个平均值,避免网络波动带来较大的误差!
- chrome tools network 查看请求信息
- lighthouse 收集网页性能指标
- webpack-bundle-analyzer 查看打包模块依赖关系和包体大小
vue-cli3
是内置了webpack-bundle-analyzer
插件,我们可以直接通过"build:report": "vue-cli-service build --report"
命令生成report.html
文件进行查看。
打包体积优化
从上图中可以看出,打包出来比较大的模块主要是moment
,xlsx
,quill
,ant-design icon
,针对这些插件都可以做出优化。
移除moment的所有本地文件
const webpack = require('webpack');
module.exports = {
plugins: [
// 忽略 moment.js的所有本地文件
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
};
ant design vue 的 icon 按需加载
ant design vue
提供的icon
很多,打包出来有500+kb,其实很多我们都用不上,需要对其进行按需加载处理。
// antIcons.js
// outLine
export { default as CopyOutline } from '@ant-design/icons/lib/outline/CopyOutline';
export { default as CloseCircleOutline } from '@ant-design/icons/lib/outline/CloseCircleOutline';
export { default as WarningOutline } from '@ant-design/icons/lib/outline/WarningOutline';
// fill
export { default as ExclamationCircleFill } from '@ant-design/icons/lib/fill/ExclamationCircleFill';
export { default as CloseCircleFill } from '@ant-design/icons/lib/fill/CloseCircleFill';
export { default as CheckCircleFill } from '@ant-design/icons/lib/fill/CheckCircleFill';
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.resolve.alias
.set('@$', resolve('src'))
.set('@ant-design/icons/lib/dist$', resolve('src/plugins/antdIcons.js'));
}
}
按需加载,会导致一些内部组件使用的icon丢失,也需要手动给它导入一下。
三方库 配置 CDN链接
// vue.config.js
const assetsCDN = {
// webpack build externals
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
xlsx: 'XLSX',
quill: 'Quill'
},
js: [
'//cdn.xxx.com/vue@2.6.0.min.js',
'//cdn.xxx.com/vue-router@3.5.3.min.js',
'//cdn.xxx.com/vuex@3.1.1.min.js',
'//cdn.xxx.com/axios@0.19.2.min.js',
'//cdn.xxx.com/xlsx.core.min.js',
'//cdn.xxx.com/quill_v1.3.6.min.js'
]
};
module.exports = {
configureWebpack: {
// 打包时遇到这些externals,就不把它们打进去
externals: isProd ? assetsCDN.externals : {}
},
chainWebpack: (config) => {
if(isProd) {
// 把cdn的地址添加到index.html页面中
config.plugin('html').tap(args => {args[0].cdn = assetsCDN;return args;});
}
}
}
externals
是 key-value
的形式,key
是package.json
中安装的包名,value
是包真实注册或者说暴露的全局变量的值,比如vue-router
的value
是VueRouter
,打开vue-router
的源码,格式化可以看到如下,注册的值是VueRouter
,其他的插件类似。
xlsx的处理
在用xlsx
做前端导出excel功能时,网上大部分代码中都是用script-loader
去加载xlsx
的:
require('script-loader!xlsx/dist/xlsx.core.min');
....
这样做并不能实现externals
的功能,最后打包出来还是会引入一个xlsx.core.min.js
文件,其实最新的xlsx
已经支持import
方式引入了:
import XLSX from 'xlsx'
cdn地址有条件的情况下,最好用公司内部的,预防第三方cdn失效,导致线上崩溃,不能使用。
图片压缩
yarn add image-webpack-loader -D
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
bypassOnDebug: true
})
.end();
其实我更喜欢在拿到图片的时候,就用第三方工具压缩一下,还可以比较好的保证图片的清晰度。使用插件压缩,还会增加打包时间,看取舍吧。
推荐一个在线图片压缩,最高压缩可达80%,比插件的效果好。
打包时间优化
speed-measure-webpack-plugin 打包时间分析
yarn add speed-measure-webpack-plugin -D
// vue.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = {
configureWebpack: smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
],
});
}
运行打包命令的时候就可以看到每个loader
和plugin
执行耗时:
speed-measure-webpack-plugin
,会大大加大打包的时间,所以部署上线的时候最好是注释掉。
Happypack 多进程打包
yarn add Happypack -D
plugins: [
new Happypack({
loaders: ['babel-loader', 'vue-loader', 'url-loader'],
cache: false,
threads: 5 // 线程数取决于你电脑性能的好坏,好的电脑建议开更多线程
})}
]
HardSourceWebpackPlugin 打包缓存
yarn add hard-source-webpack-plugin -D
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
plugins: [
isProd ? new HardSourceWebpackPlugin() : () => {},
]
第一次打包时间没什么提升,但是第二次开始提升效果就很明显了。在node_modules/.cache
中可以看到它的缓存文件。
首屏加载优化
分析 覆盖率 ,进行拆包splitChunks
Chrome DevTools
有一组平行的选项卡,被隐藏在主窗口之下。这个组合被称为 Drawer
。
当你在 DevTools
(任何选项卡)中时,按 [esc]
来显示它,再次按 [esc]
隐藏它:
通过上图我们可以看出chunk-vendors.js
文件的未使用字节数(Unused Bytes
)达到了97%
,这表示首页加载的大部分js都是用不着的。
经过上面的优化,我们可以打包看一下包体分析:
chunk-venders
很大,它包含了大量的依赖库,ant-design-vue
,vxe-table
,moment
等,我们可以通过splitChunks
将这些第三方库进行抽离独立打包。
// vue.config.js
config.optimization.splitChunks({
cacheGroups: {
common: {
name: 'chunk-common',
chunks: 'all',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
priority: 1,
reuseExistingChunk: true
},
echarts: {
name: 'chunk-echarts',
test: /[\\/]node_modules[\\/]echarts[\\/]/,
chunks: 'all',
priority: 2,
reuseExistingChunk: true,
enforce: true
},
// 增加一个vxe-table
vxeTable: {
name: 'chunk-vxe-table',
test: /[\\/]node_modules[\\/]vxe-table[\\/]/,
priority: 3,
chunks: 'all',
reuseExistingChunk: true,
enforce: true
},
// 增加一个ant-design-vue
antDesign: {
name: 'chunk-ant-design',
test: /[\\/]node_modules[\\/]ant-design-vue[\\/]/,
chunks: 'all',
priority: 4,
reuseExistingChunk: true,
enforce: true
},
esdkObs: {
name: 'esdk-obs-browser',
test: /[\\/]src[\\/]plugins[\\/]esdk-obs-browserjs-3\.19\.5\.min/,
chunks: 'all',
reuseExistingChunk: true,
enforce: true
},
moment: {
name: 'chunk-moment',
test: /[\\/]node_modules[\\/]moment[\\/]/,
priority: 3,
chunks: 'all',
reuseExistingChunk: true,
enforce: true
}
}
});
- name: 打包的 chunks 的名字
- test: 匹配到的模块奖杯打进这个缓存组
- chunks: 代码块类型 必须三选一: “initial”(初始化) | “all”(默认就是 all) | “async”(动态加载)默认 Webpack 4 只会对按需加载的代码做分割。如果我们需要配置初始加载的代码也加入到代码分割中,可以设置为 ‘all’
- priority: 缓存组打包的先后优先级,数值大的优先
- minSize: 默认是30000)形成一个新代码块最小的体积
- minChunks: (默认是1)在分割之前,这个代码块最小应该被引用的次数
- maxInitialRequests:(默认是3)一个入口最大的并行请求数
- maxAsyncRequests:(默认是5)按需加载时候最大的并行请求数
- reuseExistingChunk: 如果当前的 chunk 已被从 split 出来,那么将会直接复用这个 chunk 而不是重新创建一个
- enforce: 告诉 webpack 忽略 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests,总是为这个缓存组创建 chunks
开启gzip
在nginx下开启gzip有两种方式:
gzip on
调用服务器cpu 动态打包gzip_static on
使用前端已经打包好的gzip文件 更快
可以看一下[nginx官方文档配置]。(nginx.org/en/docs/htt…)
我们使用前端打好gzip文件,因为调用服务器cpu 动态打包,耗时间也耗性能,部署的时候前端打包好,就省时省力了。
yarn add compression-webpack-plugin -D
plugins: [
new CompressionPlugin({
test: productionGzipExtensions, // 所有匹配此{RegExp}的资产都会被处理
threshold: 512, // 只处理大于此大小的资产。以字节为单位
minRatio: 0.8, // 只有压缩好这个比率的资产才能被处理
deleteOriginalAssets: false // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
}),
]
其他代码优化
- 取公共组件, 公共代码(Mixins, Utils)
- 异步加载组件
components: {
TestComponent: () => import('@/compents/TestComponent')
}
- 路由懒加载
{
path: '/login',
component: () => import('@/views/login.vue')
}
主要作用是将路由对应的组件打包成一个个的js代码块, 只有在这个路由被访问到的时候,才加载对应的组件,否则不加载!
最后
看一下优化的效果: