在 2018 年 Google IO 大会上,我们介绍了一系列工具、库和优化技术,可帮助您更轻松地提升网站性能。在本视频中,我们将通过 Oodles Theater 应用来介绍这些功能。我们还将介绍我们在预测性加载和新推出的 Guess.js 计划方面的实验。
过去一年来,我们一直在努力寻找如何让网络变得更快、性能更出色的方法。这促成了我们在本文中与您分享的新工具、方法和库。在第一部分,我们将向您展示在开发 Oodles Theater 应用时实际使用的一些优化技术。在第二部分,我们将介绍我们对预测性加载和新 Guess.js 计划开展的实验。
对性能的需求
互联网的使用量与日俱增。如果我们查看网络状况,就会发现移动设备上的网页平均大小约为 1.5MB,其中大部分是 JavaScript 和图片。
网站不断增大,再加上网络延迟时间、CPU 限制、呈现阻塞模式或多余的第三方代码等其他因素,都导致了复杂的性能难题。
大多数用户都将速度视为其需求用户体验层次结构的最顶层。这并不奇怪,因为在网页加载完成之前,您实际上无法执行太多操作。您无法从该页面中获得价值,也无法欣赏其美感。
我们知道,性能对用户至关重要,但对于如何开始优化,这也可能是秘密。幸运的是,有一些工具可以帮助您完成这项工作。
Lighthouse - 性能工作流的基础
Lighthouse 是 Chrome DevTools 的一部分,可让您对网站进行审核,并提供有关如何改进网站的提示。
我们最近推出了一系列新的性能审核,这些审核在日常开发工作流中非常有用。
我们通过一个实用示例来探索如何利用这些功能:Oodles Theater 应用。这是一个小型演示版 Web 应用,您可以在其中试用一些我们喜爱的互动式 Google 涂鸦,甚至可以玩一两款游戏。
在构建应用时,我们希望确保其性能尽可能高。优化起点是 Lighthouse 报告。
Lighthouse 报告显示,我们应用的初始性能非常糟糕。 在 3G 网络上,用户需要等待 15 秒,以便进行首次有意义的绘制,或让应用进入可互动状态。Lighthouse 突出指出了我们网站存在的大量问题,而总体性能得分为 23 也正反映了这一点。
该网页的重量约为 3.4MB,我们迫切需要精简一些内容。
这引发了我们面临的第一个性能挑战:找出可以轻松移除且不会影响整体体验的内容。
性能优化建议
移除不必要的资源
有一些显而易见的内容可以放心地移除:空白和评论。
Lighthouse 在非精简版 CSS 和 JavaScript 审核中重点介绍了这一机会。我们在构建流程中使用的是 webpack,因此为了缩减大小,我们只需使用 Uglify JS 插件。
缩减是常见任务,因此您应该能够找到适用于您所用 build 流程的现成解决方案。
该领域的另一项实用审核是启用文本压缩。没有理由发送未压缩的文件,而且目前大多数 CDN 都支持开箱即用。
我们之前使用 Firebase Hosting 托管代码,而 Firebase 默认启用 gzip 压缩,因此纯粹是在合理的 CDN 上托管代码后,我们就可以免费获得这项服务。
虽然 gzip 是一种非常流行的压缩方式,但 Zopfli 和 Brotli 等其他机制也越来越受欢迎。Brotli 可在大多数浏览器中提供支持,您可以使用二进制文件对资源进行预压缩,然后再将其发送到服务器。
使用高效的缓存政策
我们的下一步是确保在不必要的情况下不会发送资源两次。
通过 Lighthouse 中的缓存效率不佳政策审核,我们发现可以优化缓存策略,以实现这一目标。通过在我们的服务器中设置 max-age 到期标头,我们可确保用户在重复访问时可以重复使用之前下载的资源。
在理想的情况下,您的目标应该是尽可能安全地缓存尽可能多的资源,并缓存尽可能长的时间,并且提供验证令牌,以便高效地重新验证已更新的资源。
移除未使用的代码
到目前为止,我们移除了不必要下载内容中明显的部分,但不太明显的部分呢?例如,未使用的代码。
有时,我们会在应用代码中添加一些实际上并不必要的代码。尤其是在您使用应用更长时间时,您的团队或依赖项会发生变化,并且有时会出现孤立的库。我们也正是这么做的。
最初,我们使用 Material Components 库快速制作了应用的原型。随着时间的推移,我们改用更具自定义外观和风格的设计,完全忘记了该库。幸运的是,代码覆盖率检查帮助我们在软件包中重新发现了它。
您可以在 DevTools 中查看应用的运行时和加载时间的代码覆盖率统计数据。您可以在底部的屏幕截图中看到两个大红条 - 我们的 CSS 有超过 95% 未使用,JavaScript 也是如此。
Lighthouse 在未使用的 CSS 规则审核中也发现了此问题。结果显示,潜在节省量超过 400kb。因此,我们返回到了代码,并移除了该库的 JavaScript 和 CSS 部分。
这样一来,我们的 CSS 软件包就缩小了 20 倍,对于一个只有两行代码的小型提交来说,这已经相当不错了。
当然,这提高了我们的性能得分,而且可交互时间也变得更好。
不过,对于此类更改,仅查看指标和得分是不够的。移除实际代码绝无风险,因此您应始终留意潜在的回归情况。
95% 的代码都没有用到,但仍有 5% 的代码在某个地方用到了。显然,我们的一个组件仍在使用该库中的样式,即涂鸦滑块中的小箭头。不过,由于它很小,我们可以直接手动将这些样式重新整合到按钮中。
因此,如果您移除代码,只需确保您已建立适当的测试工作流,以帮助您防范潜在的视觉回归问题。
避免巨大的网络载荷
我们知道,大型资源可能会减慢网页加载速度。这可能会给用户带来费用,并对其流量套餐产生重大影响,因此请务必注意这一点。
Lighthouse 使用网络载荷过大审核功能检测到,我们的某些网络载荷存在问题。
在这里,我们发现有超过 3MB 的代码要下发,这非常多,尤其是在移动设备上。
在此列表的最顶部,Lighthouse 突出显示了我们有一个 JavaScript 供应商软件包,其中包含 2MB 的未压缩代码。这也是 webpack 强调的问题。
俗话说:最快的请求是根本不发出请求。
理想情况下,您应衡量向用户分发的每个资源的价值,衡量这些资源的效果,并确定是否值得将其实际分发到初始体验中。因为有时这些资源可能会在空闲时间被延迟、延迟加载或处理。
在我们的示例中,由于我们要处理大量 JavaScript 软件包,因此很幸运的是,JavaScript 社区提供了丰富的 JavaScript 软件包审核工具。
我们首先使用了 webpack bundle analyzer,它告知我们,我们包含了一个名为 unicode 的依赖项,该依赖项包含 1.6MB 的解析 JavaScript,因此非常大。
然后,我们转到编辑器,使用 Visual Studio Code 的导入成本插件,直观地查看我们要导入的每个模块的成本。这样,我们就可以发现哪个组件包含引用此模块的代码。
然后,我们改用另一种工具 BundlePhobia。借助此工具,您可以输入任何 NPM 软件包的名称,并实际查看其经过缩减和压缩后的估算大小。我们找到了一个非常适合我们所用 Slug 模块的替代方案,该替代方案只有 2.2kb,因此我们改用该方案。
这对我们的广告效果产生了很大影响。在进行这项更改并发现其他缩减 JavaScript 软件包大小的机会后,我们节省了 2.1MB 的代码。
考虑到这些软件包经过 GZIP 压缩和缩减后的大小,我们总体上实现了 65% 的改进。 我们发现,这个过程非常值得一试。
因此,一般来说,请尽量避免在网站和应用中提供不必要的下载内容。对素材资源进行清单管理并衡量其对效果的影响会产生很大的影响,因此请务必定期审核素材资源。
通过代码拆分缩短 JavaScript 启动时间
虽然较大的网络载荷可能会对应用产生重大影响,但还有另一个因素可能会产生非常大的影响,那就是 JavaScript。
JavaScript 是最昂贵的资源。在移动设备上,如果您发送大量的 JavaScript 软件包,可能会延迟用户与界面组件交互的时间。这意味着,用户可以点按界面,但实际上不会发生任何有意义的事情。因此,我们有必要了解 JavaScript 的开销如此之高的原因。
这就是浏览器处理 JavaScript 的方式。
首先,我们必须下载该脚本,然后我们有一个 JavaScript 引擎,该引擎需要解析该代码、编译并执行该代码。
现在,在台式机或笔记本电脑等高端设备上,甚至是高端手机可以完成这些阶段,无需花费太多时间。但在平均水平的手机上,此过程可能需要多 5 到 10 倍的时间。这会延迟互动,因此我们必须尽量缩减此类内容。
为帮助您发现应用存在的这些问题,我们在 Lighthouse 中引入了新的 JavaScript 启动时间审核。
在 Oodle 应用的情况下,它告诉我们 JavaScript 启动花费了 1.8 秒的时间。当时的情况是,我们将所有路由和组件静态导入到一个单体 JavaScript 软件包中。
解决此问题的一种方法是使用代码分块。
代码分块的概念是,如果您不向用户提供一整块 JavaScript,而是根据他们的需要一次只提供一片,那会怎么样?
代码拆分可在路线级别或组件级别应用。它非常适合与 React 和 React Loadable、Vue.js、Angular、Polymer、Preact 和许多其他库搭配使用。
我们将代码分块功能纳入了应用,并从静态导入切换到了动态导入,从而能够根据需要异步延迟加载代码。
这不仅缩减了软件包的大小,还缩短了 JavaScript 的启动时间。缩短到 0.78 秒,使应用的速度提高了 56%。
一般来说,如果您要构建大量使用 JavaScript 的体验,请务必仅向用户发送他们需要的代码。
利用代码拆分等概念,探索摇树优化等概念,并查看 webpack-libs-optimizations 代码库,了解在使用 webpack 时如何缩减库大小的技巧。
优化图像
在 Oodle 应用中,我们使用了大量图片。遗憾的是,Lighthouse 对它的热情不如我们。事实上,我们在与图片相关的所有三项审核中都未通过。
我们忘了优化图像,没有正确调整图像大小,而且还可以通过使用其他图像格式获得一些收益。
我们首先优化了图片。
对于一次性优化,您可以使用 ImageOptim 或 XNConvert 等可视化工具。
更自动化的方法是使用 imagemin 等库,将图片优化步骤添加到构建流程中。
这样,您就可以确保日后添加的图片会自动进行优化。某些 CDN(例如 Akamai)或第三方解决方案(例如 Cloudinary、Fastly 或 Uploadcare)可为您提供全面的图片优化解决方案,因此您也可以直接在这些服务上托管您的图片。
如果您因成本或延迟问题而不想这样做,Thumbor 或 Imageflow 等项目提供了自托管替代方案。
我们的背景 PNG 在 webpack 中被标记为大小过大,这是正确的。在将其正确调整为视口大小并通过 ImageOptim 运行后,我们将其缩减到了 100kb,这是一个可接受的大小。
对网站上的多张图片重复上述操作,可以显著降低整体页面重量。
使用正确的格式动画内容
GIF 的费用可能会很高。令人惊讶的是,GIF 格式从一开始就不是用作动画平台。因此,切换到更合适的视频格式可以大大缩减文件大小。
在 Oodle 应用中,我们在首页上使用 GIF 作为片头序列。根据 Lighthouse 的分析,如果改用更高效的视频格式,我们可以节省超过 7MB 的空间。我们的剪辑大小约为 7.3MB,对于任何合理的网站来说都太大了,因此我们将其转换为一个视频元素,并添加了两个源文件(一个 MP4 文件和一个 WebM 文件),以便获得更广泛的浏览器支持。
我们使用 FFmpeg 工具将动画 GIF 转换为 mp4 文件。 WebM 格式可让您节省更多空间 - ImageOptim API 可以为您执行此类转换。
ffmpeg -i animation.gif -b:v 0 -crf 40 -vf scale=600:-1 video.mp4
得益于这种转换,我们总体重量减少了 80% 以上。这样一来,我们的大小就缩减到了 1MB 左右。
不过,1MB 还是一个推送到线下较大的资源,对于带宽受限的用户来说更是如此。幸运的是,我们可以使用 Effective Type API 来了解他们的带宽较慢,并改为向他们提供体积更小的 JPEG。
此接口使用有效往返时间和服务中断值来估算用户正在使用的网络类型。它只会返回一个字符串,即“2G 网络(速度缓慢)”“2G”“3G”或“4G”。因此,如果用户使用的是低于 4G 的网络,我们可以根据此值将视频元素替换为图片。
if (navigator.connection.effectiveType) { ... }
这确实会影响用户体验,但至少在连接速度缓慢时,用户仍可使用该网站。
延迟加载屏幕外图片
轮播界面、滑块或非常长的网页通常会加载图片,即使用户无法立即在网页上看到它们也是如此。
Lighthouse 会在屏幕外图片审核中标记此行为,您也可以在 DevTools 的“Network”(网络)面板中自行查看。如果您看到大量图片正在传入,但只有少数图片显示在页面上,则表示您不妨考虑改为延迟加载这些图片。
浏览器尚不支持原生延迟加载,因此我们必须使用 JavaScript 来添加此功能。我们使用 Lazysizes 库向 Oodle 封面添加了延迟加载行为。
<!-- Import library -->
import lazysizes from 'lazysizes' <!-- or -->
<script src="lazysizes.min.js"></script>
<!-- Use it -->
<img data-src="image.jpg" class="lazyload"/>
<img class="lazyload"
data-sizes="auto"
data-src="image2.jpg"
data-srcset="image1.jpg 300w,
image2.jpg 600w,
image3.jpg 900w"/>
Lazysizes 非常智能,因为它不仅会跟踪元素的可见性变化,还会主动预提取靠近视图的元素,以提供最佳用户体验。它还提供了可选的 IntersectionObserver
集成,可让您非常高效地查询可见性。
此项更改生效后,我们的图片将按需提取。如果您想更深入地了解该主题,请参阅 images.guide - 一项非常便捷且全面的资源。
帮助浏览器提前提供关键资源
通过网络发送到浏览器的每个字节并非都具有同等重要性,浏览器深谙此道。许多浏览器都采用启发词语来决定应先提取哪些内容。因此,有时它们会先提取 CSS,然后再提取图片或脚本。
作为网页的作者,我们可以告知浏览器哪些内容对我们来说非常重要,这可能会很有用。值得庆幸的是,在过去几年里,浏览器供应商一直在添加一些功能来帮助我们实现这一点,例如 link rel=preconnect
、preload
或 prefetch
等资源提示。
这些功能为 Web 平台带来了强大助力,可帮助浏览器在适当的时间提取正确的内容,并且比使用脚本实现的一些基于逻辑的自定义加载方法效率更高。
我们来看看 Lighthouse 如何实际引导我们有效使用其中的一些功能。
Lighthouse 首先告知我们要避免多次往返任何来源,因为这会产生高昂的费用。
在 Oodle 应用中,我们实际上大量使用了 Google Fonts。每当您将 Google Fonts 样式表放入网页时,它都会连接到两个子网域。Lighthouse 告诉我们,如果能够预热该连接,我们最多可以缩短 300 毫秒的初始连接时间。
利用链接 rel 预连接,我们可以有效掩盖该连接延迟时间。
尤其是对于 Google Fonts 等资源,我们的字体 CSS 托管在 googleapis.com 上,而字体资源托管在 Gstatic 上,这可能会产生很大的影响。因此,我们应用了此优化,并缩短了几百毫秒的时间。
Lighthouse 的下一项建议是,我们预加载关键请求。
<link rel=preload>
非常强大,它会告知浏览器当前导航需要某个资源,并尝试让浏览器尽快提取该资源。
现在,Lighthouse 指示我们应预加载关键的网页字体资源,因为我们要使用两种网络字体进行加载。
在 Web 字体中预加载的代码如下所示:指定 rel=preload
后,您将 as
与字体类型一起传入,然后指定您尝试加载的字体类型,例如 woff2。
这对您的网页的影响非常明显。
通常,在不使用 link rel preload 的情况下,如果网页字体恰好对您的网页至关重要,浏览器首先必须提取您的 HTML,解析您的 CSS,然后最终将提取您的网页字体。
使用 link rel 预加载时,一旦浏览器解析了您的 HTML,它实际上可以更早地开始提取这些网页字体。对于我们的应用,这能够将我们使用网络字体渲染文本所需的时间缩短了一秒。
现在,如果您要使用 Google Fonts 预加载字体,就没那么简单了,有个问题所在。
我们在样式表中为字体指定的 Google 字体网址恰好是字体团队定期更新的网址。这些网址可能会过期或定期更新,因此,如果您想完全控制字体加载体验,我们建议您自行托管 Web 字体。这非常有用,因为它可以让您使用链接 rel preload 等功能。
在我们的案例中,我们发现 Google Web Fonts Helper 非常有用,它可以帮助我们离线使用某些 Web 字体并在本地进行设置,因此请试用该工具。
无论您是将 Web 字体用作关键资源的一部分,还是将 JavaScript 用作关键资源,请尽量帮助浏览器尽快提供关键资源。
实验性功能:优先级提示
今天,我们有特别的内容要与您分享。除了资源提示和预加载等功能外,我们还在开发一项全新的实验性浏览器功能,称为优先提示。
这是一项新功能,可让您向浏览器提示资源的重要性。它会公开一个新属性 - importance,其值为 low、high 或 auto。
这样,我们就可以降低不太重要的资源(例如非关键样式、图片或提取 API 调用)的优先级,以减少争用。我们还可以提升更重要内容(例如主打图片)的优先级
在 Oodle 应用中,这实际上让我们找到了一个实用且可优化的方面。
在为图片添加延迟加载之前,浏览器的做法是,我们有一个包含所有涂鸦的图片轮播界面,浏览器会在轮播界面开始时以高优先级提前提取所有图片。遗憾的是,对用户而言,轮播界面中间部分的图片才是最重要的。我们将这些背景图片的重要性设为非常低,前景图片的重要性设为非常高,这样一来,在速度缓慢的 3G 网络下,我们能够在 2 秒内提取和渲染这些图片。因此,这是一个非常好的积极体验。
我们希望在几周内将此功能引入 Canary 版,敬请期待。
具有网页字体加载策略
排版是优质设计的基础,如果您使用的是 Web 字体,最好不要阻止文本呈现,也绝对不要显示不可见文本。
现在,我们在 Lighthouse 中通过避免在网页字体加载时显示不可见文本审核来突出显示这一点。
如果您使用字体样式块加载网页字体,则表示您让浏览器决定在网页字体提取花费很长时间时该怎么做。有些浏览器会等待最多 3 秒钟,然后才会回退到系统字体,并最终在下载字体后将其替换为该字体。
我们会尽量避免这种不可见的文字,因此在本例中,如果网络字体加载时间过长,我们将无法看到本周的经典涂鸦。幸运的是,有了名为 font-display
的新功能,您实际上可以更好地控制此过程。
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-display: swap;
font-weight: 400;
src: local('Montserrat Regular'), local('Montserrat-Regular'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('montserrat-v12-latin-regular.woff2') format('woff2'),
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('montserrat-v12-latin-regular.woff') format('woff');
}
字体显示有助于您根据 Web 字体切换所需的时间来决定 Web 字体的呈现方式或回退方式。
在本例中,我们使用的是字体显示切换。“swap”为字体指定零秒的阻止期,以及无限的交换期。这意味着,如果字体需要一些时间才能加载完毕,浏览器会立即使用后备字体绘制文本。字体可用后,系统会进行切换。
在我们的应用中,这样做非常有用,因为它让我们能够在很早的阶段显示一些有意义的文本,并在 Web 字体准备就绪后切换到 Web 字体。
一般来说,如果您恰好使用的是网页字体(就像大部分网站所做的那样),请采用良好的网页字体加载策略。
有许多网络平台功能可用于优化字体的加载体验,您也可以查看 Zach Leatherman 的 Web Font Recipes 代码库,因为它真的很棒。
减少阻塞渲染的脚本
我们可以在下载链的早期阶段推送应用的其他部分,以便至少提前一点提供一些基本的用户体验。
在 Lighthouse 时间轴条上,您可以看到在前几秒当所有资源都处于加载状态期间,用户实际上看不到任何内容。
下载和处理外部样式表阻止了我们的渲染进程取得任何进展。
我们可以尝试提前一点提交某些样式,以优化关键渲染路径。
如果我们提取负责此初始渲染的样式并将其内嵌到 HTML 中,浏览器便可立即渲染这些样式,而无需等待外部样式表到达。
在我们的示例中,我们使用了一个名为 Critical 的 NPM 模块,在构建步骤期间将关键内容内嵌到 index.html 中。
虽然此模块为我们完成了大部分繁重工作,但要让其在不同路线上顺利运行,仍然有点棘手。
如果您不够谨慎,或者您的网站结构非常复杂,那么如果您没有从一开始就规划 App Shell 架构,那么可能很难引入这种类型的模式。
因此,请务必尽早考虑性能问题。如果您从一开始就没有考虑性能,那么日后很可能会遇到性能问题。
最终,我们的冒险得到了回报,我们设法使其正常运行,应用开始提前提供内容,从而显著缩短了首次有效绘制时间。
结果
以上是我们对网站应用的一系列性能优化措施。我们来看看结果。这是我们的应用在 3G 网络上中型移动设备上的加载情况,优化前后对比。
Lighthouse 性能得分从 23 提高到了 91。就速度而言,这是相当不错的进步我们之所以能够做出这些改变,是因为我们一直在持续检查和跟踪 Lighthouse 报告。如果您想了解我们是如何从技术层面实现所有改进的,请随时查看我们的代码库,尤其是与该代码库有关的 PR。
预测性性能 - 数据驱动的用户体验
我们认为,机器学习在许多领域都代表着未来的巨大机遇。 我们希望未来能够激发更多的实验者,那就是真实数据可以真正指导我们正在打造的用户体验。
目前,我们会根据用户可能想要或需要的内容做出许多任意决策,从而确定哪些内容值得预提取、预加载或预缓存。如果我们猜得正确,我们能够优先考虑少量资源,但很难将其扩展到整个网站。
我们目前已经有数据可供参考,以便更好地进行优化。使用 Google Analytics Reporting API,我们可以查看网站上任何网址的下一个热门网页和退出百分比,从而得出关于应优先处理哪些资源的结论。
如果将此方法与良好的概率模型相结合,我们就可以通过积极过度预提取内容来避免浪费用户的数据。我们可以利用这些 Google Analytics 数据,并使用机器学习和马尔可夫链或神经网络等模型来实现此类模型。
为了便于进行这些实验,我们很高兴地宣布推出一项名为 Guess.js 的新计划。
Guess.js 是一个专注于为 Web 提供数据驱动型用户体验的项目。我们希望这项研究能激励您探索如何使用数据来提升网站性能,并取得更出色的成效。目前,它都是开源的,可在 GitHub 上获取。该应用由 Minko Gechev、Gatsby 的 Kyle Matthews、Katie Hempenius 等众多开源社区联合构建。
请查看 Guess.js,并告诉我们您的想法。
摘要
得分和指标有助于提升 Web 速度,但它们只是手段,本身不是目标。
我们都曾遇到过随时随地页面加载缓慢的经历,但现在我们有机会为用户提供更愉悦且加载迅速的体验。
提升广告效果是一项长期的任务。很多小变化可以带来巨大成效。通过使用合适的优化工具并密切关注 Lighthouse 报告,您可以为用户提供更好、更具包容性的体验。
特别鸣谢:Ward Peeters、Minko Gechev、Kyle Mathews、Katie Hempenius、Dom Farolino、Yoav Weiss、Susie Lu、Yusuke Utsunomiya、Tom Ankers、Lighthouse 和 Google 涂鸦。