最佳化資源載入作業

在先前的單元中,我們探討了關鍵轉譯路徑背後的一些理論,以及會阻擋轉譯的封鎖功能和剖析器資源如何延遲網頁的初始轉譯作業。現在您已瞭解這個理論的一些理論,您現在可以學習一些最佳化重要算繪路徑的技巧。

載入網頁時,HTML 中參照的許多資源會透過 CSS 提供網頁的外觀和版面配置,並透過 JavaScript 提供網頁互動功能。在本單元中,我們會討論一些與這些資源相關的重要概念,以及它們對網頁載入時間的影響。

禁止轉譯

上一個模組所述,CSS 是「算繪封鎖」資源,會在建構 CSS 物件模型 (CSSOM) 之前禁止瀏覽器顯示任何內容。瀏覽器會封鎖轉譯作業,以避免發生 Flash 未設定樣式的內容 (FOUC),這不適合使用者體驗。

在上部影片中,這段影片提供簡短的 FOUC 連結,表示您可以瀏覽頁面,沒有任何樣式。接著,當網頁的 CSS 從網路載入完畢時,就會套用所有樣式,而無樣式的網頁版本會立即替換成樣式版本。

一般來說,FOUC 是您平常不會看到的內容,但這個概念很重要,以便您瞭解瀏覽器在下載 CSS 並套用至頁面之前,會封鎖轉譯網頁的原因「為何」。轉譯封鎖功能不一定是理想的情況,但建議您保持 CSS 最佳化,盡可能縮短持續時間。

剖析器封鎖

剖析器封鎖資源會中斷 HTML 剖析器,例如不含 asyncdefer 屬性的 <script> 元素。當剖析器遇到 <script> 元素時,瀏覽器必須先評估並執行指令碼,才能繼續剖析 HTML 的其他部分。這是一種設計,因為指令碼可以在建構期間修改或存取 DOM。

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

使用外部 JavaScript 檔案 (不含 asyncdefer) 時,系統會持續封鎖剖析器,直到檔案下載、剖析和執行為止。使用內嵌 JavaScript 時,剖析器會同樣遭到封鎖,直到系統剖析並執行內嵌指令碼為止。

預先載入掃描器

預先載入掃描器是以次要 HTML 剖析器的形式呈現瀏覽器最佳化作業,可掃描原始 HTML 回應,在主要 HTML 剖析器發現之前,先尋找並推測擷取的資源。舉例來說,即使在擷取及處理 CSS 和 JavaScript 等資源時封鎖 HTML 剖析器,預先載入掃描器仍會允許瀏覽器開始下載 <img> 元素中指定的資源。

如要利用預先載入掃描器,伺服器傳送的 HTML 標記應包含重要資源。預先載入掃描器無法找到下列資源載入模式:

  • CSS 使用 background-image 屬性載入的圖片。這些圖片參照位於 CSS,且預先載入掃描器無法找到。
  • 動態載入的指令碼格式為插入 DOM 的 <script> 元素標記,或使用動態 import() 載入的模組。
  • 使用 JavaScript 在用戶端上算繪 HTML。這類標記包含在 JavaScript 資源的字串內,而且無法由預先載入掃描工具找出。
  • CSS @import 宣告。

這些資源載入模式都是所有延遲探索的資源,因此不適用於預先載入掃描工具。請盡可能避免使用這類字元。不過,如果「無法」避免這類模式,也可以使用 preload 提示,避免資源探索作業的延遲情形。

CSS

CSS 會決定網頁的呈現和版面配置。如前文所述,CSS 是禁止轉譯的資源,因此最佳化 CSS 可能會對整體網頁載入時間造成很大的影響。

壓縮

壓縮 CSS 檔案能夠縮減 CSS 資源的檔案大小,加快下載速度。方法是從來源 CSS 檔案 (例如空格和其他隱形字元) 中移除內容,然後將結果輸出至新的最佳化檔案:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

在最基本的格式中,CSS 壓縮是一種有效的最佳化功能,可改善網站的 FCP,在某些情況下甚至可以採用 LCP。Bundler 等工具可以在實際工作環境版本中自動執行這項最佳化作業。

移除未使用的 CSS

瀏覽器必須先下載並剖析所有樣式工作表,才能轉譯任何內容。完成剖析所需的時間也包含目前網頁上未使用的樣式。如果使用的 Bundler 將所有 CSS 資源併入單一檔案,使用者可能會下載更多 CSS,而非轉譯目前頁面所需的數量。

如要找出目前網頁未使用的 CSS,請使用 Chrome 開發人員工具中的涵蓋率工具

Chrome 開發人員工具的涵蓋率工具螢幕截圖。在底部窗格中選取 CSS 檔案,顯示目前網頁版面配置未使用的 CSS 數量。
Chrome 開發人員工具的涵蓋率工具可以有效偵測目前頁面未使用的 CSS (和 JavaScript)。此工具可將 CSS 檔案分割成多個資源,並由不同網頁載入,而不是傳送較大的 CSS 組合,導致網頁轉譯時間延遲。

移除未使用的 CSS 會有雙重效果:除了減少下載時間之外,您也會最佳化算繪樹狀結構的建構方式,因為瀏覽器需要處理的 CSS 規則較少。

避免使用 CSS @import 宣告

雖然這樣看起來很方便,但請避免在 CSS 中宣告 @import

/* Don't do this: */
@import url('style.css');

如同 <link> 元素在 HTML 中的運作方式,CSS 中的 @import 宣告可讓您從樣式表內匯入外部 CSS 資源。這兩種方法的主要差異在於 HTML <link> 元素是 HTML 回應的一部分,因此會比透過 @import 宣告下載的 CSS 檔案更快發現。

這是因為系統如要探索 @import 宣告,您必須先下載包含此宣告的 CSS 檔案。這會導致所謂的「要求鏈」 (在 CSS 中),這會延遲網頁初始轉譯所需的時間。另一個缺點是,預先載入掃描工具無法找到使用 @import 宣告載入的樣式工作表,因此會成為較晚發現會禁止轉譯的資源。

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

在大多數情況下,您可以使用 <link rel="stylesheet"> 元素取代 @import<link> 元素可讓您同時下載樣式表,並縮短整體載入時間,相較於「繼續」下載樣式表的 @import 宣告。

內嵌重要的 CSS

下載 CSS 檔案所需的時間可能會增加網頁的 FCP。在文件 <head> 中內嵌重要樣式後,系統就會移除 CSS 資源的網路要求,如果正確處理正確,則可在未修剪瀏覽器快取的情況下縮短初始載入時間。其他 CSS 可以以非同步方式載入,或在 <body> 元素結尾處附加。

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

不過,內嵌大量的 CSS 則會在初始 HTML 回應中增加更多位元組。由於 HTML 資源通常無法長時間或完全無法快取,這表示如果後續網頁可能會在外部樣式表中使用同一個 CSS,系統就不會快取內嵌的 CSS。測試和評估網頁效能,確保權衡利弊。

CSS 示範

JavaScript

JavaScript 能夠為網路帶來多數的互動功能,但也需付費使用。 JavaScript 過多可能會使網頁在網頁載入時回應緩慢,甚至可能導致回應速度變慢,而這些問題都會讓使用者感到困擾。

禁止轉譯的 JavaScript

載入不含 deferasync 屬性的 <script> 元素時,瀏覽器會封鎖剖析及轉譯功能,直到指令碼下載、剖析和執行為止。同樣地,內嵌指令碼也會封鎖剖析器,直到指令碼剖析和執行為止。

asyncdefer

asyncdefer 可讓外部指令碼在不封鎖 HTML 剖析器的情況下載入,同時系統會自動延遲使用 type="module" 的指令碼 (包括內嵌指令碼)。不過,asyncdefer 有一些差異,請務必瞭解。

說明各種指令碼載入機制,所有依據各種屬性 (如 async、defer、type=&#39;module&#39;) 及上述三種屬性的組合,詳述剖析器、擷取和執行角色。
資料來源:https://html.spec.whatwg.org/multipage/scripting.html

系統會剖析使用 async 載入的指令碼並在下載完成後立即執行,而在 HTML 文件剖析完成後,就會執行透過 defer 載入的指令碼,這種情況與瀏覽器的 DOMContentLoaded 事件也是一樣。此外,async 指令碼可能會以亂序執行,而 defer 指令碼會按照在標記中的顯示順序執行。

用戶端轉譯

一般來說,請避免使用 JavaScript 轉譯任何重要內容或網頁的 LCP 元素。這就是所謂的用戶端轉譯,也是單頁應用程式 (SPA) 廣泛採用的技術。

由 JavaScript 轉譯的標記會側重預先載入掃描器,因為用戶端轉譯標記中包含的資源「無法找到」。這可能會延遲下載重要資源 (例如 LCP 映像檔)。只有在指令碼執行後,瀏覽器才會開始下載 LCP 圖片,並將元素新增至 DOM。因此,只有在發現、下載和剖析指令碼後,系統才會執行指令碼。這就是所謂的「關鍵要求鏈」,應避免使用。

此外,比起從伺服器下載標記來回應導覽要求,使用 JavaScript 轉譯標記較可能產生「長時間工作」。大量使用 HTML 的用戶端轉譯可能會對互動延遲造成負面影響。如果網頁的 DOM 非常龐大,在 JavaScript 修改 DOM 就會觸發大量算繪工作,這一點尤其重要。

壓縮

與 CSS 類似,壓縮 JavaScript 可縮減指令碼資源的檔案大小。這樣可以加快下載速度,讓瀏覽器可以更快開始剖析和編譯 JavaScript。

此外,比起壓縮其他資產 (例如 CSS),壓縮 JavaScript 比壓縮其他資產更進一步。壓縮 JavaScript 時,不僅會去除空格、分頁和註解等項目,還會縮短 JavaScript 中的符號。這項程序有時也稱為「清除」。如要查看差異,請擷取以下 JavaScript 原始碼:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

上述 JavaScript 原始碼如遭到改動,結果可能如以下程式碼片段所示:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

在上述程式碼片段中,您可以看到來源中的人類可讀變數 scriptElement 已縮短為 t。套用到大量指令碼後可節省大量成本,而且不會影響網站實際工作環境 JavaScript 提供的功能。

如果您使用 Bundler 處理網站的原始碼,系統通常會為實際工作環境版本自動進行更新。Uglifier (例如 Terser) 也具有高度設定能力,可讓您調整修正演算法的積極程度,以便達到最大節省量。不過,所有修改工具的預設值通常就足以在輸出大小和保留功能之間取得適當平衡。

JavaScript 示範

學以致用

在瀏覽器中載入多個 CSS 檔案的最佳方式為何?

CSS @import 宣告。
請再試一次。
多個 <link> 元素。
答對了!

瀏覽器預先載入掃描器的作用是什麼?

此為次要 HTML 剖析器,可在 DOM 剖析器之前檢查原始標記來探索資源,以便更快找到資源。
答對了!
偵測 HTML 資源中的 <link rel="preload"> 元素。
請再試一次。

為什麼瀏覽器在下載 JavaScript 資源時,會暫時禁止剖析 HTML?

防止非樣式的 Flash 內容 (FOUC)。
請再試一次。
因為評估 JavaScript 是一項耗用大量 CPU 的工作,而暫停 HTML 剖析功能可讓 CPU 有更多頻寬完成載入指令碼。
請再試一次。
因為指令碼可以修改或以其他方式存取 DOM。
答對了!

下一項:協助瀏覽器提供資源提示

現在,您已瞭解 <head> 元素中載入資源對初始網頁載入和各種指標的影響,接著是時候繼續動起來了。下一個單元將介紹「資源提示」,並說明這些資源如何向瀏覽器提供寶貴的提示,以比瀏覽器早先載入資源及開啟跨來源伺服器的連線。