JavaScript 架構中的內嵌資源

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

Aurora 專案中,Google 一直與熱門的網路架構合作,確保這些架構能根據Core Web Vitals 的規定順利運作。Angular 和 Next.js 均已放入字型內嵌,詳情請參閱本文第一部分。我們將介紹的第二項最佳化方式是 CSS 內嵌,現在 Angular CLI 會預設啟用這項功能,並在 Nuxt.js 中實作中。

字型內嵌

在分析數百個應用程式後,Aurora 團隊發現開發人員經常在應用程式中加入字型,方法是在 index.html<head> 元素中參照字型。以下是加入 Material Icons 後的範例:

<!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 10.2 和 Angular 11 會啟用這項改善功能。兩者都支援內嵌 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 內嵌以及非同步樣式載入,都能改善載入行為。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 {
  /* ... */
}

內嵌前範例。

在上例中,Critters 會讀取並剖析 styles.css 的內容,接著將兩個選取器與 HTML 比對,並發現我們使用 section button.primary。最後,Critters 會在頁面的 <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 後,您會發現網頁的閃爍現象已消失:

CSS 內嵌後的網頁載入畫面。

您現在可以在 Angular 中使用 Critical CSS 內嵌功能,且在 v12 中預設為啟用。如果您使用的是 v11,請在 angular.jsoninlineCritical 屬性設為 true,如要在 Next.js 中啟用這項功能,請將 experimental: { optimizeCss: true } 新增至 next.config.js

結論

在這篇文章中,我們談到一些 Chrome 與網路架構之間的合作。如果您是架構作者,並認同我們在技術中解決的部分問題,希望我們的發現能激勵您採用類似的效能最佳化方式。

進一步瞭解改善項目。如要查看我們為 Core Web Vitals 進行的完整最佳化工作,請參閱「Aurora 簡介」一文。