Vite 是一种新型前端构建工具,能够显著提升前端开发体验。本文将带领大家掌握如何对 Vue3 + Vite 项目进行性能优化,提升用户体验。
在开始之前,我们需要明白一个原则:性能优化的最终目的是提升用户体验。
简而言之就是让用户感觉这个网站很「快」(至少不慢hh),这里的「快」有两种,一种是「真的快」一种是「觉得快」
- 「真的快」:可以客观衡量的指标,像网页访问时间、交互响应时间、跳转页面时间
- 「觉得快」:用户主观感知的性能,通过视觉引导等手段转移用户对等待时间的关注
做好这两方面都能提升用户对网站的性能评价。
一、指标
- FCP(First Contentful Paint):白屏时间(第一个文本绘制时间)
- Speed Index:首屏时间
- TTI(Time To Interactive): 第一次可交互的时
- lighthouse score:Chrome浏览器审查工具性能评分
二、性能分析
我们大部分的性能分析都可以借助 Chrome 完成,大致可以分为
- Network 分析
- Lighthouse 分析
- Bundle 分析
1. Network 分析
2. Lighthouse 分析
使用:
结果:
3. Bundle 分析
因为使用的是 vite,借助插件 rollup-plugin-visualizer,来进行 bundle 分析
import { visualizer } from 'rollup-plugin-visualizer'
const plugins = [vue(), visualizer()]
打包之后会在项目根目录生成 stats.html 文件,打开
三、开始优化
1. 传输优化
(1)GZIP 压缩
gzip 是一种使用非常普遍的压缩格式。使用 gzip 压缩可以大幅减小代码体积,提升网络性能。
import viteCompression from 'vite-plugin-compression'
plugins: [vue(), viteCompression()]
打包后就会生成 gzip 文件了,但是服务端 nginx 还需要配置一下才能生效
http {
gzip_static on;
gzip_proxied any;
}
(2)分包策略
默认情况下,浏览器重复请求相同名称的静态资源时,会直接使用缓存的资源。利用这个机制我们可以将不会经常更新的代码单独打包成一个 JS 文件,这样就可以减少 HTTP 请求,同时降低服务器压力。以 lodash 为例:
// 安装 lodash
npm i lodash
// src/main.js
import { cloneDeep } from 'lodash'
const obj = cloneDeep({})
1.2.3.4.
打包结果:
项目代码和依赖模块打包成了一个 JS 文件,接着我们来配置分包,修改底层的 Rollup 配置:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: id {
// 将 node_modules 中的代码单独打包成一个 JS 文件
if(id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
打包结果如下:
可以看到依赖模块已经单独生成一个 JS 文件了。这样我们即使修改了 main.js 中的代码重新打包,依赖文件 vendor.528a7280.js 也不会发生变化的,对于这个文件,浏览器也不会再次发起请求。
(3)treeshaking
treeshaking 也被称为 摇树优化。简单来讲,就是在保证代码运行结果不变的前提下,去除无用的代码。Vue3中,许多 ApI 的引入都支持 treeshaking 优化,也就是说只打包你用到的 API,忽略那些没有用到的。
Vue3 会默认使用 Rollup 进行 treeshaking ,不需要额外进行配置。但有一个条件,必须是 ES6 module 模块才行。还是上面那个例子:
// src/main.js
import { cloneDeep } from 'lodash'
const obj = cloneDeep({})
由于 lodash 是使用 CommonJS 规范的模块,所以无法进行 treeshaking ,Vue 会把整个 lodash 依赖打包进来,整个依赖文件的大小是 78.64 KB 。然后我们使用 ESM 版的 loadsh 对比一下:
// 安装
npm i lodash-es
// src/main.js
import { cloneDeep } from 'lodash-es'
const obj = cloneDeep({})
打包结果如下:
可以看到依赖体积瞬间变成了 13.23 KB ,是不是一下小了很多。所以我们在选择第三方库时,要尽可能使用 ESM 版本,可以提升不少性能!
(4)低版本浏览器兼容
import legacyPlugin from '@vitejs/plugin-legacy'
...
plugins: [
legacyPlugin({
targets: ['chrome 52'], // 需要兼容的目标列表,可以设置多个
additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // 面向IE11时需要此插件
})
],
(5)cdn 加速
内容分发网络(Content Delivery Network)就是让用户从最近的服务器请求资源,提升网络请求的响应速度。通常我们请求依赖模块使用 CDN ,而请求项目代码依然使用自己的服务器。还是以 lodash 为例:
// src/main.js
import _ from 'lodash'
const obj = _.cloneDeep({})
使用 CDN 也比较简单,一个插件就可以搞定:vite-plugin-cdn-import
// vite.config.js
import { defineConfig } from 'vite'
import viteCDNPlugin from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
viteCDNPlugin({
// 需要 CDN 加速的模块
modules: [
{
name: 'lodash',
var: '_',
path: `https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js`
}
]
})
]
})
构建成功后,Vite 会自动帮我们将 cdn 资源通过 script 标签插入到 html 中:
(6)开启HTTP2
HTTP2是HTTP协议的第二个版本,相较于HTTP1 速度更快、延迟更低,功能更多。通常浏览器在传输时并发请求数是有限制的,超过限制的请求需要排队,以往我们通过域名分片、资源合并来避开这一限制,而使用HTTP2协议后,其可以在一个TCP连接分帧处理多个请求(多路复用),不受此限制。(其余的头部压缩等等也带来了一定性能提升)
如果网站支持HTTPS,请一并开启HTTP2,成本低收益高,对于请求多的页面提升很大,尤其是在网速不佳时。在 Nginx中开启HTTP2:
// nginx.conf
listen 443 http2;
// 重启Nginx
nginx -s stop && nginx
HTTP2开启后,多路复用避开了资源并发限制,但资源太多的情况,也会造成浏览器性能损失。
(7)路由懒加载
SPA 中一个很重要的提速手段就是路由懒加载,当打开页面时才去加载对应文件,我们利用Vue的异步组件就可以轻松实现懒加载了。
{ path: '/login', component: () => import('@/views/login/index.vue') }
2. 体积优化
(1)排查并移除冗余资源
- 移除项目模板冗余依赖
- 静态资源应该放在 assets 下,public 只会单纯的复制到dist,而 assets 中的静态资源会走打包压缩流程,压缩后的文件会放置在 static 文件中跟着 index.html 一同上传至服务器、
(2)图片压缩
根据项目对清晰度的要求,我们可以使用 vite-plugin-imagemin 插件,对图片进行适当压缩:
// vite.config.js
import { defineConfig } from 'vite'
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
})
]
})
打包后会生成压缩的图片,但是每次打包都会重新压缩一遍,比较浪费时间,如果不介意的话采用此方案也很简单,网上查了一下有使用本地压缩的方案,不过配置比较繁琐。
(3)使用webP图片
webP 是谷歌推出的新图片格式(2010),同等质量下体积比png、jpg 格式小很多,目前兼容性还算可以。可以手动,也可以加入构建自动化生成webP图片。
(4)按需引入组件库
可以使用 unplugin-vue-components 和 unplugin-auto-import 这两款插件来开启按需加载及自动导入的支持。插件会自动解析模板中的使用到的组件,并导入组件和对应的样式文件。
这里以 Arco Design Vue 为例:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite';
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ArcoResolver()],
}),
Components({
resolvers: [
ArcoResolver({
sideEffect: true
})
]
})
]
});
(5)Echarts 单独拆分
build: {
rollupOptions: {
output: {
manualChunks: {
echarts: ['echarts']
}
}
}
}
3. 感知优化
(1)渐进加载图片
一般来说,图片加载有两种方式,一种是自上而下扫描,一种则是原图的模糊展示,然后逐渐/加载完清晰。前者在网速差的时候用户体验较差,后者的渐进/交错式加载则能减轻用户的等待焦虑,带来更好的体验
(2)Loading动画
首屏优化,在 JS 没解析执行前,让用户能看到 Loading 动画,减轻等待焦虑。通常会在 index.html上写简单的 CSS 动画,直到 Vue 挂载后替换挂载节点的内容,但这种做法实测也会出现短暂的白屏,建议手动控制CSS动画关闭。
(3)首屏骨架加载
首屏优化,APP内常见的加载时各部分灰色色块。针对骨架屏页的自动化生成,业界已有不少解决方案。