分析關鍵轉譯路徑效能

Ilya Grigorik
Ilya Grigorik

想要找出並解決關鍵的轉譯路徑效能瓶頸,您需要對常見陷阱有非常瞭解。現在就讓我們開始實作導覽,找出常見的成效模式,協助您將網頁最佳化。

最佳化重要轉譯路徑可讓瀏覽器盡快繪製網頁:較快的頁面轉譯速度可以提高參與度、增加網頁瀏覽量並提高轉換率。為了盡量縮短訪客查看空白畫面的時間,我們必須最佳化要載入的資源和載入順序。

為協助您說明這項程序,我們先從最簡單的情況開始,然後逐步建立網頁,加入其他資源、樣式和應用程式邏輯。我們會在過程中將每個案例調整到最佳狀態,並且也會瞭解哪些部分可能出現錯誤。

到目前為止,我們只關注了瀏覽器在資源 (CSS、JS 或 HTML 檔案) 可供處理後會發生的情況。我們已略過從快取或網路擷取資源所需的時間。假設以下情況:

  • 對伺服器的網路往返 (傳播延遲時間) 會耗費 100 毫秒。
  • HTML 文件的伺服器回應時間為 100 毫秒,所有其他檔案則是 10 毫秒。

Hello World 體驗

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

我們將從基本 HTML 標記和單一圖片開始,不支援 CSS 或 JavaScript。在 Chrome 開發人員工具中開啟網路時間軸,檢查產生的資源瀑布:

CRP

和預期一樣,HTML 檔案的下載時間約為 200 毫秒。請注意,藍色線條的透明部分代表瀏覽器在沒有收到任何回應位元組的情況下,等待網路等待的時間長度,實線部分則代表在收到第一個回應位元組後,完成下載的時間。HTML 下載的檔案大小極小 (<4K),所以我們只需要一次來回移動即可擷取完整檔案。因此,HTML 文件的擷取大約需要 200 毫秒,其中一半的時間花在網路上,另一半則在等待伺服器回應。

當 HTML 內容可供使用時,瀏覽器會剖析位元組、將其轉換為符記並建立 DOM 樹狀結構。請注意,開發人員工具可以輕鬆回報底部 DOMContentLoaded 事件的時間 (216 毫秒),後者也會對應藍色垂直線。HTML 下載結束和藍色垂直線 (DOMContentLoaded) 之間的間隔,就是瀏覽器建構 DOM 樹狀結構所需的時間。

請注意,我們的「優質相片」並未封鎖 domContentLoaded 活動。結果,我們可以在不用等待網頁素材資源的情況下,建構轉譯樹狀結構,甚至在繪製頁面時便不費心:並非所有資源都提供快速首次顯示所需時間的關鍵。事實上,討論的關鍵轉譯路徑時,我們通常討論的是 HTML 標記、CSS 和 JavaScript。圖片不會妨礙網頁初始轉譯,但我們應該可以盡快繪製圖片。

不過,圖片上的 load 事件 (也稱為 onload) 遭到封鎖:開發人員工具會用 335 毫秒回報 onload 事件。提醒您,onload 事件會標示網頁需要的「所有資源」都已下載並處理。此時,載入旋轉圖示可能會停止在瀏覽器中旋轉 (刊登序列中的紅色垂直線)。

混合加入 JavaScript 和 CSS

「Hello World 服務」頁面看起來很簡單,但背後有許多工作需要。實務上,我們需要不只 HTML 標記,還有一個 CSS 樣式表和一或多個指令碼,可讓網頁增添互動元素。讓我們把兩者都加進組合,看看會發生什麼事:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

試用

新增 JavaScript 和 CSS 前的注意事項:

DOM 總收視點數

透過 JavaScript 和 CSS:

DOM、CSSOM、JS

新增外部 CSS 和 JavaScript 檔案後,會額外將兩個要求新增至刊登序列,所有要求都會同時分派。不過請注意,現在 domContentLoadedonload 事件之間的時間差異稍短了。

為什麼會這樣?

  • 不同於一般的 HTML 範例,我們也必須擷取並剖析 CSS 檔案以建構 CSSOM,而我們需要 DOM 和 CSSOM 來建立算繪樹狀結構。
  • 由於網頁也含有會封鎖 JavaScript 檔案的剖析器,因此系統會封鎖 domContentLoaded 事件,直到系統下載並剖析 CSS 檔案為止:因為 JavaScript 可能會查詢 CSSOM,我們必須先封鎖 CSS 檔案,直到檔案下載才能執行 JavaScript。

如果我們以內嵌指令碼取代外部指令碼,該怎麼辦?即使指令碼直接內嵌在網頁,瀏覽器必須等到建構 CSSOM 後才能執行。簡單來說,內嵌 JavaScript 也能封鎖剖析器。

儘管如此,儘管在 CSS 上進行封鎖,內嵌指令碼仍會加快網頁轉譯速度嗎?我們來親身體驗,看看會怎樣。

外部 JavaScript:

DOM、CSSOM、JS

內嵌 JavaScript:

DOM、CSSOM 和內嵌 JS

我們減少了一次要求,但 onloaddomContentLoaded 時間其實相同。原因何在?我們知道 JavaScript 內嵌或外部也沒有任何作用,因為瀏覽器按下指令碼標記時會加以封鎖,並等待 CSSOM 建構完成。此外,在第一個範例中,瀏覽器會同時下載 CSS 和 JavaScript,然後幾乎同時完成下載。在本例中,內嵌 JavaScript 程式碼並不足以幫助我們。不過,有幾種策略可以加快網頁顯示速度。

首先,請注意,所有內嵌指令碼都會封鎖剖析器,但針對外部指令碼,我們可以新增「async」關鍵字,將剖析器解除封鎖。我們取消內嵌,以嘗試下列操作:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

試用

剖析器封鎖 (外部) JavaScript:

DOM、CSSOM、JS

非同步 (外部) JavaScript:

DOM、CSSOM、非同步 JS

好得多!剖析 HTML 後,很快就會觸發 domContentLoaded 事件;瀏覽器知道不要封鎖 JavaScript,而且因為沒有任何其他剖析器會封鎖指令碼,CSSOM 結構也會同時執行。

或者,我們可以同時內嵌 CSS 和 JavaScript:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

試用

DOM、內嵌 CSS、內嵌 JS

請注意,domContentLoaded 時間實際上與先前範例相同,我們在網頁本身中加入 CSS 和 JS,而不是將 JavaScript 標示為非同步。這使得 HTML 網頁變大了,但缺點是瀏覽器不用等待擷取任何外部資源,一切都在網頁中。

如您所見,即使網頁很簡單,最佳化關鍵轉譯路徑仍是一項小小的練習:我們必須瞭解不同資源之間的依附關係圖、找出「關鍵」的資源,而我們必須選擇不同的策略來將這些資源納入網頁。這個問題沒有一個解決方法,每個頁面都不一樣。您需要自行按照類似的程序操作,才能找出最佳策略。

儘管如此,我們還是看看能否更快回歸,找出一些一般效能模式。

成效模式

最簡單的網頁僅包含 HTML 標記;沒有 CSS、無 JavaScript 或其他類型的資源。如要讓瀏覽器轉譯這個網頁,請等待 HTML 文件送達、剖析、建構 DOM,最後在螢幕上算繪這個頁面:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

Hello World CRP

T0 和 T1 之間的時間會擷取網路和伺服器處理時間。在最佳情況下 (如果 HTML 檔案較小),只有一個網路往返作業會擷取整份文件。根據 TCP 傳輸通訊協定的運作方式,大型檔案可能需要更多往返作業。因此,在最理想的情況下,上方網頁至少會有一個往返 (至少) 一趟的重要轉譯路徑。

現在,我們思考使用外部 CSS 檔案的同一個網頁:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試用

DOM + CSSOM 目標對象收視率

再次我們進行了網路往返作業,以便擷取 HTML 文件,接著擷取的標記告知我們還需要 CSS 檔案;這表示瀏覽器必須先返回伺服器取得 CSS 檔案,才能在畫面上轉譯網頁。因此,這個網頁至少會產生兩次往返行程後才能顯示。同樣地,CSS 檔案可能會多次往返,因此強調「最少」。

我們現在定義用來描述重要轉譯路徑的詞彙:

  • 關鍵資源:可能阻擋網頁初次轉譯的資源。
  • 關鍵路徑長度:往返作業次數,或擷取所有重要資源所需的總時間。
  • Critical Bytes (嚴重位元組):首次轉譯網頁所需的位元組總數,也就是所有重要資源的傳輸檔案大小總和。 我們的第一個範例,只有一個 HTML 頁面,其中包含單一重要資源 (HTML 文件);關鍵路徑長度也等於一次網路往返 (假設檔案較小),且關鍵位元組總數只是 HTML 文件本身的傳輸大小。

現在,讓我們比較上述 HTML + CSS 範例的重要路徑特性:

DOM + CSSOM 目標對象收視率

  • 2 項重要資源
  • 因關鍵路徑長度下限而產生的 2 次以上的來回行程
  • 9 KB 的重要位元組

我們需要 HTML 和 CSS 才能建構算繪樹狀結構。因此,HTML 和 CSS 都是關鍵資源:只有在瀏覽器取得 HTML 文件後,系統才會擷取 CSS,因此關鍵路徑長度至少為兩次來回。兩項資源的總和等於 9 KB 的重要位元組。

現在,讓我們混合加入一個額外的 JavaScript 檔案。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

試用

我們新增了 app.js,同時同時是網頁上的外部 JavaScript 素材資源和剖析器封鎖工具 (亦即重要的資源)。更糟的是,為了執行 JavaScript 檔案,我們必須封鎖並等待 CSSOM。請注意,JavaScript 可以查詢 CSSOM,因此瀏覽器會暫停,直到 style.css 下載並建構 CSSOM 完成為止。

DOM、CSSOM、JavaScript CRP

換句話說,在實際查看這個網頁的「聯播網刊登序列」時,您會發現 CSS 和 JavaScript 請求幾乎同時發出,瀏覽器取得 HTML、發現這兩項資源並發出這兩個要求。因此,上述網頁具有以下重要路徑特性:

  • 3 項重要資源
  • 因關鍵路徑長度下限而產生的 2 次以上的來回行程
  • 11 KB 的重要位元組

我們現在有三個重要資源,總計高達 11 KB 的關鍵位元組,但關鍵路徑長度仍然是兩次往返值,因為我們可以同時轉移 CSS 和 JavaScript。判斷重要轉譯路徑的特性,意味著要能識別重要資源,並瞭解瀏覽器排定擷取時間的方式。讓我們繼續看範例。

與網站開發人員即時通訊後,我們發現我們在網頁中加入的 JavaScript 無須封鎖,因為我們在其中有一些分析和其他程式碼,無需封鎖網頁的轉譯。瞭解這些知識後,我們就可以在指令碼標記中加入「async」屬性,以解除封鎖剖析器:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試用

DOM、CSSOM、非同步 JavaScript CRP

非同步指令碼有幾個優點:

  • 此指令碼不會再封鎖剖析器,也不屬於重要轉譯路徑。
  • 由於沒有其他重要指令碼,因此 CSS 不需要封鎖 domContentLoaded 事件。
  • domContentLoaded 事件越早觸發,其他應用程式邏輯就能越快開始執行。

因此,我們最佳化的網頁現在會回溯至兩項重要資源 (HTML 和 CSS),其中關鍵路徑最短為兩次來回,總共有 9 KB 的關鍵位元組。

最後,如果只有平面印刷品才需要 CSS 樣式表,那麼外觀會怎樣?

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試用

DOM、非封鎖的 CSS 和非同步 JavaScript CRP

由於 style.css 資源僅供列印使用,瀏覽器不需封鎖即可轉譯網頁。因此,當 DOM 建構完成後,瀏覽器就會取得足以顯示網頁的資訊。因此,這個頁面只有單一重要資源 (HTML 文件),且重要算繪路徑的長度下限為一次。

意見回饋: