2018 前端性能检查表

以前,性能往往只是事后的想法。通常直到项目最后的时候才会被考虑,然后被归结为压缩、合并、静态资源优化或者对服务器配置文件的一些细微调整。现在回想起来,事情似乎已经发生了很大的变化。

原文地址:https://juejin.im/post/5ac1d117f265da2396128b9f

众所周知,性能十分重要。然而,我们真的知道性能瓶颈具体在哪儿吗?是执行复杂的 JavaScript,下载缓慢的 Web 字体,巨大的图片,还是卡顿的渲染?研究摇树(Tree Shaking),作用域提升(Scope Hoisting),或是各种各样的与 IntersectionObserver、Clients Hints、CSS containment、HTTP/2 和 Service Worker 一同工作的华丽的加载模式真的有价值吗?最重要的是,我们从哪里开始优化性能,以及我们如何建立长期的性能文化呢?

以前,性能往往只是事后的想法。通常直到项目最后的时候才会被考虑,然后被归结为压缩、合并、静态资源优化或者对服务器配置文件的一些细微调整。现在回想起来,事情似乎已经发生了很大的变化。

性能不仅仅是一个技术问题:它很重要,而且当把它引入到工作流时,设计决策必须根据其性能影响来决定。我们必须不断的测量、监视和改进性能,而且 Web 日益复杂的情况带来了新的挑战,使得性能指标难以被跟踪,因为性能指标将因设备、浏览器、协议、网络类型和延迟(CDN、运营商、缓存、代理、防火墙、负载平衡器和服务器都在其中发挥作用)而有很大差异。

因此,如果我们创作一个在提高性能时必须牢记的所有事项的概述——从流程的一开始到网站的最终发布——这样的列表将是什么样子?下面就是 2018 前端性能检查表(但愿不偏不倚和足够客观)——说明您可能需要考虑的问题,以确保您的站点响应时间快、用户交互流畅,并且不会用尽用户的带宽。

下面是您可能需要考虑的前端性能问题的概述,以确保您的响应时间快速而流畅。

(译注:原文详细地阐述了文中所涉及的所有优化策略的原理和来龙去脉。此处仅翻译了原文中附带的 PDF 检查表文件,意在提供一个快速、简洁的性能优化清单。)

一、准备:规划和指标

01 建立性能文化

只要团队之间没有协作,高性能就无法长期维持。研究用户反馈中常见的抱怨,看看提高性能是否可以帮助缓解其中一些问题。用真实数据来建立适合自己的案例和模型。在设计过程中就开始规划加载顺序和权衡。

02 选择正确的性能指标

并非每个指标都同等重要。研究最重要的度量标准:一般而言,它与您开始渲染最重要像素的速度以及提供输入响应的速度有关。根据客户的感受确定页面加载的优先级。可交互时间、页面大标题元素的渲染时间、首次有效绘制时间(FMP)、速度指数(Speed Index)一般都很重要。

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

收集代表您受众的设备上的数据。在数据来源上,真实设备比模拟数据更好。选择一台 Moto G4、中端三星设备或者 Nexus 5X 等性能良好的中端设备。或者,也可以通过在电脑上,通过设置网络限速(例如:150ms RTT,1.5Mbps 下载,0.7Mbps 上传)和 CPU 限速(5 倍慢速)以模拟移动体验。最后在常规 3G、4G 和 Wi-Fi 之间切换。收集数据、设置电子表格、将指标提高 20% 并设置目标(即,“性能预算”)。

04 把这张检查表分享给你的同事

确保团队中的每个成员都熟悉该清单。每一个决策都涉及性能问题,前端开发人员的积极参与将使您的项目受益匪浅。将你的性能预算映射到设计决策上。

二、制定现实的目标

05 100 毫秒的响应时间 + 每秒60帧

每帧动画应在少于 16 毫秒(理想情况下为 10 毫秒)内完成,从而达到每秒 60 帧(1 秒 ÷ 60 = 16.6毫秒)。保持乐观,明智地利用空闲时间。对于像动画这样的高压点,只要能,就不要做任何其它事情。预计输入延迟时间(Estimated Input Latency)应低于 50 毫秒。

06 速度指数(SpeedIndex)小于 1250,可交互时间(Time-To-Interactive)在 3G 上小于 5 秒

目标是在 1 秒内(在高速网络下)完成首次绘制(FMP),速度指数(SpeedIndex)低于 1250 毫秒。考虑速度基线是一台有着 3G 网络的,价格为 200 美元左右的 Android 手机(译注:国产千元机水平),那么可以以 400 毫秒 RTT 和 400kb/s 的传输速度进行网络模拟,以达成可交互时间(Time-To-Interactive)小于 5 秒,第二次打开的速度低于 2 秒。尽你所能地降低这些指标。

07 核心块 = 15kb,关键文件 < 170 kb

HTML 的前 1415kb 是最关键的核心块(chunk),也是整个文件中唯一可以在第一个 RTT 内被下载的部分。要实现上述目标,请设定关键文件的最大尺寸“预算”。170kb gzip 后的文件(原始文件尺寸 0.81mb),在普通手机上可能需要 1 秒才能解析和编译完成。

三、定义环境

08 选择并设置你的构建工具

不要太注意所谓的“酷”。只要您能够快速获得结果,而且在维护构建过程上没有问题就很好了。

09 渐进增强

首先设计和构建核心功能,然后再在此基础上为功能强大的浏览器的高级功能增强效果,从而创建弹性的体验。如果您的网站在性能差、网络差的机器上还能运行得比较快,那在性能好、网络棒的机器上只会运行得更快。

10 设定硬性的性能基准

用 JavaScript 实现交互效果的成本相当高昂。170kb 的尺寸预算已经包含了核心的 HTML / CSS / JavaScript、路由、状态管理、工具函数、框架还有产品逻辑,因此,请彻底检查我们选择的框架的网络传输成本、解析 / 编译时间和其运行时的时间成本。

11 圣战止于智者:Angular, React 还是 Ember

并不是每个项目都需要框架。但是如果你的项目需要框架,那么最好选择使用一个支持服务器端渲染(SSR)的框架。在使用框架之前,请确保在移动设备上以服务器端渲染和客户端渲染两种模式来评估框架的启动时间。了解您将依赖的框架的具体细节。了解 PRPL 模式和 App Shell 模型。

12 你会使用 AMP 或者 Instant Articles 吗

(译注:AMP 为 Google 的开源项目,意在以组件化的形式以提升移动设备对网站的访问速度;Instant Articles 是 Facebook 的协议,意在通过渲染页面的精简版本以提升页面在 Facebook App 内的打开速度。在国内,MIP 是和 AMP 类似的解决方案。)

没有它们,你也可以获得良好的性能。但是 AMP 提供了一个可靠的性能框架,有免费的 CDN ,而 Instant Articles 将提高你在 Facebook 上的知名度和性能。你也可以构建一个渐进式 AMP(译注:Progressive Web AMP,PWA 和 AMP 的结合体)。

13 选择合适的 CDN

您可以将部分内容“外包”给静态站点生成器,然后将其推送到 CDN,并从CDN 提供静态版本,从而避免数据库请求(即 JAMStack)。当然,这取决于您拥有的动态数据量。仔细检查 CDN 是否为您执行了内容压缩和转换、智能 HTTP/2 和边缘端包含(ESI, edge-side includes)。

四、优化构建

14 合理安排优先级

把你所有的静态资源(JavaScript,图片,字体,第三方脚本,尺寸大的模块)列成一个表,然后把它们按优先级分成三组:基本核心功能(老浏览器也能浏览的核心内容)、增强体验效果(为现代浏览器设计的强大功能和丰富体验)、附加功能(不一定需要并且可以惰性加载的资源,比如字体、轮播脚本、视频播放器、分享按钮等)。

15 使用“符合标准”技术

(译注:“符合标准”技术(cutting-the-mustard technique)是 BBC News 开发者博客提出的,一种基于浏览器特性来检测其支持程度,并以此选择要加载哪些功能的技术。)

对老旧的浏览器,仅输出核心功能代码;对现代浏览器输出增强的功能代码。严格按标准加载静态资源:直接加载核心代码,在 DOMContentLoaded 事件中加载增强代码,并在 load 事件中加载剩下的代码。注意:廉价的 Android 手机虽然很符合标准,但这些手机的内存和 CPU 性能有限。因此,您可能需要使用读取设备内存大小的 JavaScript API 来检测设备性能,只有不支持的时候才按“符合标准”技术来。

16 减少 JavaScript 体积

由于解析 JavaScript 很耗时,所以请尽可能的减少 JavaScript 的体积。在构建 SPA 时,您可能需要用一定时间初始化应用程序之后,才能开始渲染页面。寻找可以加快初始渲染事件的模块和技术(在低端移动设备上,这可以轻松将速度提高 2-5 倍)。彻底检查每一个 JavaScript 依赖,以找出谁在消耗初始化的宝贵时间。

17 使用微优化和渐进式启动

使用服务器端渲染来获得快速的首次有效绘制时间(FMP),但也在页面里输出一些最小功能的 JavaScript 来保持交互时间(TTI)接近首次有效绘制时间(FMP)。然后,如果有需要或者有多余的时间,才开始启动应用程序的非必要部分。在加载时显示一个骨架屏幕,而不是“加载中”动画。

18 使用摇树和代码分割

使用摇树(Tree Shaking)技术和代码分割(Code Splitting)技术以减少代码体积。

摇树(Tree Shaking)技术是一种通过丢弃未使用的代码以在构建过程清理代码的方法。代码分割(Code Splitting)技术将您的代码拆分为按需加载的“chunks(块)”。作用域提升(Scope Hoisting)技术使得链式的依赖能被无缝地转换成行内函数。通过 WebPack 将上述技术用于您的代码。使用 AOT 编译器(译注:例如 Closure Compiler)将一些客户端计算移到服务端。

19 异步加载 JavaScript

作为开发者,我们必须显式地使用 deferasync 属性来告诉浏览器不要等待脚本下载、开始渲染页面。如果你不需要关注 IE 9 及以下版本的浏览器,那么使用 defer 更好;否则,使用 async 更好。使用静态的分享按钮、静态链接交互式地图而不是使用第三方库。

20 HTTP 缓存头是否设置好了

重新检查你是否正确的设置了 Expires, Cache-Control, Max-Age 等 HTTP 缓存控制响应头。通常而言,一个资源要么只被缓存很短的时间(比如经常修改的资源),要么永久缓存(比如不会被更改的那种资源)。使用专为带哈希指纹的静态文件设计的响应头 Cache-Control: imuutable 以避免浏览器重新请求文件。

五、静态资源优化

21 是否使用了 Brotli 或 Zopfli 压缩

Brotli 是一种新的无损压缩格式。现在,所有的现代浏览器都支持它。它比 Gzip 和 Deflate 压缩率更高,压缩非常慢,但是解压速度很快。使用最高压缩比的 Brotli+Gzip 预压缩静态文件,并使用 1~4 级的 Brotli 实时压缩动态内容。也顺便检查一下 CDN 是否支持 Brotli。或者,你也可以试试在不常变化的资源上使用 Zopfli —— 它将数据用 Deflate、Gzip 和 Zlib 格式压缩,并且被设计为一次压缩、多次下载。

22 图片是否被正确优化

尽可能使用通过 srcsetsizes<picture> 元素实现的响应式图片。使用 WebP 格式的图片;这可通过 <picutre> 标签配合 JPEG fallback,或者通过 Accept 请求头来实现。对于核心图片,使用渐进式的 JPEG 并用高斯滤镜模糊掉不重要的部分。

23 Web Font 是否被正确优化

您使用的 Web Font 很可能包含未真正被使用的执行和额外的特性。制作字体的子集(译注:仅包含部分文字的字体,如 fontmin 等方案)。优先使用 WOFF2 并使用 WOFF 作为后备。立即使用后备字体显示文字、异步加载字体(例如,使用 loadCSS),然后再切换字体。同时也考虑本地操作系统中已经安装了的字体。不要忘记在 CSS 中写 font-display: optional;如果你无法从您的服务器加载字体,请记得使用 Font Load Events。

六、分发优化

24 快速推送核心 CSS

将所有首屏渲染所需要的 CSS 放在一起,然后方法在 <head> 标签中。考虑有选择的内联的方法。或者,使用 HTTP/2 服务端推送;但这样你可能需要构建一个可感知缓存的 HTTP/2 服务端推送机制。

25 使用 babel-preset-env 以仅转译 ES2015+ 特性

由于 ES2015 已被广泛支持了,您可以考虑使用 babel-preset-env 以仅转译现代浏览器不支持的 ES2015+ 特性。然后你可以编译两份,一份是 ES6 ,另一份是 ES5。使用 <script type="module"> 使得有 ESM 支持的浏览器加载新文件,剩下的老的浏览器可以使用 <script nomodule> 来加载老的文件。

26 提升渲染性能

使用 CSS 包含(CSS Containment)隔离渲染十分耗时的组件。请保证在滑动页面或者元素动画的时候,页面不会卡顿,而且你的页面能持续以 60fps 的速度渲染。如果那不可能,那么至少也要把 fps 控制在 15~60 之间。使用 CSS 的 will-change 属性通知浏览器哪个元素将会变化。

27 使用 Intersection Observer 懒加载大型脚本

Intersection Observer API 提供了异步监听目标元素与祖先元素或顶层文档视口交点中的更改的能力。浏览器支持?Chrome, Firefox, Edge 和三星浏览器都支持了。WebKit 还在开发。浏览器不支持?懒加载一个 polyfill。

28 是否优化了渲染体验

不要低估感知性能的作用。在加载静态文件时,尽量始终领先用户一步,这样在后台发生很多事情时,会感觉体验上很快。例如,要让用户持续关注你的页面,请使用骨架屏幕而不是一些加载中的动画。

29 预热连接以加快分发速度

使用骨架屏幕,然后懒加载所有的大型组件,比如字体、JavaScript、轮播图、视频和 iframe 等。使用资源提示(Resource Hints),如 dns-prefetchpreconnectprefetchpreload来节省时间。

七、HTTP/2

30 为 HTTP/2 做准备

HTTP/2 支持很好,而且提供了不小的性能提升。缺点是,您必须迁移到 HTTPS;根据您不支持 HTTP/2 的用户群大小,你可能需要为 HTTP/1.1 和 HTTP/2 的用户返回不同版本的代码,这就要求您调整您的编译工具。

31 正确地部署 HTTP/2

您需要在打包模块和并行加载许多小模块之间找到一个良好的平衡。将整个界面分解为许多小模块;然后分组、压缩和打包。整个网站分为大约 6 到 10 个包应该是一个不错的折衷方案(对于传统浏览器来说也不错)。通过实验和数据监测来为您的网站找到正确的平衡。

32 你为 Save-Data 头节约数据流量了吗

Save-Data 请求提示头可以让我们为关心流量费用和性能的用户提供个性化的响应。例如,你可以把所有高清的图片都改成低清的,不用 Web Font 和华丽的动效,关掉视频自动播放和服务器推送,甚至修改你的应用界面。

34 确保服务器上的安全性是无懈可击的

再次检查安全标头是否设置正确,消除已知漏洞,并检查 SSL 证书。确保所有外部插件和跟踪脚本都是通过 HTTPS 加载的、没有 XSS,并且 HSTS 响应头和内容安全策略(CSP)响应头都已正确设置。

35 你的服务器和 CDN 都支持 HTTP/2 吗

不同的服务器和 CDN 可能对 HTTP/2 有不同的支持。使用 Is TLS Fast Yet? 来检查你的设置,或者直接看看你的服务器性能如何,支持的特性情况怎么样。

36 是否启用了 OCSP Stapling

在服务器上启用 OCSP Stapling 有助于提升 TLS 握手速度。OCSP 协议可以让浏览器无需下载并检索证书信息,从而减少握手时间。

37 你使用 IPv6 了吗

研究标明,IPv6 的邻居发现(NDP)和路由优化可以使网站快 10% ~ 15%。升级到支持 IPv6 的 DNS 以为未来做好准备。只需确保双栈网络能正常工作——这使得 IPv6 和 IPv4 能同时运行。毕竟,IPv6 不是向后兼容的。

38 HPACK 压缩启用了吗

如果你使用了 HTTP/2,再次检查你的服务器是否实现了 HPACK 压缩。HPACK 压缩可以压缩 HTTP 响应头,以减少不必要的开支。由于 HTTP/2 服务器现在都很新,他们可能不能完全支持包括 HPACK 压缩在内的所有标准。H2spec 是一个非常好的用于检测标准支持程度的工具。

39 你使用了 Service Worker 来缓存或者提供离线内容吗

网络再怎么优化,也不会比本地缓存更快。如果你的网站使用了 HTTPS,那么你可以把静态资源放在 Service Worker 的缓存中,而不用请求网络。

八、测试和监控

40 监控混合内容警告

如果您最近从 HTTP 迁移到了 HTTPS,请确保使用类似 Report-URI.io 之类的服务监控了严格的或被动的混合内容报警。你也可以用 Mixed Content Scan 来扫描你的 HTTPS 站点上是否有非 HTTPS 的混合内容。

41 使用 DevTools 的开发工作流是优化过的吗

选择一个调试工具,并试着点击每一个按钮。请确保您理解如何分析渲染性能、控制台输出、调试 JavaScript 和编辑 CSS 样式。

42 是否在代理浏览器和老式浏览器上测试过了

在 Chrome 和 Firefox 上测试是不够的。请看看你的网站在代理浏览器和老式浏览器(包括 UC 浏览器和 Opera Mini 等。译者注:此处的代理浏览器即指国内浏览器中常见的云加速功能)上是什么样子。统计你受众国家的网络平均速度,避免出现重大意外。使用网络节流并模拟高 DPI 设备。虽然 BrowserStack 很好,但也得在真机上测试。

43 是否设置了持续的监控

良好的性能指标是被动和主动监控工具的组合。拥有 WebPagetest 的私有实例和使用 Lighthouse 确实有利于快速测试,但也需要使用诸如 Calibre、speedscurve 等 RUM 工具建立持续的监控体系。设置您自己的用户计时打点以监控特定的业务速度指标。

九、速战速决

此列表相当全面,完成所有优化可能需要相当长的时间。如果你只有一个小时的时间,但又想获得显著的提升,你应该怎么做?我们挑出了 10 个最容易实现的方法。显然,在开始之前和完成之后,请统计结果,包括 3G 和有线连接上的开始渲染时间和速度指数(SpeedIndex)。

  1. 统计真实的用户体验,设置可接受的目标。一个好的目标大致是:FMP < 1s,速度指数 < 1250,TTI 在 3G 网络上 < 5s 、二次访问 < 2s。优化开始渲染时间和 TTI。
  2. 为你的主模板准备核心 CSS,并放在 <head> 标签里(你的预算是 14KB)。对于 CSS/JS,请保证核心文件尺寸最大为 170kb (gzip 后的尺寸;压缩前 0.8~1Mb)
  3. 延迟或懒加载尽可能多的脚本,不管是你自己的还是第三方的——特别是分享按钮、视频播放器和其它的复杂模块。
  4. 增加资源提示,包括 dns-lookup, preconnect, prefetchpreload
  5. 为 Web Font 创建子集,并异步加载(或者干脆别用)。
  6. 优化图片。考虑在关键的页面(比如落地页)上用 WebP 格式。
  7. 检查 HTTP 缓存头和安全头是否正确设置了。
  8. 在服务器上启用 Brotli 或者 Zopfli 压缩。如果不支持,别忘了开 gzip。
  9. 如果有 HTTP/2,启用 HPACK 压缩并上报混合内容警告。如果使用了 LTS,那么请打开 OCSP 装订。
  10. 如果可能,将静态资源(包括字体、样式、脚本和图片等)尽可能多地在 service worker 里缓存起来。

Huge thanks to Yoav Weiss, Addy Osmani, Artem Denysov, Denys Mishunov, Ilya Pukhalski, Jeremy Wagner, Colin Bendell, Mark Zeman, Patrick Meenan, Leonardo Losoviz, Guy Podjarny, Andy Davies, Rachel Andrew, Anselm Hannemann, Patrick Hamann, Andy Davies, Tim Kadlec, Rey Bango, Matthias Ott, Mariana Peralta, Philipp Tellis, Ryan Townsend, Mohamed Hussain S H, Jacob Groß, Tim Swalling, Bob Visser, Kev Adamson and Rodney Rehm for reviewing this article, as well as our fantastic community, which has shared techniques and lessons learned from its work in performance optimization for everybody to use. You are truly smashing!

Built with Hugo
Theme Stack designed by Jimmy