[前端优化]项目优化--Lighthouse
- 前端优化的分类
- Lighthouse 优化工具
- 优化维度--性能(Performance)
- 性能指标概览
- 白屏时间--FP
- 首字节时间--TTFB
- 首次输入延迟--FID
- 累积布局偏移--CLS
- 性能指标分析
- Lighthouse的性能优化方案
- 性能优化实战解析
- Serve images in next-gen formats
- Enable text compression
- gzip 压缩
- 项目打包时gzip压缩
- vue.config.js配置 gzip 压缩:
- vite.config.js 配置 gzip 压缩:
- 加载时允许使用压缩
- nginx代理 gzip压缩
- 图片压缩
- vue.config.js配置图片压缩
- vite.config.js配置图片压缩:
- 禁止生成 map 映射文件
- 拆分三方包
- vue.config.js的拆分方式
- vite.config.js的拆分方式
- 删除 console 打印语句
- vite.config.js配置删除 console
- @vue/cli配置删除 console
- CDN 资源加载
- vite配置 CDN 资源加载
- Treeshaking
- 总结分析
- 优化维度--可访问性(Accessibility)
- ARIA
- NAMES AND LABELS
- 优化维度--SEO(Search Engine Optimization)
前端优化的分类
前端优化分为代码优化及页面优化
页面优化主要针对页面加载环节,包括:HTTP 请求数、脚本的无阻塞加载、内联脚本的位置优化等内容。
代码优化包括:Javascript 中的 DOM 操作优化、CSS 选择符优化、图片优化以及 HTML 结构优化等内容。
代码级别优化则更关注数据请求,很重要的一条就是减少 HTTP 请求的数量。一个完整的 HTTP 请求需要经过路由查找,TCP 握手,发送请求,服务器响应和浏览器接收等一些列过程。对于小文件,实际下载文件的时间对于整个请求的时间占比很低,因此需要将小文件合并为大文件来传输。
Lighthouse 优化工具
Lighthouse 是 Google Chrome 推出的开源工具,现在的 Google Chrome 浏览器也集成了 LightHouse 优化工具,它从多个维度量化的去衡量前端项目的用户体验。
从上图可以看出通过性能、可访问性、是否是最佳实践、SEO的设置与渐进式框架的得分来判断是否还有优化空间
优化后
优化维度–性能(Performance)
性能指标概览
Google定义了衡量网站性能的基本指标,性能维度的数值是各个性能指标综合后的得分,一般将得分与标准值进行比较反应网站的性能,体现性能的指标项包括:
性能指标名称 | 性能指标说明 | 推荐标准值 |
---|---|---|
首次内容绘制(First Contentful Paint) | 即浏览器首次将任意内容(如文字、图像、canvas 等)绘制到屏幕上的时间点 | 标准≤1s |
最大内容绘制(Largest Contentful Paint) | 度量标准报告视口内可见的最大图像或文本块的呈现时间 | 标准≤2s |
交互时间(Time to Interactive) | 指的是所有的页面内容都成功加载,且能够快速地对用户的操作做出反应的时间点 | |
总阻塞时间(Total Blocking Time) | 指首次内容绘制 (FCP)与 交互时间 (TTI)之间的总时间 | |
累积布局偏移(Cumulative Layout Shift) | 衡量的是页面整个生命周期中每次元素发生的非预期布局偏移得分的总和。每次可视元素在两次渲染帧中的起始位置不同时,就说是发生了 LS(Layout Shift) | 标准≤0.1s |
速度指标(Speed Index) | 衡量了首屏可见内容绘制在屏幕上的速度。在首次加载页面的过程中尽量展现更多的内容,往往能给用户带来更好的体验,所以速度指标的值约小越好 |
以下是一些实际中经常出现的一些概念性问题的说明。
白屏时间–FP
白屏时间FP(First Paint)指的是从用户输入url的时刻开始计算,一直到页面有内容展示出来的时间节点,推荐的标准≤2s
首字节时间–TTFB
默认指导航请求的TTFB(Time to First Byte),导航请求是指在浏览器切换页面时创建,从导航开始到该请求返回HTML
首次输入延迟–FID
FID(First Input Delay)首次输入延迟,标准是用户触发后,浏览器的响应时间, 标准≤100ms
累积布局偏移–CLS
这里对 CLS(Cumulative Layout Shift) 做一个说明,一开始我理解的CLS关注的是因为偏移问题,导致了重新渲染,降低了性能,之后了解到官网的解释是指:
用户下单时发现自己的订单有误,所以想返回重新选择,但是此时页面并没有完成加载,导致了广告栏被展示了,还没来得及隐藏,使得提交按钮与取消按钮的整体下移,此时用户手快,点击了提交按钮,其实用户是想点击取消按钮,用户的操作偏移到了提交上,因此该指标也是一个重要指标
性能指标分析
开发环境性能指标分析:
线上生产环境性能指标分析:
上图明显可以看出线上环境的指标数据比开发环境好,这是因为一般浏览器的开发插件可能导致性能降低!!!而且开发环境米有经历压缩打包等一系列的优化处理,而线上资源包都是处理后的产品,线下环境是不打包开发资源的
Lighthouse的性能优化方案
Lighthouse工具提供了一些优化的方案:
Opportunities
节点下的建议:
- Enable text compression:开启文本压缩
- Serve images in next-gen formats:使用下一代图片格式
- Eliminate render-blocking resources:消除非页面加载时关键的js、css资源
- Efficiently encode images:压缩图片大小
- Avoid large layout shifts:避免大的布局变化
Diagnostics
节点下的内容需要开发者通过调试来分析诊断;建议包括:
- Serve static assets with an efficient cache policy: 静态资源使用高效的缓存策略,这里是因为一般静态资源发生变化的概率较小,而从内存缓存中加载静态资源基本是不消耗时间,磁盘缓存消耗时间一般也很小,也就是说从本地加载资源都比从服务器加载资源速度快,所以使用合适的缓存策略很重要
- Avoid enormous network payloads:避免大的资源加重网络负载
Passed Audit
节点下的内容可以查看所有检查的项,你也可以根据这部分逐项优化改进:
- Minimize main-thread work:最小化主线程 这里会执行解析 Html、样式计算、布局、绘制、合成等动作
Chrome浏览器,默认会限制6个文件数量的并发请求。因此如果你有12个文件,针对 http 1.0 协议,就会分成2个批次去请求资源;如果是http2.0,就会同时请求这12个文件
性能优化实战解析
Serve images in next-gen formats
本人的项目Lighthouse工具有该提示,展开后有详细说明,说是最好使用下一代图片格式webp或者avif
。
因为现在网站涉及图片越来越多,所以对于同样清晰度的图片,体积自然是越小越好,而目前(2023年)这一点做的比较极致的两种格式是 webp/avif,接下还有一个比较关键的问题是浏览器的支持性。
支持webp的浏览器包括chrome/edge/firefox;
avif格式的图片,同样清晰度文件大小比webp还要小20%,但是支持的浏览器较少,所以本人采取了webp格式图片。
本人背景图是采用了一张大图,通过在线转换工具转换为webp质量无损的情况,体积缩小了20倍!!!
之前页面会出现渲染的痕迹,一部分一部分的在加载,修改后加载非常快,也不再有该现象
但是有一点需要注意的是,我发现本身体积就小的png图片,转化为webp反而体积增大了,所以感觉较大的图片比较适合转换为webp图片
这里记录一个非常好用的在线转换工具squoosh
Enable text compression
开启文本压缩,本人因为是轻量级的小项目,因此一开始是米有考略压缩的问题,通过该工具,发现部分三方资源的库打包后还是挺大的,具体解决方案可以参考以下压缩方案
gzip 压缩
gzip 压缩可以特别明显的提高我们的代码加载效果,提升效率 5-6 倍左右,它会把诸如 js、css 等文件进行压缩,并且让我们在加载时去请求那些 gz 文件而提升请求效率。
项目打包时gzip压缩
vue.config.js配置 gzip 压缩:
const CompressionPlugin = require("compression-webpack-plugin");
{
configureWebpack: {
plugins: [
new CompressionPlugin({
algorithm: "gzip", // 使用gzip压缩
test: /\.js$|\.html$|\.css$/, // 匹配文件名
filename: "[path].gz[query]", // 压缩后的文件名(保持原文件名,后缀加.gz)
minRatio: 1, // 压缩率小于1才会压缩
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
})
];
}
}
以下是压缩前后对比图:
vite.config.js 配置 gzip 压缩:
import viteCompression from 'vite-plugin-compression'
plugins: [
viteCompression(
{
algorithm: 'gzip',//压缩算法,可选['gzip',' brotliccompress ','deflate ','deflateRaw']
threshold: 10240,// 体积大于阈值,压缩,单位为b
verbose: false,//是否在控制台中打印压缩结果
ext: '.gz',
deleteOriginFile: true,// 源文件压缩后是否删除
disable: false,//是否禁用
}
)
]
注意,虽然设置了阈值,导致小于阈值的文件不会被压缩,但是终端却会打印出每个文件的压缩后大小--即最终生成的打包文件夹里没有该文件的压缩文件,但是会打印该文件压缩后的大小
加载时允许使用压缩
以上方式是在打包阶段生成了一个名为chunk-common.js.gz
的压缩文件200kb,但是发现项目部署后,并不会加载.gz文件,仍旧加载是未压缩的600kb的chunk-common.js
文件???!!!
通过资料的查找说是服务器也要允许加载压缩文件,因为现在基本都是前后端分离,所以这里的服务器,指的是前端代码部署的服务器
,也就是nginx服务器,要支持并仅仅设置gzip_static on;
。然后查看加载的资源,此时如果响应头中有Content-Encoding:gzip
,说明启用gzip加载压缩文件成功
这里需要注意添加配置的位置!!!配置在nginx中 server 中是成功的,但是如果配置在server的location中有时是不成功的??!!!
以下是gzip_static
配置在不同位置时的效果说明:
-
直接配置在 server
server { gzip_static on; }
成功,此时response的响应类型、响应编码都是正常符合要求
-
配置在location根目录中,响应同上,也成功
location / { gzip_static on; }
-
配置在非根location中,目前测试均不成功,也记录一下
# assets文件夹中所有静态资源 location ~ ^/assets { gzip_static on; } # assets文件夹中所有静态资源 location /assets { gzip_static on; } # assets文件夹中所有静态资源js /css文件 location ~ \.(css|js)$ { gzip_static on; } location ~ .*\.(css|js)${ gzip_static on; }
发现所有的响应类型都是text/html,根本不是静态资源的本身类型???!!!
最后在尝试的过程中发现以下设置会正常的动态响应静态文件的类型,发现是因为系统加载了源文件
,根本没有开启gzip,感觉还是对于nginx配置不够熟悉导致的,以后再继续深入吧,目前可以通过方式1,2实现location = .*\.(css|js)${ gzip_static on; }
这里有一点明确说明一下,不论是否是开启压缩,浏览器端加载的文件链接始终是.../assets/js/chunk-common.js
,所以仅从Request URL
是无法判断是否启用压缩了的。但是启用压缩后,可以观察到加载的文件大小变了,由原来的.js
大文件变成了.gz
文件的大小,加载的真实文件是chunk-common.js.gz
,如果找不到该文件可能会去找chunk-common.js
,所以在打包阶段一般不删除源文件,并且浏览器根据响应头的解压缩编码Content-Encoding:gzip
对文件进行解码,所以可以根据两点判断是否启用了压缩:
- 文件的大小
- 响应头中是否存在
Content-Encoding:gzip
,存在即使用了压缩
注意:因为浏览器解压也需要时间,所以代码体积不是很大的话不建议使用 gzip 压缩
nginx代理 gzip压缩
项目开启gzip的方式有多种,这里并不是打包时预先生成了gzip,而是在请求资源时服务器(nginx)临时生成了.gz
文件,服务器实时生成的gz资源再传输给浏览器
该方式主要是通过设置服务器(nginx)的配置文件,而不需要在其它位置再设置或添加代码了
nginx.conf配置文件:
http{
# 开启压缩机制输出
gzip on;
# 指定会被压缩的文件类型(也可自己配置其他类型)
gzip_types text/plain application/javascript text/css application/xml text/javascript image/jpeg image/gif image/png;
# 设置压缩级别,越高资源消耗越大,但压缩效果越好
gzip_comp_level 5;
# Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容
# gzip_static on;
# 在头部中添加Vary: Accept-Encoding(建议开启)
gzip_vary on;
# 处理压缩请求的缓冲区数量和大小
gzip_buffers 16 8k;
# 对于不支持压缩功能的客户端请求不开启压缩机制
gzip_disable "MSIE [1-6]\."; # 低版本的IE浏览器不支持压缩
# 设置压缩响应所支持的HTTP最低版本
gzip_http_version 1.1;
# 设置触发压缩的最小阈值
gzip_min_length 2k;
# 关闭对后端服务器的响应结果进行压缩
gzip_proxied off;
}
设置了以上内容,以上设置放置在非local /
的块内时,静态资源加载没有使用gzip,但是在server/http块内都是生效的
根据以上两种方式:
一种预生成gzip文件,所有都使用gz文件,如果gz不存在就使用js文件
一种是加载时生成,所有请求时实时生成并使用gz文件
nginx.conf中关于gzip_static是注释掉的,或者没有的,此时仅仅是实时生成。
那么想要实现服务器存在则使用服务器.gz文件,如果服务器不存在.gz文件则实时生成.gz文件文件?
很简单,就是nginx.conf的配置文件两种配置方式都涵盖在内(即实时生成的配置中 gzip_static选项也开启或者去掉注释即可)!!!
那么对于同时使用了以上两种压缩方式的时候如何判断请求的资源是实时生成的.gz文件,还是打包时生成的.gz文件?
发现两种资源的响应头存在显著的区别,实时在线生成的Etag标签前有W/
符号,但是响应头中两者的解码方式都是Content-Encoding:gzip
:
Etag:"654a0526-30eab"//打包时生成.gz文件
Etag:W/"654a0526-1a92"//请求时在线生成.gz文件
图片压缩
这里的图片压缩与上面的方式完全不同,上面Serve images in next-gen formats
是指修改为下一代的图片格式,以最小体积展示同样清晰度的图片,而这里是指打包的时候对图片进行压缩
vue.config.js配置图片压缩
npm install image-webpack-loader
module.exports = {
chainWebpack: config => {
// 开启图片压缩
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true });
},
};
vite.config.js配置图片压缩:
npm i vite-plugin-imagemin -D
// 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
}
]
}
})
]
});
禁止生成 map 映射文件
vue 打包会保留源文件的 map 映射,方便打包部署后依然可以通过控制台的 source 查看搜索源文件的代码,方便定位问题,但是生成 map 文件会导致打包文件过大,看是否需要打包部署后的定位问题,不需要的话可以考虑禁止生成 map 映射文件。
module.exports = {
productionSourceMap: true
};
拆分三方包
将一些三方包拆出来,如 vue, echarts 等都很大,拆出来后直接在页面加载更利于缓存。
vue.config.js的拆分方式
{
configureWebpack: (config) => {
let plugins = [
new CompressionWebpackPlugin({
algorithm: "gzip", //压缩方式
test: productionGzipExtensions, //匹配压缩文件
threshold: 2048 //对大于10k压缩
})
];
if (process.env.NODE_ENV === "production") {
config.mode = "production";
config.plugins = [...config.plugins, ...plugins];
} else {
config.mode = "development";
}
Object.assign(config, {
externals: {
vue: "Vue",
element: "ElementUI",
echarts: "echarts"
}
});
};
}
拆出三方包后,打包文件会明显变小,但在 html 页面上需要显示引入三方包,如下:
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
但是该方式也是存在隐患的,假如,有一天,该静态资源不可使用了…
vite.config.js的拆分方式
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: id => {
// 将 node_modules 中的代码单独打包成一个 JS 文件
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
}
}
})
删除 console 打印语句
vite 引入工具包:
npm i terser -D
vite.config.js配置删除 console
export default defineConfig(({ mode }) => {
return {
build:{
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}
}
})
@vue/cli配置删除 console
在@vue/cli脚手架中一般通过配置 babel.config.js:
const prodPlugin = []
if (process.env.NODE_ENV === 'production') {
prodPlugin.push([// 如果是生产环境,则自动清理掉打印的日志,但保留onsole.error 与 console.warn
'transform-remove-console',
{
exclude: ['error', 'warn']
}
])
}
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
...prodPlugin
]
};
CDN 资源加载
内容分发网络(Content Delivery Network,简称 CDN)是让用户从最近的服务器请求资源。提升网络请求的响应速度,同时减少应用打包体积,利用浏览器缓存,可以长期缓存不会变动的文件
vite配置 CDN 资源加载
- 通过 vite-plugin-cdn-import 工具包
引入工具包:
npm i vite-plugin-cdn-import -D
vite.config.js配置CDN 资源加载:
// 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-plugin-html/rollup-plugin-external-globals 工具包
引入工具包
npm i vite-plugin-html -D
npm i rollup-plugin-external-globals -D
vite.config.js配置:
export default defineConfig(()=>{
return {
build: {
rollupOptions: {
external: ['vue'],
plugins: [
externalGlobals({
// "项目中引入的变量名称":"CDN包导出的名称,一般在CDN包中都是可见的"
vue: 'Vue'
})
]
}
},
plugins: [
createHtmlPlugin({
minify: true,//是否压缩 html
inject: {
data: {
vuescript: '<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37"></script>'
}
}
})
]
}
})
在index.html文件中的引入(定义的名称是什么,引入就用什么名称,例如上述定义为vuescript
):
<head>
<%- vuescript %>
</head>
Treeshaking
对没有用到的插件、api 不打包到最终打包后的静态文件中去,最新的 Vue、React 都是支持 Tree-Shaking 。
目前vue3支持自动treeshaking(删除冗余没有用到的相关代码)
总结分析
常见的一些提高性能的优化方案包括:
- 采用路由懒加载
- 将一些静态 js css 放到其他地方(如 OSS),减小服务器压力
- 按需加载三方资源,如 ant-design-vue,建议按需引入 ant-design-vue 中的组件
- nginx 容器开启 gzip 减小网络传输的流量大小
- webpack(打包的脚手架,也可以是vite) 开启 gzip 压缩
- 若首屏为登录页,可以做成多入口,登录页单独分离为一个入口
- 图片懒加载方案
优化维度–可访问性(Accessibility)
ARIA
Accessible Rich Internet Applications (ARIA) 无障碍技术规定了能够让 Web 内容和 Web 应用(特别是那些由 Ajax 和 JavaScript 开发的)对于残障人士更易使用的各种机制.
该技术的应用是无障碍阅读的工具,帮助改善无障碍阅读
NAMES AND LABELS
为FORM元素添加label标签,用于说明FORM元素的作用
优化维度–SEO(Search Engine Optimization)
可以点击查看SEO的一些简要介绍
- 网页添加 viewport,兼容移动端内容的展示
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
网页适应移动端浏览器,并且阻止用户的 300ms 的用户输入延迟
2. 网页添加 title 标题
3. 设置 meta description 描述,方便搜索引擎搜索
4. 关于 rel = “canonical” 这个标签,早在 2009 年 2 月,谷歌、雅虎和 live search 三家搜索引擎宣布支持 Link 的一个新属性 Canonical.主要是帮助搜索引擎解决网站内容存在多个版本,来制定规范的链接,解决内容重复的收录。
rel="canonical"标签,是为了解决网站由于网站 url 链接不一样但网页内容是一样而造成百度重复收录的问题,对于这样的情况,如果不采用百度该标签,将导致百度对两个相同的网页收录和排名的问题上不知情,久而久之,当网站存在大量这样的网页的时候,可能导致网站大量重复内容而被降权、不收录甚至被禁用