前言
网站的性能跟用户的体验息息相关,用户体验差,客流量就会降低,所以在面试中也经常会被问:“你做过那些性能优化....”,诸如此类的问题,下面小编以自己的项目为例,从时间和空间维度介绍如何从用户视觉开启性能优化的视觉盛宴...
性能指标
常见的性能指标有以下几个:
-
FP(first-paint) ---> 页面首次绘制元素时间
-
FCP(first-contentful-paint) ---> 页面内容首次加载时间
-
LCP(largest-contentful-paint) ---> 最大内容加载时间
-
FID(first-input-delay) ---> 用户首次交互到浏览器响应时间
-
CLS ---> 累计位移偏移,页面上非预期位移波动
构建
在使用 build 命令打包项目之后,可以看到打包完的 js 文件夹只有一个 .js 文件,里面包含业务代码、第三方依赖,导致该文件体积臃肿
如图,在项目打包完之后的 js 体积接近 2.4M,可以想象一个请求传输的资源体积这么大,耗时可谓相当可怕
可以看到一个 2.5M 的包在上线后的加载速度高达 3秒半。
问题来了:如何减小包体积加快请求响应时间呢?
聪明的小伙伴应该想到可以通过 gzip 压缩包体积,该项目用的是 webpack ,那小编就使用compression-webpack-plugin 实施
// 通过 npm 下载包后,添加该配置
const CompressionWebpackPlugin = require('compression-webpack-plugin');
(config) => {
if (process.env.NODE_ENV === 'production') {
config.plugins.push(
new CompressionWebpackPlugin({
test: /\.js$|\.css$/, // 压缩 js 和 css
filename: '[path][base].gz', // 输出 .gz 文件
algorithm: 'gzip',
threshold: 10240, // 文件大于10KB压缩
minRatio: 0.8, // 当压缩后的文件大小小于原始文件大小的 80% 时,才会保留该压缩文件。
deleteOriginalAssets: false, // 删除原文件
})
);
}
return config;
},
有些浏览器默认支持gzip压缩,可以通过网络条件配置,如图:
浏览器支持gzip压缩后,在响应头会多出 content-encoding 字段,如图:
我们再回来看压缩后的体积以及响应速度
成功了,可以看到响应速度由原来的3秒半缩短到1秒,但是响应时间还是很久。
那除了压缩还有什么方法?
小编想到 http 可以支持并发请求,在打包的时候如果按照不同功能分成不同的包如:核心库(react、react-dom、vue、vue-router...),体积大的包如(echarts、lodash...),业务包...等等,在请求的时候分别请求不同的包是不是就大大加快了响应时间。
// 拆分 chunks
function splitChunks(config) {
config.optimization = {
...config.optimization,
splitChunks: {
cacheGroups: {
// React 相关库单独打包
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 30,
enforce: true,
},
// lodash 单独打包
lodash: {
test: /[\\/]node_modules[\\/]lodash[\\/]/,
name: 'lodash',
chunks: 'all',
priority: 25,
enforce: true,
},
// echarts 及其依赖单独打包
echarts: {
test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
name: 'echarts',
chunks: 'all',
priority: 20,
enforce: true,
},
// 业务代码公共部分打包进 common.js
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
reuseExistingChunk: true,
priority: 10,
},
// 其它第三方依赖打包进 vendors.js
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 0,
},
},
},
};
return config;
}
分包主要做了什么优化:
- 加快构建速度,每一个包的构建相互独立并发构建,充分利用了多核 CPU
- 当依赖或业务代码发生改变,无需重新构建所有依赖,只需改变对应的包
- 利用构建工具的缓存功能,没有改变的依赖在下一次构建时直接读取缓存
- 减小包体积,并利用请求并发,在线上可以并发请求各个包,加快加载速度
结合分包及 gzip 压缩后效果如图:
可以看到请求是并发进行的互不干扰,减少了白屏时间,提高用户体验。
这时候可能会有人想到,对每个页面的代码及依赖进行分包,并对每个网页都进行组件懒加载,这样在需要某个部分的代码时加载对应的文件,岂不美哉?
小编很久之前就做过这样的事,结果网络请求太多,网络负荷过高,得不偿失。
适合组件懒加载的场景(常考题):
- 某个页面的 JS 文件体积大,导致页面白屏时间长,通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)
- 该组件不是一进入页面就展示,需要一定条件下才触发(弹框组件)
- 该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)
尽管进行压缩以及分包,但是效果似乎并没有那么出色,那还有什么方法呢?
我们接着观察打包后的产物,可以发现单独一个 echarts 的包就高于1M,那是不是可以在 echarts 上面做文章
如何减少echarts包的体积呢?应该很多小伙伴已经想到了吧,没错!就是按需引入我们需要的部分。因为该项目只用了饼图,所以就只引入饼图相关内容即可:
// echarts.js
import * as echarts from 'echarts/core'; // 引入 ECharts 的核心模块,包含基础功能和框架。
import { PieChart } from 'echarts/charts'; // 引入饼图组件
import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent } from 'echarts/components'; // 引入常用的 ECharts 组件
import { LabelLayout, UniversalTransition } from 'echarts/features'; // 引入可选功能
import { CanvasRenderer } from 'echarts/renderers'; // 引入 Canvas 渲染器
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
PieChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
export default echarts;
接着只需在组件部分引入该 echarts.js 就能够正常使用了,实现后包体积减小了一半,效果如图:
我们再利用 lightHouse 插件对网页进行分析,如图:
可以看到是由媒体资源引起的 LCP(最大内容渲染时间)过久
接下来对该图片资源进行分析:
- 内容大小远高于呈现大小(即分辨率)
- 分辨率过高导致图片体积大
解决方案:图片格式转换 png -> webp,图片预加载
转换之后效果如图,对于网页展示效果并不影响,大大减小了图片体积
在入口文件index.html中通过link对图片进行预加载
<link rel="preload" as="image" href="..." />优化后效果如图: