浅谈前端性能优化(webpack+csr)

前言

我们要优化一个网站的性能,首先需要学会如何衡量一个网站的性能。

Google提出RAIL模型

RAIL 是一种以用户为中心的性能模型,它提供了一种考虑性能的结构。该模型将用户体验分解到按键操作(例如,点击、滚动、加载)中,帮助您为每个操作定义性能目标。

基于此模型,Google提出

用户为中心的性能指标

  • First Paint 首次绘制(FP)
  • First contentful paint 首次内容绘制 (FCP)
  • Largest contentful paint 最大内容绘制 (LCP)
  • First input delay 首次输入延迟 (FID)
  • Time to Interactive 可交互时间 (TTI)
  • Total blocking time 总阻塞时间 (TBT)
  • Cumulative layout shift 累积布局偏移 (CLS)

性能指标

FP&FCP&LCP

首次绘制,FP(First Paint),这个指标用于记录页面第一次绘制像素的时间。

首次内容绘制,FCP(First Contentful Paint),这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。

FP 指的是绘制像素,比如说页面的背景色是灰色的,那么在显示灰色背景时就记录下了 FP 指标。但是此时 DOM 内容还没开始绘制,可能需要文件下载、解析等过程,只有当 DOM 内容发生变化才会触发,比如说渲染出了一段文字,此时就会记录下 FCP 指标。因此说我们可以把这两个指标认为是和白屏时间相关的指标,所以肯定是最快越好。

最大内容绘制LCP(Largest Contentful Paint),用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。指标变化如下图: LCP在2.5s内完成算优秀。

TTI

首次可交互时间,TTI(Time to Interactive)。这个指标计算过程略微复杂,它需要满足以下几个条件

  1. 从 FCP 指标后开始计算
  2. 沿时间轴搜索时长至少为 5 秒的安静窗口(空闲时间段),其中,安静窗口 的定义为:没有长任务且不超过两个正在处理的网络 GET 请求。
  3. 沿时间轴反向搜索安静窗口之前的最后一个长任务,如果没有找到长任务,则在 FCP 步骤停止执行。
  4. 长任务的结束时间点,即为TTI。

FID

首次输入延迟,FID(First Input Delay),记录在 FCP 和 TTI 之间用户首次与页面交互时响应的延迟。

这个指标其实挺好理解,就是看用户交互事件触发到页面响应中间耗时多少,如果其中有长任务发生的话那么势必会造成响应时间变长。

值得一提的是,FID 是一项实际指标,数据的好坏取决于用户在何时与页面发生交互。但是,Total Blocking Time 总阻塞时间 (TBT)指标不仅可以进行实验室测量,还与实际的 FID 关联性强,而且可以捕获影响交互性的问题。能够在实验室中改进 TBT 的优化也应该能为您的用户改进 FID。

TBT

总阻塞时间(TBT) 指标测量First Contentful Paint 首次内容绘制 (FCP)Time to Interactive 可交互时间 (TTI)之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应。

某个给定长任务的阻塞时间 是该任务持续时间超过 50 毫秒的部分。一个页面的总阻塞时间 是在 FCP 和 TTI 之间发生的每个长任务的阻塞时间总和。下图TBT 为345ms

CLS

累积布局偏移CLS 测量整个页面生命周期 内发生的所有意外布局偏移中最大一连串的布局偏移分数

CLS 在0.1内算优秀。

核心指标

Google提出三大核心指标为 LCP、FID、CLS

性能分析工具

LightHouse

lighthouse是Chrome高版本集成的页面性能评分工具,可以看到所有性能指标的对应数据,以及一些修改的建议。

web-vitals

web-vitals google 官方出品,用来采集页面性能指标相关数据,sentry 的页面性能收据收集就是依赖这个库的。

优化措施

我们从浏览器加载页面的完整流程来逐步分析,有哪些可用的优化手段来提升页面性能指标。

使用HTTP2

http2 有以下优点:

解析速度快

服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。

多路复用

HTTP1.1 如果要同时发起多个请求,就得建立多个 TCP 连接,因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。

在 HTTP2 上,多个请求可以共用一个 TCP 连接,这称为多路复用。同一个请求和响应用一个流来表示,并有唯一的流 ID 来标识。 多个请求和响应在 TCP 连接中可以乱序发送,到达目的地后再通过流 ID 重新组建。

首部压缩

HTTP2 提供了首部压缩功能。

优先级

HTTP2 可以对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后,可以优先处理。

流量控制

由于一个 TCP 连接流量带宽(根据客户端到服务器的网络带宽而定)是固定的,当有多个请求并发时,一个请求占的流量多,另一个请求占的流量就会少。流量控制可以对不同的流的流量进行精确控制。

使用服务端渲染

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。

  • 优点:首屏渲染快,SEO 好。
  • 缺点:配置麻烦,增加了服务器的计算压力。

静态资源使用CDN

内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,通过负载均衡,让用户访问离自己最近服务器,从而缩短请求时间。

js脚本放在body的后面,样式文件放在<head>

css 文件和js文件都会阻止DOM的解析构建,但是为了能够尽早展示良好的页面,css要先加载解析,然后构建body下的dom,然后是页面js的加载解析执行。

但是对于CSR(react,vue)的页面,body 内一般只有一个空的容器div,并没有太大的效果。

减少页面初始js和css的体积

因为css 文件和js文件都会阻止DOM的解析构建,文件越大,其下载解析构建的时间就越长,从而影响FP/FCP/LCP等指标,js 体积大还会导致TBT/TTI 变大,从而导致FID 变大。

如何减少css体积:

  • 压缩css
  • 考虑使用原子化class方案(tailwind css)

如何减少js体积:

  • 压缩js
  • code Spliting
  • 开启tree shaking
  • 使用webpack-bundle-analyzer去分析打包后的代码

开发环境下配置:new BundleAnalyzerPlugin({ openAnalyzer: false, analyzerPort: 8001 })

生产环境下配置:new BundleAnalyzerPlugin({ analyzerMode:'static' })

延迟加载非关键路径的js

某些第三方js库,如果在首屏渲染路径上不会用到,那么就可以使用defer或者async来做异步加载。有先后依赖关系就是用defer,比较独立的使用async。

页面资源缓存

解析HTML过程中会遇到css和js文件,然后浏览器会先查看资源有没有被缓存过,以及资源有没有过期,如果存在没过期的已缓存文件,浏览器将直接使用,从而省去建立连接、下载资源的时间。对于首屏渲染速度提升有很大帮助。

优化手段:

  • 通过webpack splitChunks 将不常变化的第三方库抽成独立的venderChunk,这样即使页面频繁迭代,venderChunk也可以被较大概率的被复用。
  • webpack externals + cdn scripts 的方式分离出React等不常变化的基础库

页面渲染阶段

优化FID

如何减少TBT(解决长任务):

  • 通过lightHouse 分析页面,观察长任务分布情况,如果长任务是js脚本的解析编译执行,那么就要优化js的体积,缩短解析编译执行的时间。
  • 如果是复杂任务,考虑使用webWorker或者使用相关的分割长任务的库,原理是在浏览器空闲的时候去分片执行任务。
  • 最大限度减少未使用的 polyfill
    • 如果您在使用 Babel 转译器,请使用@babel/preset-env来只将您计划定位的浏览器所需的 polyfill 包括在其中。对于 Babel 7.9,启用bugfixes选项来进一步精简任何不需要的 polyfill
    • 使用模块/非模块模式来交付两个单独的资源包(@babel/preset-env也能够通过target.esmodules支持这一操作)
html 复制代码
    <script type="module" src="modern.js"></script>
    <script nomodule src="legacy.js" defer></script>
相关推荐
像风一样自由202028 分钟前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem1 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊1 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术1 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing1 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止2 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall2 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴2 小时前
简单入门Python装饰器
前端·python
袁煦丞3 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作
天天扭码3 小时前
从图片到语音:我是如何用两大模型API打造沉浸式英语学习工具的
前端·人工智能·github