原文

Front-End Performance Checklist 2021

我们需要知道目前我们的项目在性能等级中处于什么等级,以及我们的性能瓶颈是什么。我们是否已经通过 tree-shaking(删除 js 文件中未引用的代码),scope hoisting(作用域提升),Code Splitting 以及所有能够想象到的载入模式比如 intersection observer(提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法)、progressive hydration(渐进式注水)、Client Hints(HTTP 客户端(通常可以认为是浏览器)能够主动将一些特性告诉服务端,以便服务端更有针对性地输出内容)、HTTP/3、service workers 做了充分的优化。最重要的是,我们要从哪里着手开始做性能优化,如何建立长期性能优化的氛围。

在过去,性能优化通常是在开发完成之后才想到的,通常延迟到项目的后期,它可以归结为源码压缩、连接、静态资源优化和对服务器配置文件的一些潜在的微调,但现在性能不仅仅是一个技术问题: 它影响到从可访问性到可用性到搜索引擎优化的所有方面,当将其融入工作流程时,设计决策必须考虑到它们对性能的影响。性能必须被不断地测量、监控和改进,网络的日益增长的复杂性提出了新的挑战,使得跟踪衡量标准变得更加困难,因为指标将发生显著的变化取决于设备,浏览器,协议,网络类型和延迟(发布商、isp、缓存、代理、防火墙、负载平衡器和服务器所有的方式)。

所以,如果我们对在 coding 时提高性能必须做的所有事情做一个概括的话,从项目一开始到最终发布,会是怎样一个流程,以下是 2021 年 front-end checkList.

计划和度量

关键词:性能文化,核心 Web 要素,性能选项,难点,Lighthouse,FID,TTI,CLS,设备

关键词:性能预算,性能目标,RAIL 框架,170KB/30KB

在保持性能方面微优化是非常重要的,但是对于性能有一个明确的目标是更加重要的,这个目标是可衡量的,可以影响整个过程中的任何决定。以下讨论的几种是相当经典的,只需要确保优先级即可。

在团队内培养性能文化的氛围

在很多部门内部,前端开发人员明确知道什么是常见的性能问题,以及该如何解决这些问题。然而,如果一个部门内部没有性能文化的氛围,那么相关的性能文化决策就很难被推行。所以如果想在部门内有一个良好的性能优化的氛围,可以提供一个案例,尤其是关于速度方面的。

比如,为了使性能现状更加具体,你可以通过展示应用加载时间与用户转换率的关系来说明性能的影响。

如果开发/设计团队和业务/营销团队之间没有比较好的平衡的话,性能将无法被长期维持。研究分析用户和业务方提出的一些常见问题,分析高跳出率和转化率下降的原因。探索如何提高性能可以帮助解决这些问题。

在移动设备和桌面设备上进行性能实验和测量,它将帮助你用真实的数据建立一个案例研究。除此之外,使用 WPO 统计数据发表的案例和实验数据,有助于提高企业对于性能重要性的敏感度,以及它对用户体验和业务指标的影响。不过,仅仅说明性能本身是不够的,还需要建立一些可衡量和可跟踪的目标,并且逐步观察它们。

就像用户体验是一系列用户体验一样,应用性能也是一系列的内容所组成的,并非是一个数字或者一个评级就可以描述的。因此,性能优化目标需要是粒度的、可追踪的并且是切实可以完成的。

可参考资料:

  1. 如何长期的保持性能优化
  2. 高效性能团队的习惯
  3. How Fast Should Your Site Load
  4. 性能与用户

比你最快的竞争对手至少快 20%

摘要:

  1. 在能够代表用户的设备上收集数据。首选真实设备而不是模拟。
  2. 选择 Moto G4 / G5 Plus,中档三星设备(Galaxy A50,S8),出色的中端设备(如 Nexus 5X,小米 Mi A3 或小米 Redmi Note 7)和速度较慢的设备 Alcatel 1X 或 Cubot X19。
  3. 或者,通过在节流的 CPU(速度降低 5 倍)下对节流的网络(例如 300ms RTT,1.6 Mbps 降速,0.8 Mbps 向上)进行测试,从而在台式机上模拟移动体验。
  4. 然后切换到常规 3G,慢速 4G(例如 170ms RTT,降低 9 Mbps,提高 9 Mbps)和 Wi-Fi。
  5. 收集数据,设置电子表格,削减 20%并设置目标(性能目标)

如果你想让用户觉得自己的应用比竞争对手更快,那么至少需要快到 20%。研究主要的竞争对手,收集他们应用各种表现指标,并设定阈值,以帮助你超越他们。为了得到准确的结果和目标,首先要确保通过学习分析,对用户的体验有一个全面的了解。然后通过测试模拟。

可以通过一系列的工具进行对比,或者使用工具来逐步的分析项目中的性能现状和瓶颈

但是在部门内部,性能优化计划不应该是空想的,而应该是务实的,作为一个控制指标,防止应用超过临界点。在这种情况下,可以选择现有数据中最差的数据点作为阈值,然后从那里开始做计划。

同时,制定绩效计划和当前性能现状,可以借助一些图表报告类的工具。

另外,性能意识不应该仅仅来自性能计划,就像 Pinterest 一样,你可以创建一个自定义的 eslint 规则,这个规则不允许从依赖性很强并且会使包膨胀的文件和目录导入内容,并建立一个能够导入的包的清单。除此之外,研究、讨论一些重要的业务可接受的时间临界点,建立各业务的关键指标阈值,根据可接受的指标阈值来进行未来的工作安排,确保增加的资源和特性的额外成本是可见的和可理解的。

将性能方面的工作与其它技术开发相关工作的计划结合起来,不管是正开发产品的新特性还是重构,在每一次开发相关的工作讨论中,性能也是需要讨论的一部分。

尽早进行一些快速能达成的优化成功可能很有吸引力,但是如果没有一个计划的话,很难在未来的业务发展中保持性能优先级

可参考资料:

  1. 谷歌数据洞察
  2. 使用 Chrome UX 报告查看真实性能
  3. 使用 Treo 检测性能
  4. Speed Scorecard
  5. 用户体验测试比较
  6. SiteSpeed CI
  7. UX 速度计算器
  8. UX 速度计算器介绍
  9. 按照关键页面类型分解性能,并跟踪不同的关键指标
  10. lighthouse 进行性能分析
  11. lighthouse 进行性能分析
  12. 使用 lightwallet 整合 lighthouse 的性能计划
  13. 一段时间内性能分布的概述
  14. 性能模拟器,强有力的说服进行性能优化的依据
  15. 网站重要页面关键指标分析
  16. 数据竞争分析
  17. 新的度量标准在 lighthouse 中的预览

选择正确的指标

研究什么度量指标对你的应用程序是最重要的,它将由你可以多久开始渲染你的界面的最重要的指标和你多久可以为这些渲染的指标提供响应来决定。这些信息可以为你提供持续可努力的最佳优化目标。最后,定义体验和性能的不是加载事件或服务器响应时间,而是对界面是否有流畅、丝滑的感觉。

这意味着与其关注整个页面的加载事件,不如根据用户的感知优先加载页面,这也意味着没有标准的正确指标,最好的是从现状出发去选择合适的指标度量。

传统的指标度量标准可以分为几组:

  1. 统计请求的数量,大小和性能得分。对于随着时间推移而提高的警报和监控变化很有用,但是对于理解用户体验则不那么有用
  2. 里程碑指标:比如说 TTFB、TTI,用于描述用户体验和监控,而不是用于知道这些指标之间发生了什么
  3. 渲染指标:比如 SI,适用于测量和调整渲染性能,但不太适用于测量何时内容出现和可以互动
  4. 自定义度量:用于精确的描述用户体验,但不适合扩展度量标准

为了完成整个性能优化过程,我们通常会在以上这些组里面寻找有用的指标,通常,最具体和相关的是:

  1. Time to Interactive (TTI):用户可以与界面进行交互的时间标记。主要用于了解用户在使用应用时需要经历多少等待时间的关键指标。它测量的是根据条件确定一个交互性能以令人满意的方式发生所需的时间。
  2. First Input Delay (FID):交互响应时间,可以很好的补充 TTI
  3. Largest Contentful Paint (LCP):最大元素首次渲染时间,假定页面加载时最大的元素也是最重要的元素,这个时间也将成为考量的指标之一。当这个元素为可见部分时。
  4. Total Blocking Time (TBT):总的阻塞时间,一个有助于量化 在页面成为可靠的交互之前,也就是说主线程已经没有任何运行超过 50ms 的任务。该指标度量从 FP 到 TTI 之间,主线程被阻塞了多长的时间以影响页面的响应。
  5. Cumulative Layout Shift (CLS):累计布局偏移。这个指标强调了用户在访问网站时遇到意料之外的布局变化的频率,它检查不稳定元素对整体体验的影响,分数越低越好。
  6. Speed Index:速度指数,测量页面内容在视觉上的填充速度,得分越低越好。速度指数的得分基于视觉进程的速度计算的,但它只是一个计算值,对于视口的大小也比较敏感。计算 SI 需要限定与目标受众相关的测试设备。
  7. CPU 时间消耗:显示主线程阻塞的频率和时间,用于 绘制、渲染、脚本和加载。如果用户的操作和响应之间存在明显的延迟,则可以使用 WebPageTest 来进行测试。
  8. 组件级 CPU 成本:通过了解组件所消耗的 CPU 成本,来针对性的给出解决方案
  9. FrustrationIndex:沮丧指数,上面许多指标解释了特定事件发生的时间,而这个指标则着眼于各个里程碑之间的间隔,而不是独立的去衡量他们
  10. Ad Weight Impact:广告占比的影响
  11. Deviation metrics:偏差指标,关注每次测试所体现的差异性数据,以及应该对偏差和异常值给予多大的关注。
  12. Custom metrics:自定义指标,根据特定的业务需求和客户体验所定义的。

需要注意的是,虽然 TTI 是在 实验室环境下运行 audits 来获得的,但 FID 代表的是真实用户体验,因此实际用户在体验的时候会明显感觉比测试中的慢,总而言之,始终测量和跟踪这两个指标是有利的。

同时,根据您的业务类型的不同,关注的首选指标可能会有所不同,举两个例子:对于 Netflix TV UI,输入响应性,内存使用率和 TTI 更为关键,而对于 Wikipedia 而言,最先/最后的视觉变化时间点和 CPU 响应时间指标则更为重要。

注意: FID 和 TTI 都不考虑滚动行为; 滚动可以独立发生,因为它是非主线程的,所以对于许多内容消费网站来说,这些指标可能不那么重要

可参考资料:

  1. 衡量渲染指标
  2. 如何可靠的测量 TTI
  3. 用于测量 FID 的 js 库
  4. webPageTest 使用
  5. 衡量 FrustrationIndex
  6. 以用户为中心的性能指标
  7. 关键 Web 指标

测量和优化关键 Web 指标

很长一段时间,性能指标都是技术化的,主要关注服务器响应速度和浏览器加载速度。近年来,衡量标准已经发生了变化,开始试图找到一种方法来获取实际的用户体验,而不是通过服务器的计时。2020 年 5 月,谷歌发布关键性能指标,一个新的以用户为中心的性能指标,每一个指标代表用户体验的一个方面。

对于每一个指标,谷歌推荐了一个可接受的速度目标范围。至少 75%的页面浏览量应超过良好范围才能通过此评估。并且随着 关键 Web 指标将会在 2021 年 5 月成为谷歌搜索的排名影响因素之一,许多公司已经开始关注这些指标

接下来,对这些指标进行详细的解释,同时使用有用的技术和工具来优化体验。


Largest Contentful Paint (LCP) < 2.5 s

LCP 指标测量页面的加载,并报告在视口中可见的最大图像或文本块的渲染时间。因此,LCP 会受到延迟重要信息渲染的所有因素的影响,比如说服务器响应速度慢,CSS 阻塞,JS 阻塞,web 字体加载,耗费性能的绘制和渲染,懒加载的图片,骨架屏或者客户端渲染等等。

一个好的用户体验,LCP 应该在页面第一次开始加载的 2.5 秒内发生。这意味着我们需要尽早渲染页面的第一个可见部分。

LCP 评分低的主要原因通常是图像。如果需要在 fast 3G 上提供小于 2.5s 的 LCP,基础环境是在一个优化的服务器上,所有的静态图像都不是由客服的渲染,并且图像来自于专用的 CDN,这意味着理论上的最大图像大小只有 144KB 左右,这就是为什么响应式图像很重要,以及 早期要使用 preload 预加载关键图像

可以在 performance 面板中将鼠标停留在 LCP 的 标志上以查看 LCP 的内容。

First Input Delay (FID) < 100ms

交互响应时间。衡量 UI 的响应能力,即浏览器在对一个离散的用户输入时间(如点击)做出反应之前,忙着处理其它任务所花费的时间。它的设计目的是统计由于主线程忙碌而导致的延迟,尤其是在页面加载期间。

我们的目标是 每次交互 保持在 50-100ms 以内,为了达到这个目标,我们需要做的是

  1. 识别出长任务(阻塞大于 50ms)并将其分解
  2. 进行 code-split
  3. 减少 js 执行时间
  4. 优化数据获取
  5. 延迟第三方脚本的执行
  6. 使用 Web workers 将 JavaScript 移动到后台线程
  7. 使用 progressive hydration 减少 SPA 中的渲染代价

获取 FID 分数的可靠策略是将主线程上的任务最小化,将较大的包拆分成较小的包,并在用户需要的时候提供相应的服务,这样用户交互就不会被延迟。

Cumulative Layout Shift (CLS) < 0.1

衡量 UI 的视觉稳定性以确保流畅和自然的交互,它统计的是所有发生在页面中意料之外的布局偏移分数的总和。当已经可见的元素改变其在页面上的位置时,就会发生单独的布局偏移。它的得分基于内容的大小和移动的距离。

因此每次出现改变时,比如说后备字体和网页字体有不同的字体指标,或者广告、嵌入或后来加入的 iframe,或者图像/视频尺寸没有被保留,或者后期 CSS 强制重新绘制,后者后期 JavaScript 注入了更改,这些都会对 CLS 得分产生影响。良好体验的推荐值是 CLS<0.1


关键性能指标将会随着周期时间而有所改变,对于第一年的更新,可能更加期待 FCP 被推广到 core Web Vitals 中,降低 FID 阈值,并且更好地支持单页应用程序。

与关键性能指标有关的参考资料:

  1. Web Vitals Leaderboard:在各种设备和网络状况下来比较自己应用的得分
  2. Core SERP Vitals:在谷歌搜索中显示 core web Vitals
  3. CLS 生成 GIFCLS 生成 GIF,也可以使用命令行
  4. web-vitals library:收集核心数据到谷歌分析
  5. 使用 WebPageTest 分析 Web Vitals
  6. 优化关键性能指标:一个视频,使用一个电商网站的案例强调如何提高关键性能指标
  7. Cumulative Layout Shift in Practice
  8. Cumulative Layout Shift in the Real World:这两篇文章几乎涵盖了关于 CLS 的所有内容以及它是如何和跳出率、愤怒的点击等相关联的。
  9. What Forces Reflow:当在 JavaScript 中请求/调用属性或方法时,它将触发浏览器同步计算样式和布局
  10. CSS Triggers:显示哪些属性触发了 Layout,Paint 和 Composite
  11. Fixing Layout Instability是一个使用 webPageTest 来识别和修复布局不稳定问题的演示。
  12. CLS 和布局不稳定性度量:另一个非常详细的指南,关于 CLS,如何计算,如何衡量和如何优化它。
  13. 如何提高关键性能指标:对每个指标(包括其他 Web 重要指标,如 FCP、TTI、TBT)的详细指南,它们何时出现以及如何度量。

需要注意的是,Core Web Vitals 并非是最终的衡量标准,至少,不完全是,它们确实已经出现在大多数的平台中,比如 PageSpeed Insights、 Lighthouse + CI、控制台等等,但是它的主要问题是缺乏跨浏览器支持,我们不能真正衡量用户体验的整个生命周期,而且很难将 FID 和 CLS 的变化与业务结果联系起来。

可参考资料:

  1. 谷歌发布的 关键性能指标
  2. 关键性能指标背后的科学性
  3. 关键性能指标影响谷歌搜索排名
  4. LCP 分数解析
  5. FID 分数解析
  6. CLS 分数解析

在具有用户代表性的设备上收集数据

为了收集准确的数据,我们需要仔细的选择需要测试的设备。对于大部分公司来说,这意味着研究分析并基于最常见的设备类型创建用户配置文件。然而大部分情况下,单独的分析无法提供一个完整的场景。很大一部分目标用户可能会因为体验太慢而放弃该网站并且不再返回,而他们的设备也不太可能因此成为分析中最受欢迎的设备。所以,在目标群体中去选择一个常用的设备是更好的。

根据 IDC 的数据,2020 年全球 84.8% 的移动电话是安卓设备。普通消费者每两年升级一次手机,而在美国手机更换周期是 33 个月。全球最畅销的手机平均售价将低于 200 美元。

一个比较典型的安卓设备:至少使用了两年,成本价 200 美元或者更少,运行缓慢的 3G,RTT 在 400ms 左右和约 400kb 的传输(悲观一点)(可能跟中国没有那么符合)

对于设备的选择,建议是高中低档各选取一个典型的代表,同时检查各个设备中的芯片组,选择具有代表性的芯片组设备

如果没有足够多的设备,可以通过在桌面上模拟移动体验来达到类似效果并进行多次的对比测量

与 PC 相比,在移动设备上,我们要有 4 到 5 倍减速的心理预期。移动设备具有不同的 gpu,cpu,内存和不同的电池特性。这也是为什么有一个良好的配置文件和总是在典型的设备上测试时非常重要的。

幸运的是,有很多选择可以帮助自动收集这些数据,并且根据这些数据来衡量一段时间内的表现。一个好的性能图涵盖了一系列的性能指标,实验室数据和真实数据。

  1. 收集:在一个可复制的环境中,使用预先设定的设备和网络
  2. 真实的用户指标监测工具:不断地评估用户交互,不断收集

前者在开发过程中特别有用,因为它可以帮助您在开发产品时识别、隔离和修复性能问题。后者对于长期维护非常有用,因为它将帮助您了解性能瓶颈,因为这些瓶颈正在实时发生——当用户真正访问站点时。

可以通过使用内置的 RUM api 进行监控,比如 Navigation Timing, Resource Timing, Paint Timing, Long Tasks, 等等。也可以使用 Calibre, Treo, SpeedCurve, mPulse 和 Boomerang, Sitespeed.io,都是性能监控好的选择。

注意:选择浏览器以外的网络状态设置工具是一个比较好的选择(DevTools 与 HTTP/2 push 交互存在问题),对于 Mac OS,我们可以使用 Network Link conditions,Windows Traffic Shaper,Linux netem,以及 FreeBSD dummynet。

因为你很可能会在 Lighthouse 中进行测试,所以请记住你可以:

  1. 使用 Lighthouse CI长期的追踪性能评分
  2. 在每次 commit 的时候执行 Lighthouse
  3. 在网站的每一个页面上执行 Lighthouse
  4. 使用 Lighthouse 分数计算器:如果需要了解更多的细节时
  5. 在火狐浏览器上同样是可用的:它使用了 PageSpeed Insights API 和 基于无头 Chrome 79 用户代理生成报告.

可参考资料:

  1. PC 测试移动页面比较:预期有 4 到 5 倍的减速
  2. lab data and field data
  3. Navigation Timing API
  4. Resource Timing API
  5. Paint Timing API
  6. Long Tasks API
  7. 通过 Lighthouse 对性能指标持续监测和保存

在测试时使用干净的网站配置,排除变量

在我们进行测试的时候,关闭一切可能影响到性能测试的任务或者统一测试环境,关闭所有的浏览器扩展,使用干净的用户配置文件进行测试

不过,研究一下目标用户经常使用的浏览器扩展,并用专门的用户配置文件进行测试也是一个好主意,除了排除变量得到理想数据之外,也可以添加变量模拟真实情况。干净的变量本身就是极其乐观情况下才存在。

可参考资料:

  1. 扩展对网站测试的影响

与同事共担性能优化目标

确保团队的每个成员都熟悉性能优化目标,以避免有误解。每一个决定,无论是设计还是开发,或是介于两者之间的任何决定,并将责任和所有权分配给整个团队。

设定现实改进目标

100ms 的响应时间,60fps

为了让交互感觉更加流畅,页面有 100ms 来反应用户的输入事件。超过这个时间,用户就会感觉应用是滞后的。以用户为中心的性能模型 RAIL 提供了一个应用健康的目标:为了达到<100 毫秒的响应,页面必须在每隔<50 毫秒后将控制权交还给主线程。Estimated Input Latency这篇文章告诉我们是否达到了这个临界点,理想情况下,它应该小于 50ms。对于像动画这样的高压点,最好在你能做的地方什么也不做,在你不能做的地方绝对最小。

此外,每一帧动画应该在 16ms 以内完成,从而达到 60 帧率/s(1s/60 = 16.6 ms),即最好在 10ms 以内。因为浏览器需要时间在屏幕上绘制新的帧,所以你的代码应该在 16.6ms 之前完成执行。目前已经开始讨论 120 帧/s(比如 ipad 的屏幕是 120hz),Surma已经介绍了一些 120 帧/s 的渲染性能解决方案,但这还不是我们目前需要考虑的目标。

在性能预期上要悲观,但在接口设计上要乐观,明智地使用空闲时间(检查 idlize、 idle-until-urgent 和 react-idle)。显然,这些目标适用于运行时性能,而不是加载性能。

可参考资料:

  1. 以用户为中心的性能模型

FID < 100ms, LCP < 2.5s, TTI < 5s on 3G, 关键文件大小 < 170KB

虽然这可能很难实现,但一个好的最终目标是 TTI 在 5s 以下,对于重复性的访问,目标在 2s 以下。还有就是 LCP 在 2.5s 以下,TBT 和 CLS 的指标尽量最小化,一个可接受的 FID 在 100ms 到 70ms。正如上面提到的,我们考虑的基准是 200 美元左右的安卓手机,在 3G 网络下,模拟 400msRTT 和 400kbps 的传输速度。

另一方面,由于 JavaScript 解析和执行时间,我们队内存和 CPU 有硬件约束。为了达到第一阶段所述的目标,我们必须考虑 JavaScript 的关键大小预算。关于预算有多少其实有不同的看法,但是 JavaScript gzipped 的 170kb 预算在中端手机上解析和编译已经需要多达 1s。假设 179Kb 在减压时扩展到 3x 的大小(约 0.7M),这个用户体验其实已经非常差了。

以 wiki 的网站为例,在 2020 你,全球范围内,维基百科用户的代码执行速度提高的 19。所以,如果你的网络表现指标相对稳定,这其实是一个警告信号,因为随着环境不断改善,你其实正在倒退。

事实上,谷歌的 ALex Russell 建议将 130-170Kb 作为一个合理的上限。在现实场景中,大多数产品其实都是没有达到的:目前打包的平均大小在 452KB,与 2015 年初相比增加了 53.6%。在中产阶级的移动设备上,这占用了 12-20s 的交互时间

我们也可以超过打包的预算。比如,我们可以根据浏览器主线程的活动来设置性能预算,比如在开始渲染之前的绘制时间,或者跟踪占用前端 CPU 的内容,像 Calibre, SpeedCurve 和 Bundlesize 这些工具可以帮助去检查预算,并且可以继承到构建过程中。

最后,性能预算不应该是一个固定值,根据网络状态的不同,性能预算也应该进行相应调整,但是无论如何,在较慢的连接上的有效负载都要昂贵的多

可参考资料:

  1. 对于延迟加载的路由,推荐的大小也小于 35kb
  2. PRPL-30 性能预算
  3. 智能手机 CPU 性能基准
  4. 性能预算也应该进行相应调整
  5. 170KB 预算分配
  6. 性能预算应根据一般移动设备的网络条件调整

定义环境

关键词:选择一个框架,基线性能成本,WebPack,依赖,CDN,前端架构,CSR,SSR,CSR+SSR,静态渲染,预编译,PRPL 模式

选择并配置构建工具

坚持使用你的构建环境,只要你得到了需要的结果,并且在你的构建过程上没有问题,就已经很好了。

在众多的构建工具中,Rollup 和 Snowpack 都获得了越来越多的关注,但 Webpack 似乎是最成熟的一个,它提供了几百个插件来优化你的构建的大小。关注 Webpack 路线图 2021

最近出现的一个最值得关注的的策略是使用 Next.js 和 Gatsby 中的 Webpack 进行细粒度分块,以最小化重复代码。默认情况下,对于不使用它的路由,如果仍在每个入口点共享的模块进行请求,随着下载的代码超过必要的数量,这最终会成为一种开销。通过 Next.js 中的粒度分块,我们可以使用服务器端的构建清单文件来确定不同入口点使用的输出块。

使用 SplitChunksPlugin,可以根据许多条件创建多个分割块,以防止跨多个路由获取重复的代码。这可以改善导航
期间的页面加载时间和缓存

学习和深入研究 Webpack 可能有些困难,以下是一些比较好的资源:

  1. Webpack 官方文档,还有Webpack — The Confusing BitsAn Annotated Webpack Config,都是比较好的学习 WebPack 的文档。
  2. Sean LarkinJeffrey发布了一些免费的课程,它们都是深入 Webpack 的很好的介绍。
  3. Webpack 基础是一个非常全面的 4h 课程。
  4. Webpack examples有数百个现成的 Webpack 配置,它们按主题和目的分类。额外的好处:还有一个 Webpack 配置器,可以生成一个基本配置文件。
  5. awesome-webpack 是一个有用的 Webpack 资源、库和工具的列表,包括 Angular、React 和框架无关项目的文章、视频、课程、书籍和示例。
  6. 使用 Webpack 文件快速构建的案例是 Etsy 的案例研究,研究团队从如何使用基于 requirejs 的 JavaScript 构建系统转换到使用 Webpack,以及他们如何优化他们的构建,平均在 4 分钟内管理超过 13200 个 asset 文件。
  7. Webpack 性能技巧 是 Ivan Akulov 写的一个非常棒的资源,它提供了许多关注性能的技巧,包括一些专门针对 Webpack 的技巧。

可参考资料:

  1. 不要过于追求新技术(不稳定的)的使用
  2. JS Size Reduction Across Routes
  3. Etsy

使用渐进增强作为默认值

尽管如此,在这么多年之后,保持渐进增强作为前端架构和部署的指导原则是安全的。首先设计和构建核心体验,然后使用功能强大的浏览器的高级功能增强体验,创建弹性体验。如果你的网站在一个较慢的机器上、在一个不理想的网络上、在一个糟糕的浏览器上、在一个糟糕的屏幕上运行得很快,那么它只会在一个较快的机器上、在一个不错的网络上、在一个好的浏览器上运行得更快。

事实上,通过自适应模块服务,我们似乎将渐进性增强带到另一个层次,即为低端设备提供“精简”核心体验,并为高端设备提供更复杂的功能。渐进式增强不太可能在短期内消失。

选择一个强有力的性能基准

有太多的未知因素影响着负载-网络,中央处理器热节流,缓存替换策略,第三方脚本,解析器阻塞模式,磁盘 i/o,IPC 延迟,浏览器扩展,防病毒软件和防火墙、后台 CPU 任务、硬件和内存约束、L2/L3 缓存的差异、字体渲染,图像渲染等等。随着性能瓶颈从服务器转移到客户端,作为开发人员,我们必须更详细地考虑所有这些未知因素。

由于 170KB 的预算已经包含了关键路径 HTML/CSS/JavaScript、路由器、状态管理、实用程序、框架和应用程序逻辑,我们必须彻底检查我们选择的框架的网络传输成本、解析/编译时间和运行时成本。幸运的是,在过去的几年中,我们已经看到浏览器解析和编译脚本的速度有了巨大的提高。然而 JavaScript 的执行仍然是主要的瓶颈,所以密切关注脚本的执行时间和网络是很有必要的。

Tim Kadlec 对现代框架的性能进行了非常棒的研究,并将其总结为 “JavaScript frameworks have a cost“ 一文。我们经常谈论独立框架的影响,但是正如 Tim 指出的,在实践中,使用多个框架并不罕见。也许是一个老版本的 jQuery 正在慢慢地迁移到一个现代的框架中,还有一些使用老版本的 Angular 的遗留应用程序。因此,探索 JavaScript 字节和 CPU 执行时间的累积成本更为合理,这些成本很容易使用户体验几乎无法使用,即使是在高端设备上。

一般来说,现代框架并没有优先考虑功能不那么强大的设备,因此在手机和桌面上的体验往往在性能方面有很大的不同。根据研究,拥有 React 或 Angular 的站点比其他站点在 CPU 上花费的时间更多(当然,这并不一定意味着 React 在 CPU 上比 Vue.js 更昂贵)。

Tim 认为,有一点是显而易见的: “如果你使用一个框架来构建你的网站,那么你在初始性能方面做出了权衡——即使是在最好的情况下。”

可参考资料:

  1. JavaScript CPU Time
  2. JavaScript Bytes

评估框架和依赖性

不是所有的项目都需要引入框架也不是单页应用程序的每个页面都需要加载框架。在 Netflix 的例子中,从客户端移除 React、几个库和相应的应用程序代码,总共减少了超过 200KB 的 JavaScript,导致 Netflix 在 注销登录上的交互时间减少了将近 50%,然后,团队利用用户在登录页面上花费的时间来预获取用户可能登陆的后续页面的响应(请阅读详细信息)。

所以,如果你完全删除关键页面上的一个现有框架又会怎样呢?通过 Gatsby,您可以检查 Gatsby-plugin-no-JavaScript,它可以从静态 HTML 文件中删除 Gatsby 创建的所有 JavaScript 文件。在 Vercel 上,您还可以在某些页面(实验性的)的生产中禁用运行时 JavaScript。

一旦选择了一个框架,我们将使用它至少几年,所以如果我们需要使用一个框架,我们需要确保我们的选择是有依据的考虑周到的,特别是对于我们关心的关键性能指标。

数据显示,默认情况下,使用框架的代价是很昂贵的: 58.6%的 React 页面使用了超过 1mb 的 JavaScript,只有 36%的 vue 页面加载时的 FCP < 1.6ms。根据 Ankur Sethi:你的 React 应用程序的加载速度永远不会超过 1.1s,你的 Angular 应用程序启动起码需要 2.7s,你的 Vue 应用程序的用户至少需要等待 1s 才能开始使用。

当然,让 spa 变得快速是可能的,但它们并不是现成的快速,所以我们需要考虑制作和保持 spa 快速所需的时间和精力。尽早选择一个轻量级的基线性能成本可能会更容易。

那么我们如何选择一个框架呢?在选择一个选项之前,至少考虑大小和初始执行时间的总成本是一个好主意;诸如 Preact、Inferno、Vue、slimte、Alpine 或 Polymer 等轻量级的选择都可以很好地完成这项工作。基线的大小将定义应用程序代码的约束条件。

正如 Seb Markbage 所指出的,衡量框架启动成本的一个好方法是首先渲染一个视图,然后删除它,然后再次渲染,因为它可以告诉你框架是如何扩展的。第一次渲染往往会预热一堆延迟编译的代码,当树变大时,可以从中受益。第二次渲染基本上模拟了页面上的代码重用如何随着页面复杂性的增加而影响性能指标。

你可以在 Sacha Greif 的 12-point scale system上通过探索特性、可访问性、稳定性、性能、包生态系统、社区、学习曲线、文档、工具、跟踪记录、团队、兼容性、安全性等来评估你的候选(或任何 JavaScript 库)。

您也可以依靠在较长一段时间内从网上收集的数据。例如,大规模数据跟踪的性能跟踪框架 Perf Track,展示 Angular, React, Vue, Polymer, Preact, Ember, Svelte 和 AMP 的关键性能指标。你甚至可以指定比较通过 Gatsby、 Next.js 或 Create React App 构建的网站的区别。

一个好的起点是为您的应用程序选择一个好的默认堆栈。Gatsby (React)、Next.js (React)、Vuepress (Vue)、Preact CLI 和 PWA Starter Kit 提供了合理的默认设置,用于在普通移动硬件上快速加载。另外,也可以看看 React 和 Angular 的特定于 web.dev 框架的性能指南

也许您可以采用一种稍微更新颖的方法来构建单页应用程序-Turbolinks,它是一个 15KB 的 javascript 库,使用 HTML 而不是 JSON 来渲染视图。因此,当你跟随一个链接时,turbollinks 会自动获取页面,交换,并合并,所有这些都不会导致整个页面加载的成本。您可以查看关于堆栈的简介完整文档

可参考资料:

  1. 通过大规模的数据跟踪框架的性能
  2. 热销手机的 CPU 和计算性能

客户端渲染还是服务端渲染?两种一起

那是一场相当激烈的谈话。最终的方法是设置某种渐进式启动:使用服务器端 render 来获得快速的第一个有内容的绘制,但也包含一些必要的 JavaScript,使交互时间接近第一个有内容的绘制。如果 JavaScript 在 FCP 之后出现得太晚,浏览器将在解析、编译和执行最新发现的 JavaScript 时锁定主线程从而限制站点或应用程序的交互性

为了避免这种情况,始终将函数的执行任务分解为单独的异步任务,并在可能的情况下使用 requestIdleCallback。考虑使用 WebPack 的动态导入,支持延迟加载 Ui 部分,避免加载、解析和编译成本增加,知道用户真正的需要它们为止。

如上所示,交互时间(TTI)告诉我们导航和交互的时间。具体来说,这个度量是通过在初始内容渲染后的前 5s 窗口来定义的,在这 5s 窗口中没有 JavaScript 任务需要超过 50ms。如果出现一个 50ms 的长任务。则重新开始搜索一个 5s 的窗口。因此,浏览器会首先假设它达到了交互式,再切换到停用模式,最终切换回可交互模式

一旦我们达到可交互状态,我们可以根据需要或者时间允许启动不必要的部分应用。不幸的是, 正如Paul Lewis所提到的, 框架通常没有向开发人员提供简单的优先级概念,因此使用大多数库和框架来实现渐进引导并不容易。

不过,我们正在朝着这个目标前进。现在我们有几个选择可以探索,Houssein Djirdeh 和 Jason Miller 在他们关于 Web 渲染的演讲和 Jason 和 Addy 关于现代前端架构的文章中对这些选项进行了很好的概述。下面的概述是基于他们出色的产出

  1. 完全使用 Server-Side Rendering(SSR)
    1. 在经典的 SSR(如 WordPress)中,所有请求都完全在服务器上处理,请求的内容为完成的 HTML 页面,浏览器可以立即渲染。因此 SSR-apps 不能真正的利用 DOM API。第一次有内容的绘制与交互时间的差距通常很小,当 HTML 到浏览器时,可以立即进行页面的渲染。这避免了在客户端和服务器之间额外的请求时间,因为它是在浏览器获得相应之前处理的。然而,我们最终会有更长的服务器响应时间,因为直到获取到第一个字节之前,我们都没有利用到现代应用程序的响应性和丰富的特性。
  2. 静态渲染
    1. 我们将产品构建为单个页面应用程序,但是所有页面都预先渲染为静态 HTML,并使用最少的 JavaScript 作为构建步骤。这意味着,使用静态渲染,我们可以提前为每个可能的 URL 生成单独的 HTML 文件,这不是很多应用程序能够负担得起的。但是,因为页面的 HTML 不必动态生成,所以我们可以始终快速地完成第一个字节。因此,我们可以快速显示登录页面,然后为后续页面预取 spa 框架。Netflix 采用了这种方法来减少加载和 TTI 时间。
  3. Server-Side Rendering 和 (Re)Hydration (通用渲染, SSR + CSR)
    1. 我们可以同时利用 SSR 和 CSR 的优势,通过 Hydration,从服务器返回的 HTML 页面还包含一个加载完成的客户端应用程序的脚本。理想情况下实现一个快速的 FCP,然后继续渲染和进行 hydration。不幸的是,这种情况很少发生,更常见的情况可能是页面看起来确实准备好了,但是它不能响应用户的输入,导致用户愤怒的放弃了。
    2. 有了 React,我们可以在像 Express 这样的节点服务器上使用 ReactDOMServer 模块,然后调用 renderToString 方法将顶级组件渲染为静态 HTML 字符串。
    3. 有了 Vue,我们可以使用 Vue-server-renderer 使用 renderToString 将 Vue 实例渲染成 HTML。
    4. 在 Angular 中,我们可以使用@nguniversal 将客户端请求转换为完全由服务器渲染的 HTML 页面。使用 Next.js (React)或 Nuxt.js (Vue)也可以实现完全的服务器渲染体验。
    5. 这种方法也有缺点。虽然,我们在提供更快的服务器端渲染的同时,也获得了客户端应用程序的完全灵活性,但我们最终也会在第一次绘制内容和交互时间之间有更长的间隔,并增加了第一次输入延迟,Rehydration 是非常昂贵的,通常这一策略本身并不够好,因为它会严重延迟互动时间。
  4. 流式服务器端渲染与 Progressive Hydration(SSR + CSR)
    1. 为了减少交互时间和第一个内容绘制之间的时间间隔,我们一次渲染多个请求,并在生成内容时将其分成块发送下去。因此,我们不必等待完整的 HTML 字符串才向浏览器发送内容,从而提高了第一个字节的时间。在 React 中,代替 renderToString(),我们可以使用 renderToNodeStream()来响应并将 HTML 分成块发送下来。在 Vue 中,我们可以使用 renderToStream(),它可以被 piped 和流化。有了 React,我们也可以使用异步渲染来实现这个目的
    2. 在客户端,我们不是一次性启动整个应用程序,而是逐步启动组件。应用程序的各个部分首先被分解成独立的脚本,并进行代码拆分,然后虚部进行 hydration(按照我们所定义的优先级)。事实上,我们可以先对关键成功进行 hydration,然后再对其余部分进行 hydration。客户端和服务器端渲染的角色可以根据每个组件的不同定义。然后我们还可以去推迟一些组件的 hydration,直到他们出现在视图中,或者在用户需要的时候,或者当浏览器空闲时。
    3. 对于 Vue, Markus Oberlehner 发表了一份关于减少 SSR 应用交互时间的指南,该指南在用户交互中使用 hydration(按照我们所定义的优先级)。事实上,我们可以先对关键成功进行,以及 Vue-lazy-hydration(按照我们所定义的优先级)。
  5. 同构渲染
    1. 随着 service workers 的使用,我们可以使用 streaming server rendering,然后让 service worker 在安装完成后为网站呈现 HTML,在这种情况下,service worker 会预渲染内容,并支持在同一会话中呈现新视图的 spa 风格导航。当您可以在服务器、客户端页面和 service worker 之间共享相同的模板和路由代码时,可以很好地工作。
  6. CSR 和预渲染
    1. 预渲染类似于服务器端渲染,但我们不是在服务器上动态渲染页面,而是在构建时将应用程序渲染为静态 HTML。虽然静态页面是完全交互式的,没有太多的客户端 JavaScript,但预渲染的工作方式不同。基本上,它在构建时将客户端应用程序的初始状态捕获为静态 HTML,而通过预渲染,应用程序必须在客户端启动,以便页面具有交互性。
    2. 使用 Next.js,我们可以通过预先渲染一个应用程序到静态 HTML 来使用静态 HTML 导出。在 Gatsby 中,一个使用 React 的开源静态站点生成器,在构建过程中使用 renderToStaticMarkup 方法而不是 renderToString 方法,主 JS 块被预加载,未来的路由被预获取,没有简单静态页面不需要的 DOM 属性。
    3. 对于 Vue,我们可以使用 Vuepress 来实现同样的目标。你也可以在 Webpack 中使用预渲染加载器。Navi 也提供了静态渲染。
    4. 结果是更好的时间进行第一个字节和第一个内容绘制,我们减少了时间之间的交互和第一个内容绘制之间的差距。如果我们期望内容会有很大的变化,我们就不能使用这种方法。另外,所有的 url 必须提前知道,以生成所有的页面。有些组件可能会使用预渲染,但如果我们需要一些动态的东西,我们就需要依赖应用来获取内容。
  7. 完全使用 客户端渲染 (CSR)
    1. 所有的逻辑、渲染和引导都在客户端完成。结果通常是在交互和第一个内容绘制之间存在巨大的时间差距。因此,应用程序经常会感到缓慢,因为整个应用程序必须在客户端启动来渲染任何东西。
    2. 由于 JavaScript 具有性能成本,随着应用程序中 JavaScript 数量的增长,积极的 code-splitting 和延迟 JavaScript 将是绝对必要的,以减少 JavaScript 的影响。对于这种情况,在不需要太多交互的情况下,服务器端渲染通常是一种更好的方法。如果这不是一个选择,考虑使用app shell 体系结构模型
    3. 一般来说,SSR 比 CSR 快。然而,CSR 仍然是很多应用程序的常见实现。

那么,客户端还是服务器端渲染?一般来说,将完全客户端框架的使用限制在绝对需要它们的页面上是一个好主意。对于高级应用程序,单独依赖服务器端呈现也不是一个好主意。如果做得不好,服务器呈现和客户端呈现都是一场灾难。

无论您是倾向于 CSR 还是 SSR,请确保您正在尽可能快地渲染重要的元素,并尽量减少渲染和交互时间之间的差距。如果页面变化不大,可以考虑预渲染,如果可以,可以推迟框架的启动。用服务器端呈现的方式将 HTML 流成块,并实现客户端渲染实现 progressive hydration。以及在可见性、交互性或空间时间进行 hydration 功能。

可参考资料:

  1. 同构渲染,在任何 3 个地方都具有相同的代码渲染: 在服务器上、在 DOM 中或在 service workers
  2. 客户端和服务器端渲染的选项范围
  3. progressive hydration,延迟加载不需要的部件、用户交互的负载或者空闲时间

我们可以提供多少静态的内容

无论你是在开发大型应用程序还是小型站点,都需要考虑哪些内容可以通过 CDN(即 JAM Stack)静态提供,而不是动态生成。即使您有数千种产品和数百个带有大量个性化选项的过滤器,您可能仍然希望静态地服务于关键的登录页面,并将这些页面从您所选择的框架中分离出来。

有很多静态站点生成器,它们生成的页面通常非常快。当我们可以提前构建的内容越多,而不是在请求时在服务器或客户机上生成页面视图,那么我们将获得更好的性能。

构建部分 hydration 和逐步增强的静态网站中,Markus Oberlehner 展示了如何使用静态网站生成器和 SPA 构建网站,同时实现渐进增强和最小的 JavaScript 包大小。Markus 使用 Eleventy 和 Preact 作为他的工具,并展示了如何设置这些工具,添加 partial hydration, lazy hydration,,客户端输入文件,为 Preact 配置 Babel 和从开始到结束用 Rollup 打包 Preact。

如今,随着 JAMStack 在大型站点上的使用,出现了一个新的性能考虑因素:构建时间。事实上,每次新部署都需要花费数分钟来构建数千个页面,所以Gatsby 的增量构建很有希望,它可以将构建时间提高 60 倍,与 WordPress、Contentful、Drupal、Netlify CMS 等流行的 CMS 解决方案集成在一起。

此外,Next.js 发布了增量静态生成,这允许我们在运行时添加新的静态页面,并在已有页面构建完成后更新它们,在流量到来时在后台重新渲染它们。

需要更轻量级的方法吗?在《Eleventy, Alpine and Tailwind: towards a lightweight Jamstack》中,Nicola Goutay 解释了 CSR、SSR 和一切之间的区别,并展示了如何使用一个更轻量级的方法,以及一个 GitHub repo,展示了这种方法的实践。

可参考资料:

  1. 使用 Next.js 进行增量静态生成功能

考虑使用 PRPL 模式和 app shell 体系结构

不同的框架会对性能产生不同的影响,需要不同的优化策略,因此您必须清楚地了解您所依赖的框架的所有具体细节。在构建 web 应用程序时,要研究 PRPL 模式和 app shell 体系结构。这个想法非常简单:将交互所需的代码推入初始路由以快速呈现,然后使用 service worker 进行缓存和预缓存资源,然后异步加载所需的路由。

可参考资料:

  1. PRPL 模式
  2. app shell 体系结构

是否对 API 性能进行优化

API 是应用程序的通信渠道,通过端点内部和第三方应用程序公开数据。在设计和构建 API 时,我们需要一个合理的协议来实现服务器和第三方请求之间的通信。REST 是一个良好的、合乎逻辑的选择:它定义了一组约束,开发人员遵循这些约束,使内容以高性能、可靠和可伸缩的方式进行访问。符合 REST 约束的 Web 服务称为 RESTful Web 服务。

与一个好的 HTTP 请求一样,当从 API 检索数据时,服务器响应中的任何延迟都会传播到最终用户,从而延迟渲染。当资源想要从 API 检索一些数据时,它将需要从相应的端点请求数据。一个组件渲染来自多个资源的数据,比如一篇带有评论的文章和每个评论中的作者照片,可能需要多次往返服务器以获取所有数据,然后才能渲染。此外,通过 REST 返回的数据量通常超过渲染 组件 所需的数据量。

如果许多资源需要来自 API 的数据,那么 API 可能会成为性能瓶颈。GraphQL 为这些问题提供了一个性能解决方案。从本质上讲,GraphQL 是 API 的查询语言,是通过使用为数据定义的类型系统执行查询的服务器端运行时。与 REST 不同的是,GraphQL 可以在单个请求中检索所有数据,响应将完全符合所需,而不会像 REST 那样出现过度或过低获取数据的情况。

此外,由于 GraphQL 使用模式(描述数据结构的元数据),它已经可以组织数据为首选的结构。所以,例如,使用GraphQL,我们可以删除用于处理状态管理的 JavaScript 代码,生成在客户机上运行更快的更清晰的应用程序代码。

GraphQL 参考资料:

  1. GraphQL 初识:为什么我们需要一种新的 API
  2. GraphQL 初识:API 设计的演变
  3. 设计一个 GraphQL 服务用于性能优化
  4. GraphQL 性能说明

可参考资料:

  1. REST 和 GraphQL 之间的不同

使用 AMP 还是 instant articles

根据你组织的优先级和策略,你可能想考虑使用谷歌的 AMP 或 Facebook 的 instant articles 或苹果的 Apple News。你可以在没有他们的情况下实现良好的性能,但 AMP 提供了一个可靠的性能框架与免费内容交付网络(CDN),而 instant articles 将提高你在 Facebook 上的能见度和性能。

对于用户来说,这些技术看似明显的好处是保证了性能,因此有时他们甚至更喜欢 AMP-/Apple News/Instant Pages-links,而不是“常规”页面和可能臃肿的页面。对于处理大量第三方内容的内容较多的网站,这些选项可能有助于显著提高渲染时间。

除非他们没有。例如,根据 Tim Kadlec 的说法,“AMP 文档往往比它们的同类文档更快,但它们并不一定意味着一个页面就是性能。从性能的角度来看,AMP 并不是最大的区别。

对网站所有者的好处是显而易见的:这些格式在各自的平台上的可发现性和增加在搜索引擎中的可见性。

至少以前是这样的。由于 AMP 不再是热门报道的必备条件,出版商可能会从 AMP 转移到传统的轨道上

不过,您也可以通过重用 AMPs 作为 PWA 的数据源来构建渐进式 web AMPs。缺点就是,显然,在一个封闭的环境中,开发者可以制作和维护自己内容的独立版本,而即时文章和苹果新闻则没有真正的 url。

明智选择您的 CDN

如上所述,取决于你有多少动态数据,你可能可以“外包”一些内容给一个静态站点生成器,将它推到一个 CDN,并从它提供一个静态版本,从而避免向服务器请求。事实上,其中一些生成器实际上是网站编译器,提供了许多开箱即用的自动优化。随着编译器不断添加优化,编译后的输出会越来越小,越来越快。

请注意,CDN 也可以提供(和卸载)动态内容。因此,限制 CDN assets 文件是不必要的。仔细检查 CDN 是否执行压缩和转换(例如图像优化和网络连接大小调整) ,它们是否为service worker提供支持,a/b 测试,以及网络连接端包括(在 CDN 网络连接组装静态和动态的页面部分(例如最接近用户的服务器) ,以及其他任务。另外,检查 CDN 是否支持通过 QUIC (HTTP/3)传输 HTTP

Katie Hempenius 写了一篇关于 CDN 的精彩指南,提供了关于如何选择一个好的 CDN,如何对其进行微调,以及在评估一个 CDN 时需要记住的所有小事情的见解。一般来说,尽可能积极地缓存内容并启用 CDN 性能特性(如 Brotli、 TLS 1.3、 HTTP/2 和 HTTP/3)是一个好主意。

注意: 基于 Patrick Meenan 和 Andy Davies 的研究,HTTP/2 的优先级在许多 CDN 上实际上被打破了,所以在选择 CDN 时要小心。Patrick 在他关于 HTTP/2 优化的演讲中有更多的细节。

当你需要选择一个 CDN 时,你可以使用这些比较网站与他们的功能的详细概述:

  1. CDN 比较:一个 CDN 比较矩阵
  2. CDNPerf:衡量了每天收集和分析超过 3 亿个测试来衡量 CDN 的查询速度,所有结果基于 用户的 RUM 数据,也检测了DNS 性能比较和云计算性能比较
  3. CDN Planet Guides:提供了针对特定主题的 cdn 概述,例如服务陈旧、清除、原始屏蔽、预拉取和压缩

可参考资料:

  1. CDNPerf 比较

静态资源优化

关键词:Brotli、AVIF、WebP、响应式图片、自适应媒体,视频压缩,网页字体,谷歌字体

使用 Brotli 进行文本压缩

2015 年,谷歌提出Brotli,这是一种新的开源无损数据格式,现在所有的现代浏览器都支持该格式。开源的 Brotli 库,为 Brotli 实现了一个编码器和解码器,为编码器有 11 个预定义的质量级别,更高的质量级别要求更多的 CPU,以换取更好的压缩比。较慢的压缩速度最终将导致更高的压缩率,然而 Brotli 解压速度仍然很快。值得注意的是,压缩级别为 4 的 Brotli 比 Gzip 更小,压缩速度更快

实际上,Brotli 似乎比 Gzip 更有效。选择和体验不同,但如果您的站点已经用 Gzip 进行了优化,您可能希望在缩小大小FCP 时间方面至少得到个位数的改进,最多得到两位数的改进。您还可以尝试使用 Brotli 在网站上做改进

只有当用户通过 HTTPS 访问网站时,浏览器才会接受 Brotli。Brotli 得到了广泛的支持,许多 cdn 支持它(Akamai, Netlify Edge, AWS, KeyCDN, fast(目前仅作为直通),Cloudflare, cn77),你甚至可以在还不支持它的 cdn 上启用 Brotli(使用 service worker)。

问题是,因为用 Brotli 压缩所有 assets 产生的成本很高,许多主机提供商不能在 最低级别上使用它,因为它产生了巨大开销。事实上,在最高的压缩级别,Brotli 非常慢,任何潜在的文件大小增加都可能被服务器在等待动态压缩 assets 时开始发送响应所花费的时间所抵消。(但如果您在构建期间有时间使用静态压缩,当然,更高的压缩设置是首选。)

不过,这种情况可能正在改变。Brotli 文件格式包括一个内置的静态字典,除了包含多种语言中的各种字符串外,它还支持对这些单词应用多种转换的选项,增加了它的通用性。Felix Hanau 发现了一种方法,通过使用“比默认值更专业的字典子集” ,并依靠 Content-Type 头部告诉压缩器是否应该使用 HTML、 JavaScript 或 CSS 的字典,来提高 5 到 9 级的压缩。结果是“在高压缩级别压缩 web 内容时,使用有限的字典使用方法,性能影响微不足道(CPU 增加 1% 到 3% ,而正常情况下为 12%)。”

除此之外,通过 Elena Kirilenko 的研究,我们可以利用之前的压缩工件实现快速有效的 Brotli 再压缩。根据 Elena 的说法,“一旦我们有了一个通过 Brotli 进行压缩的 assets,并且我们试图对动态内容进行动态压缩,这些内容类似于提前提供给我们的内容,我们就可以在压缩时间方面取得显著的改进。”

这种情况多久发生一次?例如,通过传递 JavaScript bundle 子集(例如,当部分代码已经缓存在客户端上或动态 bundle 服务于 WebBundles 时)。或者使用基于已知预先模板的动态 HTML,或者使用动态子设置的 WOFF2 字体。Elena 认为,去掉 10% 的内容后,压缩效率提高了 5.3% ,压缩速度提高了 39% ,去掉 50% 的内容后,压缩效率提高了 3.2% ,压缩速度提高了 26% 。

Brotli 压缩变得越来越好,所以如果你可以绕过动态压缩静态文件的成本,这绝对是值得的。不用说,Brotli 可以用于任何纯文本有效负载 HTML、CSS、SVG、JavaScript、JSON 等等。

注意:到 2021 年初,大约 60%的 HTTP 响应是在不使用基于文本的压缩的情况下发送的,其中 30.82%使用 Gzip 压缩,9.1%使用 Brotli 压缩(在移动设备和桌面设备上)。例如,23.4%的 Angular 页面没有被压缩(通过 gzip 或 Brotli)。然而,通常打开压缩是通过简单的配置来提高性能的最简单的可用方法之一。

这一战略?用 Brotli+Gzip 在最高级别预压缩静态 assets,用 Brotli 在级别 4 实时压缩(动态)HTML 确保服务器正确处理 Brotli 或 Gzip 的内容协商。

可参考资料:

  1. 比较各种压缩方法后的后端时间
  2. 使用改进的字典方法,我们可以在更高的压缩级别更快的压缩文件
  3. 文件压缩统计

我们是否使用自适应媒体加载和客户端提示?

对于那些占用大量媒体资源的网站,我们可以进一步使用自适应媒体加载(在这个例子中是 React + Next.js),为慢速网络和低内存设备提供轻体验,为快速网络和高内存设备提供全面体验。在 React 的上下文中,我们可以通过服务器上的客户端提示和客户端上的响应react-adaptive-hooks来实现它。

随着客户端提示的广泛采用,响应式图像的未来可能会发生戏剧性的变化。客户端提示是 HTTP 请求报头字段,例如 DPR, Viewport-Width, Width, Save-Data, Accept(指定图像格式参数)等。它们应该通知服务器用户的浏览器、屏幕、网络连接等细节。

因此,服务器可以决定如何用适当大小的图像填充布局,并仅以所需的格式提供这些图像。通过客户机提示,我们将资源选择从 HTML 标记转移到客户端和服务器之间的请求-响应协商中。

正如 Ilya Grigorik 之前指出的那样,client hints 并不能替代响应图像。<picture>元素在 HTML 标记中提供了必要的艺术方向控制。client hints 提供了用于支持资源选择自动化的结果图像请求的注释。Service Worker 在客户机上提供完整的请求和响应管理功能。

例如,service worker 可以向请求添加新的客户端提示头值,重写 URL 并将图像请求指向 CDN,根据连接性和用户首选项调整响应,等等。它不仅适用于图像资源,而且适用于几乎所有其他请求。

对于支持 client hints 的客户端,可以测量出图像节省 42% 的字节,70% 以上节省 1MB+或者更少字节。在 Smashing Magazine 上,我们也可以测量出 19-32% 的改进。基于 chromium 的浏览器支持 client hints,但 Firefox 仍在考虑中。

但是,如果你同时为 client hints 提供正常的响应图像标记和<meta>标记,那么支持浏览器将评估响应图像标记并使用 client hints HTTP 头请求适当的图像源。

可参考资料:

  1. 自适应媒体加载

我们是否使用响应式图像作为背景图像

当然!我们应该使用 image-set,现在已经支持 Safari 14大部分的浏览器除了火狐,我们也可以做一些兼容来使用响应式背景图片。

background-image: url("fallback.jpg");
background-image: image-set(
"photo-small.jpg" 1x,
"photo-large.jpg" 2x,
"photo-print.jpg" 600dpi
);

基本上,我们可以有条件地使用 1x 的低分辨率背景图像,使用 2x 的高分辨率图像,甚至使用 600dpi 的打印质量图像。不过要注意: 浏览器并没有提供任何关于背景图片的特殊信息,所以理想的情况下,这些照片只是装饰辅具。

我们是否应该使用 WebP

图像压缩通常被认为是一种快速取胜的方法,但它在实践中仍然未被大量使用。当然,图像不会阻碍渲染,但它们会导致较差的 LCP 分数,而且对于它们所消耗的设备来说,它们往往过于沉重和过大。

所以至少,我们可以探索使用 WebP 格式的图片。事实上,随着苹果在 Safari 14 中增加了对 WebP 的支持。因此,经过多年的讨论和辩论,直到今天,WebP 在所有现代浏览器中都得到了支持。因此,如果需要,我们可以使用 <picture> 元素和 JPEG 兼容,或者使用内容协商来提供 WebP 图像。

不过 WebP 也不是没有缺点。虽然 WebP 图像文件的大小与 Guetzli 和 Zopfli 相当,但它不支持 JPEG 这样的渐进渲染,这就是为什么用户可以通过一个好的 ol’JPEG 更快地看到完成的图像,尽管 WebP 图像可能通过网络更快。使用 JPEG,我们可以为一半甚至四分之一的数据提供“体面”的用户体验,然后再加载其余的数据,而不是像 WebP 那样只有一半的图像。

您的决定取决于您所追求的:使用 WebP,您将减少有效负载,而使用 JPEG,您将提高感知的性能。你可以在谷歌的 Pascal Massimino 的 WebP Rewind talk 中了解更多关于 WebP 的内容。

转换到 WebP,你可以使用 WebP 转换器,cwebp 或 libwebp。Ire Aderinokun 也有一个非常详细的教程,把图像转换成 WebP, Josh Comeau 也在他的文章中提出拥抱现代图像格式

Sketch 支持 WebPack,而且可以通过 Photoshop 的 WebP 插件从 Photoshop 导出 WebP 图像。但是其他的选择也是可行的

如果你使用 WordPress 或 Joomla,有一些扩展可以帮助你轻松实现对 WebP 的支持,例如 Optimus缓存启用器,WordPress 和 Joomla 自己支持的扩展(通过 Cody Arsenault)。您还可以使用 React、styled components 或 gatsby-image 抽象出<picture>元素。

可参考资料:

  1. 关于 WebP 全面的讨论

我们是否应该使用 AVIF

你可能已经听说了这个大新闻:AVIF 发布了。它是一种新的图像格式,源自 AV1 视频的关键帧。这是一个开放的,免费的格式,支持有损和无损压缩,动画,失真 alpha 通道,可以处理锐利的线条和纯色(这是 JPEG 的一个问题),同时提供更好的结果

事实上,与 WebP 和 JPEG 相比,AVIF 性能显著更好,在相同的 DSSIM(使用近似人类视觉的算法,两个或更多图像之间的相似性)下,产生的中等文件大小节省高达 50%。事实上,Malte Ubl 在他关于优化图像加载的详细文章中指出,AVIF“在一个非常重要的方面始终优于 JPEG”。这与 WebP 不同,WebP 并不总是产生比 JPEG 小的图像,而且由于缺乏对渐进加载的支持,实际上可能是一个倒退。

具有讽刺意味的是,AVIF 甚至可以比大型 svg 表现得更好,尽管它当然不应该被视为 SVGs 的替代品。它也是最早支持 HDR 颜色支持的图像格式之一,提供更高的亮度、颜色位深度和色域。唯一的缺点是目前 AVIF 不支持渐进式图像解码。而且,与 Brotli 类似,高压缩率编码目前相当缓慢,尽管解码速度很快

目前,Chrome、Firefox 和 Opera 支持 AVIF, Safari 的支持也将很快推出(因为苹果是 AV1 创建团队的成员之一)。

那么,现在什么是呈现图像的最佳方式呢?对于插图和矢量图像,(压缩的)SVG 无疑是最佳选择。对于照片,我们使用图片元素的内容协商方法。如果支持 AVIF,我们发送一个 AVIF 图像;如果不是这样,我们首先退回到 WebP,如果 WebP 也不支持,我们切换到 JPEG 或 PNG 作为退回(如果需要,应用@media 条件)

<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" alt="Photo" width="450" height="350" />
</picture>

坦率地说,我们更有可能在图片元素中使用一些条件

<picture>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.avif 1920w,
/img/Z1s3TKV-1280w.avif 1280w,
/img/Z1s3TKV-640w.avif 640w,
/img/Z1s3TKV-320w.avif 320w
"
type="image/avif"
/>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.webp 1920w,
/img/Z1s3TKV-1280w.webp 1280w,
/img/Z1s3TKV-640w.webp 640w,
/img/Z1s3TKV-320w.webp 320w
"
type="image/webp"
/>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.jpg 1920w,
/img/Z1s3TKV-1280w.jpg 1280w,
/img/Z1s3TKV-640w.jpg 640w,
/img/Z1s3TKV-320w.jpg 320w
"
type="image/jpeg"
/>
<img src="fallback-image.jpg" alt="Photo" width="450" height="350" />
</picture>

您还可以更进一步,将动态图像与静态图像交换给那些选择使用 prefers-reduced-motion 的用户

<picture>
<source media="(prefers-reduced-motion: reduce)" srcset="no-motion.avif" type="image/avif"></source>
<source media="(prefers-reduced-motion: reduce)" srcset="no-motion.jpg" type="image/jpeg"></source>
<source srcset="motion.avif" type="image/avif"></source>
<img src="motion.jpg" alt="Animated AVIF">
</picture>

在过去的几个月里,AVIF 也取得了相当多的关注:

  1. 我们可以在 DevTools 中的 render 面板 中尝试 WebP/AVIF
  2. 我们可以使用 SquooshAVIF.iolibavif 去编码、解码和转换 AVIF 文件
  3. 我们可以使用 AVIF Pract 组件来解码 AVIF 文件,并在 canvas 上展示结果
  4. 为了只将 AVIF 交付给支持的浏览器,我们可以使用一个 PostCSS 插件和一个 315B-script 在你的 CSS 声明中使用 AVIF。
  5. 我们可以使用 CSS 和 Cloudlare Workers逐步交付新的图像格式,以动态修改返回的 HTML 文档,从 accept 报头推断信息,然后增加 webp/avif 等等
  6. AVIF 在 Cloudinary 已经可用了(具有使用限制),Cloudflare 支持 AVIF 图像改变大小,你可以 在 Netlify 自定义 AVIF 头启用 AVIF。
  7. 说到动画,AVIF 的表现和 Safari 的<img src=mp4>一样好,总体上优于 GIF 和 WebP,但 mp4 的表现仍然更好
  8. 总的来说,对于动画,AVC1 (h264) > HVC1 > WebP > AVIF > GIF,假设基于 chromium 的浏览器将永远支持<img src=mp4>
  9. 你可以在 AVIF 与下一代图像编码了解更多关于 AVIF 的细节

那么未来会是 AVIF 的 吗?Jon Sneyers 不同意:AVIF 的性能比 JPEG XL 差 60%,后者是谷歌和 Cloudinary 开发的另一种自由开放格式。事实上,JPEG XL 似乎在各个方面都表现得更好。然而,JPEG XL 仍然处于标准化的最后阶段,还不能在任何浏览器中工作。

可参考资料:

  1. 使用 AVIF 作为浏览器渐进增强,将 WebP,JPEG 或 PNG 提供给旧的浏览器
  2. 可参考资料

JPEG/PNG/svg 是否正确优化

当你打开一个登录页时,它的主题图片加载的非常快,这一点很关键,确保 jpeg 是渐进的并且是压缩的使用 mozJPEG(通过操作扫描级别提高开始渲染时间)或 Guetzli,谷歌开源编码器关注感知性能,并且利用 Zopfli 和 WebP。唯一的缺点是:处理时间较慢(每百万像素需要 1 分钟的 CPU)

对于 PNG,我们可以使用 Pingo,对于 SVG,我们可以使用 SVGOSVGOMG。如果您需要快速预览、复制或下载网站上的所有 SVG assets,SVG-grabber 也可以为您做到这一点。

每一篇图像优化的文章都会提到这一点,但是保持矢量资产的整洁和紧凑总是值得一提的。确保清理未使用的 assets,删除不必要的元数据并减少美术作品中的路径点数量(从而减少 SVG 代码)。

还有一些有用的在线工具:

  1. 使用 Squoosh用于压缩,调整和处理图像在最佳压缩水平(有损或无损)
  2. 使用 Gyetzli.it用于压缩优化 JPEG 图像,对于有锐利边缘和纯色的图像效果很好(但可能会慢一些)。
  3. 使用 响应式图像生成或者 Cloudinary or Imgix 提供的服务来自动优化图像。此外,在许多情况下,仅使用 srcset 和 size 将获得显著的效果。
  4. 要检查响应式标记的效率,可以使用 image-heap,这是一个命令行工具,可以通过视口大小和设备像素比率来衡量效率。
  5. 您可以添加自动图像压缩到您的GitHub 工作流程。这个操作使用了 mozjpeg 和 libvip,它们可以处理 png 和 jpg。
  6. 为了在内部优化存储,你可以使用 Dropbox 新的 Lepton 格式,对 jpeg 文件进行平均 22% 的无损压缩。
  7. 如果您想要早期显示占位图像,请使用 BlurHash。BlurHash 获取一个图像,并为您提供一个短字符串(只有 20-30 个字符!),它代表该图像的占位符。该字符串足够短,可以很容易地将其添加为 JSON 对象中的字段。

有时候,优化图片并不能解决问题。为了缩短重要图像的渲染开始所需的时间,延迟加载不太重要的图像,并在重要图像已经渲染后推迟加载任何脚本。最有效的方法是懒加载,当我们使用本地延迟加载和懒加载时,一个库可以检测通过用户交互触发的任何可见性变化(使用 IntersectionObserver,我们将在后面探讨)。此外:

  1. 考虑预加载关键图像,这样浏览器就不会发现得太晚。对于背景图像,如果您想要更加大胆,可以使用<img src>将图像添加为普通图像,然后将其隐藏在屏幕之外。
  2. 考虑使用 size 属性来控制图像,根据媒体查询指定不同的图像显示尺寸。
  3. 检查图像下载的不一致,以防止前景和背景图像的意外下载。注意那些默认加载,但可能永远不会显示的图像,例如在旋转木马,手风琴和图片库。
  4. 确保始终设置图像的宽度和高度。要注意 CSS 中的纵横比属性和 intrinsicsize 属性,它们允许我们设置图像的纵横比和尺寸,这样浏览器就可以提前预留一个预定义的布局槽,避免在页面加载时出现布局跳跃。

如果你喜欢冒险,你可以使用 Edge workers (基本上是一个基于 CDN 的实时过滤器)来切断和重新排列 HTTP/2 流,以便更快地通过网络发送图像。Edge worker 使用 JavaScript 流,使用 chunks,你可以控制(基本上他们是 JavaScript 运行在 CDN edge,可以修改流响应) ,所以你可以控制图像的传输。

使用 service worker,已经太迟了,因为你无法控制在网络上的内容,但它确实与 Edge worker 一起工作。因此,你可以使用它们在静态 jpeg 文件上逐步保存为一个特定的页面。

还不够好?当然,你也可以通过使用多背景图像技术来提高图像的感知性能。请记住,使用对比度和模糊掉不必要的细节(或去掉颜色)也可以减少文件大小。啊,你需要放大一张小照片而不失去质量吗?考虑使用 Letsenhance.io

到目前为止,这些优化只涵盖了基本的内容。Addy Osmani 已经发布了一个非常详细的指南,关于基本的图像优化,深入到图像压缩和颜色管理的细节。例如,你可以模糊掉图像中不必要的部分(通过对它们应用高斯模糊滤镜),以减少文件大小,最终你甚至可以开始删除颜色或将图片变为黑色和白色,以进一步缩小大小。对于背景图片,从 Photoshop 中导出 0 到 10%的图片质量也是绝对可以接受的。

在 Smashing Magazine 上,我们使用后缀-opt 来命名图像,例如:brotli-compression-opt.png;当一个图像包含这个后缀时,团队中的每个人都知道这个图像已经被优化过了。

不要在网络上使用 JPEG-XR ー“ CPU 处理解码 JPEG-XRs 软件的过程抵消甚至超过了节省字节大小的所带来的积极影响,特别是在 spi 环境下”(不要与 Cloudinary/Google 的 JPEG XL 混淆)。

可参考资料:

  1. 占位图形式
  2. 宽高比在浏览器展示
  3. 测试不同视口和不同设备像素比的效率
  4. 将 GIF 图替换为 video 图片,文件大小的差异很明显(节省 80%)

视频是否得到了适当的优化

到目前为止我们已经讨论了图片,但是我们避开了关于好的 ol’gif 的讨论。尽管我们喜欢 gif,但现在是时候永远放弃它们了(至少在我们的网站和应用程序中是这样)。与其加载同时影响渲染性能和带宽的大量 GIF 动画,不如切换到动画 WebP (GIF 作为后备)或者替换为循环的 H5 内嵌视频

不同于图像,浏览器不会预加载<video>内容,但 HTML5 视频往往比 gif 更轻,更小。不是一个选择吗? 好吧,至少我们可以用有损 GIF, gifsiclegifflossy 给 GIF 添加有损压缩。

Colin Bendell 的测试表明,在 Safari 技术预览中,img 标签内嵌的视频比 GIF 标签至少快 20 倍,解码速度也快 7 倍,而且在文件大小上只占一小部分。但是,其他浏览器不支持它。

视频格式在过去几年里取得了巨大的进步。很长一段时间以来,我们都希望 WebM 能成为所有这些格式的主宰,而 WebP(基本上是 WebM 视频容器中的一个静态图像)将成为过时图像格式的替代品。的确,Safari 现在支持 WebP,但是尽管 WebP 和 WebM 这些天来获得了支持,这个突破并没有真正发生。

尽管如此,我们还是可以在大多数现代浏览器上使用 WebM:

<!-- By Houssein Djirdeh. https://web.dev/replace-gifs-with-videos/ -->
<!-- A common scenartio: MP4 with a WEBM fallback. -->
<video autoplay loop muted playsinline>
<source src="my-animation.webm" type="video/webm" />
<source src="my-animation.mp4" type="video/mp4" />
</video>

但也许我们可以重新考虑一下。2018 年,开放媒体联盟发布了一种新的有前途的视频格式,名为 AV1。AV1 具有类似于 h.265 编解码器的压缩(h.264 的进化) ,但与后者不同,AV1 是免费的。H.265 许可证定价促使浏览器厂商采用同等性能的 AV1: AV1(就像 h.265 一样)压缩效果是 WebM 的两倍。

事实上,苹果目前使用 HEIF 格式和 HEVC (h.265) ,最新 iOS 上的所有照片和视频都以这些格式保存,而不是 JPEG。而 HEIF 和 HEVC (h.265)还没有正确地在网络上使用(还没有?),而 AV1 正在获得浏览器支持。因此,在你的 <video> 标签中添加 AV1 源代码是合理的,因为所有的浏览器供应商似乎都在考虑这个问题。

目前,最广泛使用和支持的编码是 h. 264,由 MP4 文件提供服务,因此在提供文件之前,请确保您的 MP4 是通过 multipass-encoding 处理的,使用 frei0r iirblur 效果(如果适用的话)进行模糊处理,moov 原子元数据移动到文件头,在您的服务器接受字节。Boris Schapira 为 FFmpeg 提供了最大限度优化视频的精确说明。当然,提供 WebM 格式作为替代也会有所帮助。

需要开始渲染视频更快,但视频文件仍然太大?例如,当你的页面上有一个很大的背景视频时?一种常用的技术是先将第一帧显示为静态图像,或者显示经过高度优化的短循环片段,这些片段可以被解释为视频的一部分,然后,当视频缓冲足够时,就开始播放实际的视频。Doug Sillars 写了一份详细的背景视频性能指南,在这种情况下可能会有所帮助。

研究表明,视频流质量影响观众的行为。事实上,如果启动延迟超过 2 秒,观众就会开始放弃视频。除此之外,延迟时间每增加 1 秒,放弃率就会增加大约 5.8% 。因此,视频开始时间的中位数为 12.8 秒并不奇怪,40% 的视频至少有一个延迟,20% 至少有 2 秒延迟视频播放。事实上,3G 上的视频暂停是不可避免的,因为视频播放速度比网络提供的内容还要快。

那么,解决方案是什么呢?通常小屏幕设备不能处理我们提供给桌面的 720p 和 1080p。根据 Doug Sillars 的说法,我们既可以创建视频的小版本,也可以使用 Javascript 检测小屏幕的源代码,以确保在这些设备上快速、平滑地播放视频。或者,我们可以使用流媒体视频。HLS 视频流将向设备提供一个合适大小的视频,从而抽象出为不同屏幕创建不同视频的需求。它还将协商网络速度,并适应视频比特率的速度,您正在使用的网络。

为了避免在带宽上的浪费,我们只能为能够很好地播放视频的设备添加视频源。或者,我们可以完全从视频标签中移除 autoplay 属性,然后使用 JavaScript 在更大的屏幕上插入 autoplay。此外,我们需要在视频中添加 preload = “none” ,告诉浏览器在实际需要视频文件之前不要下载任何视频文件:

<!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ -->
<video
id="hero-video"
preload="none"
playsinline
muted
loop
width="1920"
height="1080"
poster="poster.jpg"
>
<source src="video.webm" type="video/webm" />
<source src="video.mp4" type="video/mp4" />
</video>

然后我们可以针对实际上支持 AV1 的浏览器:

<!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ -->
<video
id="hero-video"
preload="none"
playsinline
muted
loop
width="1920"
height="1080"
poster="poster.jpg"
>
<source src="video.av1.mp4" type="video/mp4; codecs=av01.0.05M.08" />
<source src="video.hevc.mp4" type="video/mp4; codecs=hevc" />
<source src="video.webm" type="video/webm" />
<source src="video.mp4" type="video/mp4" />
</video>

然后我们可以在某个阈值(例如 1000px)上重新添加自动播放:

/* By Doug Sillars.
https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ */
<script>
window.onload = addAutoplay();
var videoLocation = document.getElementById("hero-video");
function addAutoplay() {
if (window.innerWidth > 1000) {
videoLocation.setAttribute("autoplay", "");
}
}
</script>

可参考资料:

  1. AV1 有可能成为未来网络视频的终极格式
  2. 在更快的网络和设备上几乎没有停顿
  3. 网页字体传递

是否进行了 web font 优化

第一个值得问的问题是,我们是否可以在一开始就使用 UI 系统字体,我们只需要确保重复检查它们在不同平台上的正确显示

您提供的网络字体很有可能包含字形和其他并未真正使用的功能。

  1. 最好使用 WOFF2 并将 WOFF 用作后备。
  2. 当自定义字体没有下载下来时,立即以备用字体显示内容,异步加载字体,然后按顺序切换字体。
  3. 最终解决方案:两阶段渲染,首先使用一个小的超级子集,然后其余系列异步加载。
  4. 预加载每个系列的 1-2 种字体。避免在字体声明中使用 local()值,但要考虑本地安装的 OS 字体。
  5. 不要忘记包括 font-display:optional,并使用 Font Load Events 进行组重绘。
  6. 使用“ Web 字体重排计数”和“实际斜体时间”度量标准。

构建优化

关键词:JavaScript 模块,module/nomodule 模式,tree-shaking,code-splitting,scope-hoisting,Webpack,差异服务,web worker,WebAssembly,JavaScript bundle,React,SPA,partial hydration,按需加载,第三方服务,cache

设置优先级

  1. 对所有文件(JavaScript,图像,字体,第三方脚本,页面上的“耗费性能的”模块)运行清单,并将其按组细分。
  2. 定义基本的核心体验(旧式浏览器可以完全访问的核心内容),增强的体验(对于功能强大的浏览器来说是丰富的,完整的体验)和其他功能(不是绝对必需的,可以延迟加载的资源)。

在生产环境中使用自定义 JavaScript

  1. 将核心体验发送到旧版浏览器,并将增强的体验发送到现代浏览器。
  2. 使用 ES2017 + <script type="module"> 加载 JavaScript:现代浏览器会将脚本解释为 JavaScript 模块并按预期运行,而旧版浏览器无法识别该脚本,因此将其忽略。
  3. 使用 module/nomodule 模式,同时提供两个 script,由浏览器决定加载哪个文件,通过减少浏览器需要处理的脚本数量来帮助减少主线程阻塞。默认情况下,本地 JavaScript 模块脚本是延迟的,因此当 HTML 解析发生时,浏览器将下载主模块。

可参考资料:

  1. JavaScript Modules

运行 js 是耗费性能的,优化 js

  1. 使用 SPA,您可能需要一些时间来初始化应用程序,然后才能呈现页面。
  2. 寻找可以加快初始渲染时间的模块和技术(在低端移动设备上,其渲染时间很容易会高出 2 到 5 倍)。

使用 tree-shaking, scope hoisting 和 code-splitting 来减少加载

  1. Tree-shaking 是仅包含生产中实际使用的代码来清理构建过程的一种方法。
  2. code-splitting: 代码拆分 将您的代码库根据分割点分为按需加载的“块”,不是所有的 js 文件都需要立刻被下载、解析和渲染的。
    1. 如何确定代码分割点:通过跟踪哪些 CSS/JavaScript 块被使用,哪些没有被使用。Umar Hansa 解释了如何使用来自 Devtools 的代码覆盖来实现它。
  3. 范围提升可以检测到导入链可以在何处打平并转换为一个内联函数而不会破坏代码(例如通过 Webpack)。
  4. 使用提前编译器将一些客户端渲染卸载到服务器。
  5. 也考虑在程序包级别进行代码拆分。

可参考资料:

  1. code splitting
  2. react 性能优化
  3. 使用 React 16 和 Chrome Devtools 分析 React 性能

是否可以将 JavaScript 卸载到 Web Worker 或 WebAssembly 中

随着代码库的发展,UI 性能瓶颈将开始显现。 发生这种情况是因为 DOM 操作与您的 JS 一起在主线程上运行。 考虑将这些耗费性能的操作转移到后台进程,该后台进程与 Web Worker 一起在不同的线程上运行。 典型的用例:预拉取数据和 PWA。 考虑将繁重的计算任务卸载到 WebAssembly 上,这最适合计算密集型 Web 应用程序,例如 Web 游戏。

仅向旧版浏览器提供旧版代码(差异服务)

  1. 使用 babel-preset-env 仅可转换您要定位的现代浏览器不支持的 ES2015 +功能。 然后设置两个构建,一个在 ES6 中,一个在 ES5 中。
  2. 对于 lodash,请使用 babel-plugin-lodash,它将仅加载您在源代码中使用的模块。
  3. 转换通用 lodash 要求精心挑选,以避免代码重复。
  4. 使用标头<link rel =" modulepreload">启动模块脚本的早期(和高优先级)加载。

通过增量解耦识别和重写旧代码

  1. 重新访问您的依赖关系,并评估重构或重写最近引起问题的旧代码需要多少时间。
  2. 首先,设置度量标准,以跟踪旧代码调用比例是保持不变还是下降,而不是上升。
  3. 公开劝阻团队不要使用该库,并确保您的 CI, 如果在请求请求中使用了 CI,则会提醒开发人员。

识别并删除未使用的 CSS / JavaScript。

  1. Chrome 中的 CSS 和 JavaScript 代码覆盖率使您可以了解哪些代码已执行/应用,哪些未执行。
  2. 一旦检测到未使用的代码,请找到那些模块并使用 import()延迟加载。
  3. 然后重复覆盖率配置文件,并验证它现在在初始加载时正在交付更少的代码。
  4. 使用 Puppeteer 以编程方式收集代码覆盖率。

缩小 JavaScript 依赖项的大小。

  1. 当您只需要一小部分时,很有可能会交付完整的 JavaScript 库。 为了避免开销,请考虑使用 webpack-libs-optimizations 在构建过程中删除未使用的方法和 polyfill。 将捆绑包审核添加到常规工作流程中。
  2. Bundlephobia 帮助您找到向软件包中添加 npm 软件包的成本,size-limit 扩展了基本软件包的大小检查,并提供了有关 JavaScript 执行时间的详细信息。

是否正在对 JavaScript 块使用预加载

使用 heuristics 确定何时预加载 JavaScript 块。 Guess.js 是使用 Google Analytics(分析)数据确定用户最有可能访问下一页的一组工具。 另外,考虑使用 Quicklink,Instant.page 和 DNStradamus。 注意:您可能会提示浏览器使用不需要的数据并预取不需要的页面,因此对预请求的数量保持在一定范围内是个好的方式。

针对您的目标 JavaScript 引擎进行优化。

利用整体脚本的脚本流,因此一旦开始下载,就可以在单独的后台线程上对它们进行解析。 也可以通过使用库将 V8 的代码缓存从代码中分离出来,或者通过其他方法来使用 V8 的代码缓存。 同时考虑 Firefox 的 Baseline Interpeter 的 JIT 优化策略。

找到一种将客户端渲染和服务器端渲染结合起来的方法

  1. 通常,目标是在客户端和服务器端渲染之间找到最佳平衡。
  2. 如果页面变化不大,请考虑预渲染;如果可以,请考虑推迟框架的启动。
  3. 通过服务器端渲染以流方式分块传输 HTML,并通过客户端渲染为单个组件实现 progressive hydration,并在可见性,交互性或空闲时间进行 hydration 处理,以充分利用两者。(使用 progressive hydration 流式传输服务器端渲染)。

考虑进行微优化和逐步引导

  1. 使用服务器端渲染可以快速获得第一个有意义的图形,同时还包含一些最小的 JS,以使交互时间接近第一个有意义的图形。
  2. 然后,根据需要或在时间允许的情况下,启动应用程序的非必需部分。
  3. 始终将功能的执行分解为单独的异步任务。
  4. 尽可能使用 requestIdleCallback。

始终自己控制第三方脚本

  1. 使用公共 CDN 不会自动导致更好的性能。
  2. 即使两个站点指向完全相同的第三方资源 URL,每个域也会下载一次代码,并且缓存“沙盒”到该域。
  3. 第一方资产比第三方资产更有可能保留在缓存中。
  4. 自我托管更加可靠,安全,并且性能更高。

限制第三方脚本的影响

  1. 通常,一个第三方脚本最终会调用链式很长的脚本。 考虑通过超时加速资源下载来使用 service worker。
  2. 建立内容安全政策(CSP),以限制第三方脚本的影响,例如 禁止下载音频或视频。
  3. 通过 iframe 嵌入脚本,因此脚本无权访问 DOM。
  4. 也将它们沙箱化。
  5. 要对脚本进行压力测试,请在“性能”概要文件页面(DevTools)中检查自底向上的摘要。

正确设置 HTTP 缓存头

  1. 仔细检查是否过期,正确设置了缓存控制,最大使用期限和其他 HTTP 缓存头。 通常,资源应该在很短的时间内(如果它们可能会更改)或无限期(如果它们是静态的)可缓存。
  2. 使用高速缓存控制:不可变,设计用于指纹静态资源,以避免重新验证。
  3. 检查您是否没有发送不必要的标头(例如 x-by-by,pragma,x-ua-compatible,expire)。
  4. 通过使用 stale-while-revalidate 将零 RTT 用于重复视图。

加载优化

关键词:延迟加载,交叉观察,延迟渲染和解码,关键的 CSS,流,资源提示,布局偏移,service worker

异步加载 JavaScript

  1. 作为开发人员,我们必须明确告知浏览器不要等待,并开始使用 HTML 中的 defer 和 async 属性来呈现页面。
  2. 如果您不必为 IE 9 及更低版本担心,那么最好选择延迟到异步。
  3. 使用 defer,浏览器在解析 HTML 之前不会执行脚本。
  4. 因此,除非需要 JS 在开始渲染之前执行,否则最好使用 defer。

使用 IntersectionObserver 懒加载耗费性能的组件

延迟加载所有耗费性能的组件,例如沉重的 JavaScript,视频,iframe,小部件以及可能的图像。 最有效的方法是使用本机延迟加载(加载和重要性属性)或使用 IntersectionObserver-后者也可用于执行滚动显示,视差和广告跟踪。

快速推送关键 CSS

  1. 收集开始渲染页面的第一个可见部分所需的所有 CSS(“关键 CSS”或需要提前下载的 CSS),并将其内联添加到页面的<head>中。
  2. 考虑条件内联方法。
  3. 或者,考虑使用 HTTP/2 服务器推送,但是您可能需要创建一个支持缓存的 HTTP/2 服务器推送机制。
  4. 将关键 CSS 放在根域上的单独文件中有很多好处,有时达到的效果甚至比缓存的效果要好

尝试重新组合 CSS 规则

  1. 考虑将主 CSS 文件拆分为各个媒体查询
  2. 避免在异步代码片段之前放置<link rel =“ stylesheet” />。 如果脚本不依赖样式表,请考虑将阻止脚本置于阻止样式之上。 如果可以,请将 JavaScript 分成两部分,然后在 CSS 的任一侧加载
  3. 与 service worker 一起缓存内联 CSS 并尝试使用 在 body 内写 CSS 样式
  4. 动态样式可能会很昂贵:当 CSS 不依赖主题/属性,不要过度组合样式化组件时,请检查 CSS-in-JS 库是否优化了执行。

流响应

  1. 流提供了一个用于读取或写入异步数据块的接口,在任何给定时间它的子集仅在内存中可用。
  2. 让 service worker 构造一个流,其中外壳程序来自缓存,而主体来自网络,而不是提供一个空的 UI Shell 并让 JavaScript 填充它。
  3. 然后,在初始导航请求期间呈现的 HTML 可以充分利用浏览器的流 HTML 解析器。

考虑使您的组件知道连接/设备内存

  1. Save-Data 客户端提示请求标头使我们能够为成本和性能受限制的用户定制应用程序和有效负载。
  2. 例如,您可以将对高 DPI 图像的请求重写为低 DPI 图像,删除 Web 字体和奇特的视差效果,关闭视频自动播放,服务器推送,甚至更改标记的交付方式。
  3. 使用网络信息 API 根据网络类型交付重型组件的变体。
  4. 也可以使用设备内存 API 根据可用的设备内存动态调整资源。

预热连接以加快交付速度

使用资源提示来节省 dns-prefetch(在后台进行 DNS 查找),预连接(启动连接握手(DNS,TCP,TLS)),预取(请求资源),预加载(不执行它们就预取资源)上的时间。 东西)和预渲染(提前获取资源,但不执行 JS 或不提前渲染页面的任何部分)。 使用预加载时,必须定义或不加载; 没有 cross origin 属性的预加载字体将加倍获取。对于预加载,优先级会令人困惑,因此请考虑在外部阻止脚本之前将 rel=”preload” 元素注入 DOM。

使用 service worker 进行缓存和网络后备

如果您的网站通过 HTTPS 运行,则将静态资源缓存在 service worker 缓存中,并存储脱机后备(甚至脱机页面),并从用户的计算机中检索它们,而不是去网络。 将应用程序 shell 以及一些关键页面(例如脱机页面或主页)存储在 service worker 的缓存中。 但是:请确保跨域资源存在正确的 CORS 响应标头,不要缓存不透明的响应,并且选择将跨域图像资源选择进入 CORS 模式。

在 CDN/Edge 上使用 service workers on the (e.g. A/B testing).

通过 CDN 在服务器上实现 service workers,请考虑 service workers 也在 Edge 上调整性能。 例如。 在 A / B 测试中,当 HTML 需要为不同的用户更改其内容时,请使用 CDN 上的 service workers 来处理逻辑。 或流式 HTML 重写以加快使用 Google 字体的网站的速度。

优化渲染性能

  1. 使用 CSS 隔离耗费性能组件。
  2. 确保在滚动页面或为元素设置动画时没有滞后,并且始终保持每秒 60 帧的速度。
  3. 如果无法做到这一点,则最好使每秒帧数保持一致,至少应介于 60 到 15 的混合范围内。
  4. 使用 CSS will-change 可以通知浏览器哪些元素将发生变化。

预防页面重绘和重排

  1. 回流是由重新缩放的图像和视频,Web 字体,注入的广告或后来发现的脚本(这些脚本用实际内容填充组件)引起的
  2. 在图像上设置宽度和高度属性,因此默认情况下,现代浏览器会分配框并保留空间
  3. 使用 SVG 占位符保留将在其中显示视频和图像的显示框
  4. 仅在不支持本机延迟加载的情况下,使用混合延迟加载来加载外部延迟加载脚本
  5. Web 字体分组重新绘制并使用 font-style-matcher 匹配行高和间距
  6. 使用 Layout Instability API 和累积布局移位(CLS)跟踪界面的稳定性

网络和 HTTP/2

关键词:OCSP stapling,EV/DV 证书,打包,IPv6,QUIC,HTTP/3

是否启用了 OCSP stapling

通过在服务器上启用 OCSP 装订,可以加快 TLS 握手的速度。 OCSP 协议不需要浏览器花时间下载,然后在列表中搜索证书信息,因此减少了握手所需的时间。

您是否已采用 IPv6

研究表明,由于 NDP 和路由优化,IPv6 使网站的速度提高了 10%至 15%。 更新 IPv6 的 DNS,以防将来发生故障。只需确保在网络上提供双栈支持即可,它使 IPv6 和 IPv4 可以同时并行运行。毕竟,IPv6 不向后兼容。

是否正在使用 TCP BBR

BBR 是一种相对较新的 TCP 延迟控制 TCP 流控制算法。它可以响应实际的拥塞,而不是像 TCP 那样处理数据包丢失,因此速度更快,吞吐量更高,延迟更短 启用 BBR 拥塞控制并将 tcp_notsent_lowat 设置为 16KB,以使 HTTP/2 优先级在 Linux 4.9 内核及更高版本上可靠地工作。

准备使用 HTTP/2

很好地支持 HTTP/2,并提高了性能。它无处不在,在大多数情况下,您最好选择它。根据您的移动用户群的大小,您可能需要发送不同的构建,并且可以从适应不同的构建过程中受益。(在丢包率明显的网络上, HTTP/2 的速度通常较慢。)

正确部署 HTTP/2

您需要在包装模块和并行加载许多小模块之间找到良好的平衡。将整个界面分为许多小模块; 然后将它们分组,压缩并打包。在打包级别或通过跟踪/不使用 CSS/JS 块进行拆分。单独的第三方脚本和客户代码,以及单独的第三方脚本依赖关系,这些依赖关系很少且经常更改。大约发送 6-10 个软件包似乎是一个不错的妥协(对于旧版浏览器来说还算不错)。试验并测量以找到合适的平衡。

服务器和 CDN 是否支持 HTTP/2

不同的服务器和 CDN 对 HTTP/2 的支持不同。 使用 TLS 快速来检查您的选项,或快速查找可以支持的功能。 启用 BBR 拥塞控制,为 HTTP/2 优先级设置 tcp_notsent_lowat 为 16KB。

服务器和 CDN 是否支持基于 QUIC 的 HTTP(HTTP/3)

QUIC 和 HTTP/3 更好,更安全:具有更快的握手,更好的加密,更可靠的独立流,更多加密,并且如果客户端以前与服务器建立连接,则使用 0-RTT。但是,这会占用大量 CPU 资源(在相同带宽下,CPU 使用率是 2-3 倍)。检查您的服务器或 CDN 是否支持基于 QUIC 的 HTTP(也称为 HTTP/3),以及是否可以启用它。

是否正在使用 HPACK 压缩

如果您使用的是 HTTP/2,请仔细检查您的服务器是否对 HTTP 响应标头实施了 HPACK 压缩,以减少不必要的开销。由于 HTTP/2 服务器相对较新,因此它们可能不完全支持该规范,以 HPACK 为例。 H2spec 是一个很棒的工具,可以进行检查。

确保服务器上的内容非常安全的

仔细检查您的安全标头设置正确,消除已知漏洞并检查证书。确保所有外部插件和跟踪脚本均通过 HTTPS 加载,无法进行跨站点脚本编写,并且正确设置了 HTTP Strict Transport Security 标头和 Content Security Policy 标头。

测试与监控

关键词:Auditing workflow,代理浏览器,404 page,GDPR cookie 同意提示,性能诊断 CSS,可访问性

监控混合内容警告

如果您最近从 HTTP 迁移到 HTTPS,请确保使用 Report-URI.io 之类的工具 监控主动和被动混合内容警告。 您还可以使用“混合内容扫描”来扫描启用 HTTPS 的网站中的混合内容。

是否优化了 Auditing workflow

花时间研究调试器,WebPageTest,Lighthouse 中的调试和审核技术,并增强文本编辑器的功能。例如,您可以通过 Google Spreadsheet 驱动 WebPageTest,并通过 Lighthouse CI 将可访问性,性能和 SEO 得分纳入 Travis 设置中,或直接纳入 Webpack 中。

是否在代理浏览器和旧版浏览器中进行了测试

在 Chrome 和 Firefox 中进行测试还不够。研究您的网站在代理浏览器和旧版浏览器(包括 UC 浏览器和 Opera Mini)中的工作方式。衡量用户群中的平均 Internet 速度,注重用户体验。使用网络限制进行测试,并仿真高 DPI 设备。 BrowserStack 很棒,但也可以在真实设备上进行测试。

是否测试了对可访问性的影响

大页面和使用 JavaScript 进行的 DOM 操作将导致屏幕阅读器延迟。快速互动时间表示屏幕阅读器可以宣布在给定页面上的导航并且屏幕阅读器用户实际上可以按下键盘进行交互之前要经过多少时间。

是否设置了持续监控

良好的性能指标是被动和主动监控工具的结合。 拥有 WebPagetest 的私有实例并使用 Lighthouse 对于快速测试总是有好处的,但同时也可以使用 SpeedTracker,SpeedCurve 等 RUM 工具设置连续监控。 设置您自己的用户计时标记,以测量和监控特定于业务的指标。

快速优化清单

  1. 衡量真实的性能现状并设定适当的目标。目标是比你最快的竞争对手至少快 20%。保持在 LCP < 2.5s,FID < 100ms,在慢速 3G 上交互时间 < 5s,对于重复访问,TTI < 2s。至少优化 LCP 和 TTI
  2. 使用 Squoosh, mozjpeg, guetzli, pingo 和 SVGOMG 优化图像,并提供一个图像 CDN 的 AVIF/WebP
  3. 为主要的页面准备关键的 CSS,并将它们内联到 <head>,对于 CSS/JS,控制关键文件的大小在 170KB 以内
  4. 修改、优化、推迟和延迟加载脚本。在打包工具中中进行配置,以消除冗余并检查轻量级替代方案
  5. 始终自行控制静态资源,始终倾向于自行控制第三方库。限制第三方库的影响。在交互中加载窗口小部件,注意防止有快速变化的代码片段
  6. 在选择框架时要有选择性。对于单页面应用程序,确定关键页面并静态地服务它们,或者至少预渲染它们,并在组件级别上使用 progressive hydration,在交互中导入模块。
  7. 单独的客户端渲染对于性能来说不是一个好的选择。如果页面变化不大,则提前渲染,如果可以,则推迟框架的启动。如果可能的话,使用流服务器端渲染
  8. 向旧版浏览器提供旧版的代码
  9. 尝试重新组合 CSS 规则并测试在<body>的 CSS
  10. 添加资源提示以加快交付速度 dns-lookup, preconnect, prefetch, 及 prerender
  11. 将 web 字体子集分组异步加载,并利用 CSS 中的字体显示快速首次呈现
  12. 检查 HTTP 缓存标头和安全标头是否设置正确
  13. 在服务器上启用 Brotli 压缩。(如果无法做到,至少确保启用 Gzip 压缩。)
  14. 启用 TCP BBR 拥塞,只要您的服务器运行在 Linux 内核版本 4.9+上
  15. 如果可能,启用 OCSP stapling 和 IPv6。始终提供一个 OCSP stapled DV 证书
  16. 允许在 HTTP/2 上使用 HPACK 压缩;如果可以的话,迁移到 HTTP/3
  17. 将字体、样式、JavaScript 和图像等资源缓存到 service worker 的缓存中