JavaScript 框架中的资源内嵌

改进整个 JavaScript 生态系统中的 Largest Contentful Paint。

作为 Aurora 项目的一部分,Google 一直致力于 框架,以确保它们根据核心网页指标表现良好。Angular 和 Next.js 已实现字体内嵌功能,这在本文的第一部分中进行了说明。第二个 优化,我们将介绍关键的 CSS 内联功能,Angular CLI 中现已默认启用 CSS 内联功能 并且目前正在 Nuxt.js 中实现。

字体内嵌

在对数百款应用进行分析后,Aurora 团队发现开发者通常会将字体 通过在 index.html<head> 元素中引用它们来实现。下面是一个示例 添加 Material 图标后的显示效果:

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

虽然此模式完全有效且功能正常,但它会阻止应用的渲染 并引入一个额外请求。为了更好地了解具体情况,请查看源代码 上述 HTML 中引用的样式表的代码:

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

请注意 font-face 定义如何引用 fonts.gstatic.com 上托管的外部文件。 加载应用时,浏览器首先必须下载引用的原始样式表

<ph type="x-smartling-placeholder">
</ph> 显示网站如何向服务器发出请求并下载外部样式表的图片
首先,网站加载字体样式表。

接下来,浏览器下载了 woff2 文件,最后才能够继续渲染 应用。

<ph type="x-smartling-placeholder">
</ph> 显示所发出的两个请求的图片,一个用于字体样式表,另一个用于字体文件。
接下来,系统会发出加载字体的请求。

一个优化机会是在构建时下载初始样式表,并将其内嵌在 index.html。这样可在运行时跳过到 CDN 的整个往返,从而缩短阻塞时间。

构建应用时,系统会将请求发送到 CDN,从而提取样式表并内嵌 将它添加到 HTML 文件中,并向网域添加 <link rel=preconnect>。通过应用这种方法, 将得到以下结果:

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

Next.js 和 Angular 中现已提供字体内嵌功能

当框架开发者在底层工具中实现优化时, 现有应用和新应用,从而为整个生态系统带来改善。

在 Next.js v10.2 和 Angular v11 中,此改进功能默认处于启用状态。两者均支持 内嵌 Google 和 Adobe 字体。Angular 预计会在 v12.2 中推出后者。

您可以在 GitHub 上找到 Next.js 字体内嵌的实现,还可以观看在 Angular 环境中解释这项优化的视频

内嵌关键 CSS

另一项增强功能涉及通过内嵌关键 CSS 来提高 First Contentful Paint (FCP)Largest Contentful Paint (LCP) 指标。网页的关键 CSS 包括 初始呈现时使用的所有样式如需详细了解该主题,请访问 推迟非关键 CSS

我们发现,许多应用会同步加载样式,这会阻止应用 呈现。一种快速解决方法是异步加载样式。您不必使用 media="all" 时,将 media 属性的值设置为 print,在加载完成后, 将属性值替换为 all

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

不过,这种做法会导致没有样式的内容闪烁。

<ph type="x-smartling-placeholder">
</ph>
样式加载时,网页似乎会闪烁。

上面的视频展示了如何呈现以异步方式加载其样式的网页。闪烁 这是因为浏览器首先开始下载样式,然后呈现 。浏览器下载样式后,就会触发 link 元素的 onload 事件, 将 media 属性更新为 all,并将样式应用于 DOM。

在呈现 HTML 和应用样式期间,网页会部分取消样式。 当浏览器使用样式时,我们会看到闪烁,这不仅会造成糟糕的用户体验,还会导致 Cumulative Layout Shift (CLS) 中的回归问题。

关键的 CSS 内嵌以及异步样式加载可以提高 加载行为critters 工具可找出哪些样式 方法是查看样式表中的选择器并将其与 HTML 进行匹配。 找到匹配的样式后,它会将相应样式视为关键 CSS 的一部分,并且 。

让我们看看以下示例:

错误做法
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

内嵌之前的示例。

在上面的示例中,动物将读取并解析 styles.css 的内容,然后解析 将两个选择器与 HTML 进行匹配,并发现我们使用了 section button.primary。 最后,动物将在页面的 <head> 中内嵌相应的样式,从而生成:

正确做法
<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

内嵌后的示例。

在 HTML 中内嵌关键 CSS 后,您就会发现页面的闪烁问题已消失:

<ph type="x-smartling-placeholder">
</ph>
在 CSS 内嵌之后加载的页面。

Angular 中现提供关键 CSS 内嵌功能,并且在 v12 中默认启用。如果您使用的是 v11 可以通过在 angular.jsoninlineCritical 属性设置为 true 来将其开启。接收者 在 Next.js 中选择启用这项功能,并将 experimental: { optimizeCss: true } 添加到 next.config.js

总结

在这篇博文中,我们谈到了 Chrome 和 Web 框架之间的一些协作。如果您是 框架作者,并认识到我们在你们的技术中解决的一些问题,希望我们的 可以启发您采用类似的效果优化措施。

详细了解改进。您可以在发布商学院中 在 Aurora 简介一文中介绍了我们针对核心网页指标所做的优化工作。