JavaScript 架構中的內嵌資源

改善 JavaScript 生態系統中的最大內容繪製。

Aurora 專案中,Google 一直與熱門的網頁架構合作,確保這些架構根據網站體驗核心指標打造良好效能。Angular 和 Next.js 已內嵌字型,詳情請參閱本文的第一部分。我們介紹的第二個最佳化是重要的 CSS 內嵌,現在在 Angular CLI 中預設為啟用,並在 Nuxt.js 中實作。

字型內嵌

在分析數百個應用程式後,Aurora 團隊發現開發人員經常會在 index.html<head> 元素中參照字型,藉此在應用程式中加入字型。以下是加入質感設計圖示後的顯示效果範例:

<!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 上代管的外部檔案。載入應用程式時,瀏覽器必須先下載標題中參照的原始樣式表。

這張圖片顯示網站如何向伺服器發出要求並下載外部樣式表
首先,網站會載入字型樣式表。

接著,瀏覽器會下載 woff2 檔案,最後再繼續轉譯應用程式。

這張圖片顯示兩個要求:一個用於字型樣式表,另一個用於字型檔案。
接著,系統會發出載入字型的要求。

有最佳化的機會,是在建構時下載初始樣式表,並將其內嵌在 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 預計會在 12.2 版中導入後者。

您可以在 GitHub 找到 Next.js 中的字型內嵌實作,並觀看這部說明 Angular 的最佳化說明影片

內嵌重要的 CSS

此外,還包括內嵌重要的 CSS,藉此改善首次內容繪製 (FCP)最大內容繪製 (LCP) 指標。網頁上重要的 CSS 包含初始轉譯時使用的所有樣式。如要進一步瞭解該主題,請參閱延遲不重要的 CSS

我們發現許多應用程式都是以同步方式載入樣式,導致應用程式無法顯示。快速解決問題的方法:以非同步方式載入樣式。不要使用 media="all" 載入指令碼,而是將 media 屬性的值設為 print,並在載入完成後,將屬性值取代為 all

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

不過,這種做法可能會導致未設定樣式的內容閃爍。

載入樣式時,網頁似乎會閃爍。

上方影片顯示了以非同步方式載入網頁樣式的呈現方式。瀏覽器會先開始下載樣式,然後轉譯接著出現的 HTML,因此閃爍畫面會發生這種情況。瀏覽器下載樣式後,會觸發連結元素的 onload 事件、將 media 屬性更新為 all,然後將樣式套用至 DOM。

從顯示 HTML 到套用樣式的期間,頁面的部分樣式不會改變。瀏覽器使用這些樣式時,我們會看到畫面閃爍,這會造成使用者體驗不佳,並會導致累計版面配置位移 (CLS) 出現迴歸情形。

「重要 CSS 內嵌」搭配非同步樣式載入,可以改善載入行為。條件工具可查看樣式表中的選取器並與 HTML 進行比對,藉此找出網頁中使用的樣式。找到相符項目時,Google 會將對應的樣式視為重要 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>

內嵌後的範例。

將重要的 CSS 嵌入 HTML 後,您會發現網頁閃爍的情形如下:

網頁在 CSS 內嵌後載入。

重要 CSS 內嵌現已在 Angular 提供,且在 v12 中預設為啟用。如果您採用第 11 版,請在 angular.jsoninlineCritical 屬性設為 true,即可開啟。如要在 Next.js 中啟用這項功能,請將 experimental: { optimizeCss: true } 新增至 next.config.js

結論

我們在這篇文章中探討了 Chrome 和網路架構之間的部分合作方式。如果您是架構作者,且瞭解我們在您的技術中遇到的一些問題,希望我們的發現可以啟發您採行類似的效能最佳化措施。

進一步瞭解改善項目。您可以參閱「Aurora 簡介」一文,瞭解我們對 Core Web Vitals 所做的任何最佳化工作。