【翻譯】高效能網站應用程式

Ivan Chiou
13 min readJan 16, 2020

--

原文: https://itnext.io/high-performance-web-apps-2a469cfd3550 Rodolfo Gonçalves. Jul 14, 2019

檔案更大不代表等待更久

當建立web應用程式時,我們都有基本的概念要小心不要產出太多的js與css程式碼給我們的使用者。我們給出的程式碼越多,可能會使我們的應用程式跑得更慢。

所以這個概念通常是”盡量以較少的程式碼提供產出”. 也就是說,越少的code越好,有越快的下載時間,越快的處裡速度,越快的渲染,越快在螢幕上有內容出現。

但是,比起我們盡可能的去榨乾(減少程式碼)我們網站應用程式,來提升效能;更重要的是去了解我們瀏覽器如何處裡HTML/CSS和JS,以及轉換成在螢幕上的像素的過程。我們稱作這個過程為 — 關鍵渲染路徑Critical Rendering Path(CRP)

渲染 vs 漸進式渲染

優化 關鍵渲染路徑有很多事情要做,包含減少關鍵資源的使用,和使用非同步的方式下載非關鍵資源,這都是為了達到更好的頁面載入時間。

而且當頁面在讀取的時候,並非只有衡量單一的時段。而是在讀取過程中,有很多不同階段需要我們去追蹤,用以衡量這網站應用程式是多快或者多慢,這裡面包含:

  1. 何時使用者在螢幕上看到網站畫面 — FCP(First Contentful Paint )
  2. 何時使用者在螢幕上看到有意義的網站畫面 — FMP(First Meaningful Paint)
  3. 何時使用者可以與畫面作互動 — TTI(Time to Interactive)

有好幾種不同策略去改善這些項目的時間維度,但沒有一種”絕對方式符合所有的解決方案”。最根本的方式是去了解關鍵渲染路徑,來定義出最符合我們問題的解決策略。

關鍵渲染路徑

以下的流程圖說明所有的關鍵渲染路徑的主要流程。從我們向server請求開始到用戶端接收到第一個byte的時候(Time To First Byte)。

關鍵渲染路徑

  1. 首先,抓取HTML檔案和開始建立文件物件模型DOM(Document Object Model),其中包含所有有關該呈現什麼內容的資訊。
  2. 接著抓取CSS檔案,建立CSS對象模型CSSOM(CSS Object Model),此包含著所有有關於如何在畫面上呈現內容資訊。
  3. 然後結合以上兩個模型建立渲染樹,裡面包含要渲染的資訊內容與樣式。
  4. 接著是Layout(布局)。在這個階段我們知道內容與樣式,但我們少了一件事情,計算每一個元素在裝置視界(Viewport)下的精確位置與大小。那就是Layout。
  5. 最後,當布局完成之後。繪製的事件會被觸發,將渲染樹轉換成在螢幕上的像素。

請注意上述我們沒有包含到javascript的部份。在我們開始之前,讓我們練習只去考慮HTML和CSS的部份。

CSS與關鍵渲染路徑

在接下來的片段,我們可以看到有一個HTML文檔,裡頭的一些標註包含頁面骨架,CSS檔案,和一些文字。

當使用者第一次瀏覽此連結與接收回應時,這些DOM開始逐步的建立。接著<link>標籤被找到,然後一個新的HTTP請求被提出,接著DOM持續地被建立,同時間HTTP收到回覆,CSS和CSSOM被建立。這過程持續到所有DOM被建立完成,還沒有任何畫面被渲染出來。一但所有的DOM被建立完成,我們就有完整的渲染樹,以及Layout布局,和像素呈現在畫面上。

這就是為什麼我們能夠確認CSS是渲染的瓶頸所在。因為如果沒有CSSOM,就沒有渲染樹的產生。而且沒有渲染樹就沒有辦法完成渲染。

渲染樹

渲染樹

但為什麼CSS會有渲染瓶頸,HTML卻沒有? 基本上因為DOM(HTML)可以漸進式地被建立,但CSSOM卻不行。是因為CSS天生屬於階層層級式(cascading)的關係,每一個CSS規則會影響另外一個CSS,但DOM(HTML)的節點之間不會相互影響。舉例:

span { color: yellow; }

p span { color: ; red } // thousands of lines below

假設CSSOM是可以被非同步(non-blocking)漸進式的創建,那可能產生的情境如下:

CSSOM

  1. 第一次渲染 — 仍然沒有任何CSS,所以只渲染沒有任何樣式的內容
  2. 第二次渲染 — 取得第一層區塊的CSS(span { color: yellow; }),接著渲染所有的span為黃色
  3. 第三次渲染 — 取得第n層區塊的CSS (p span { color: red; }- 階層層級式樣式開始),接著渲染所有的span為紅色

因此這樣會產生很多效能上的問題,因為同一元件可能會需要畫好幾次,甚至該元件可能會消失再出現(因為像這樣的語法display: “none”),這樣會給使用者很差的操作體驗。

這就是為什麼CSS是同步渲染(render blocking),所以必須確認CSS是精簡的,能盡可能的快速發布,並且使用media屬性來獲取非同步的渲染。

所以對於HTML,因為DOM是漸進式的被創建,因此我們可以切分HTML為一小區塊,來盡可能地達到快速發布,並且使用伺服器端渲染(Server Side Render, SSR)技術來達到漸進式的渲染。提供類似像google搜尋的體驗:

Google Search漸進式渲染

現在在這個片段裡,我們有一份文檔,裡面包含一些內聯(inline)的Javascript,且沒有包含任何CSS檔案。就是這樣一個簡單的頁面,裡面有script標籤會去改變顯現在螢幕上的內容。

在這個案例,DOM開始被漸進式的創建,直到<script>標籤被找到,DOM創建會暫停,然後等待Javscript執行,並且去修改DOM。之後,DOM恢復創建,然後渲染在畫面上。

關鍵渲染路徑 — Javascript

Javascript執行非常快速,似乎不會影響關鍵渲染路徑太多。就像我們上述的例子,使用非常簡單的內聯script一樣,執行非常快速。但是,針對外部相依的部分,DOM的創建會被停止,直到HTTP的外部請求結束且執行完請求的Javascript檔案後才繼續。這個請求過程會需要一點時間…

所以內聯的CSS和JS是好的策略來增進網頁的讀取時間,但是也要適可而止。這是因為它雖然能減少HTTP請求的時間,但是你可能需要多次複製同一段程式碼到不同檔案,如果你需要在不同檔案使用它的時候。但或許使用外部檔案,也是一個很好機會,可以透過cache(暫存)機制來增進擷取與下載外部檔案的速度。並且要記得,最快的HTTP請求方式,就是從不去請求。

一個好的範例就是避免內聯檔案超過1kb,而且外部擷取它們使用暫存機制。

現在一個有包含CSS和Javascript的網頁檔案;CSS是外部連結檔案,Javascript是內聯script標籤裡面修改網頁內容與樣式。

如同在建構DOM的過程中,當發現到<link>標籤,且有一個HTTP請求同時被發起時,DOM建構會持續進行中;當找到<script>標籤,瀏覽器還無法執行Javascript時,但它還可以繼續操作DOM和CSSOM,此時瀏覽器會先等待CSSOM建立,並且會相依於擷取CSS檔案的HTTP回應的時間。只有成功擷取到CSS然後CSSOM被建立後,這個瀏覽器才會接著執行Javascript,然後DOM的創建才不會被停止,它才會繼續,最終渲染在畫面上。

關鍵渲染路徑的步驟 — 同時考慮JS與CSS

CSS不只是會造成停止渲染而且還會停止Javascript的執行,這就是為什麼優化CSS這麼重要。

有一些script不會改變DOM和CSSOM,這樣就不會造成頁面停止。一個好的非同步script案例那就是analytics. Analytics script是一個好的非同步讀取的案例,在每當頁面被讀取的時候,我們可以使用async和defer的標籤。

<script src=”/analytics.js” async />

更多的在後面。

總結

我們目前只考慮到關鍵渲染路徑以及JS和CSS檔案如何影響頁面載入速度。我們只稍微觸碰到一些基本改善頁面載入效率的策略,但其實還有很多可以討論的有關頁面載入速度方面的優化,需要我們去掌握。

現在我們心中已經有基本的概念,有關於瀏覽器如何去解讀HTML/CSS/Javascript來渲染內容在畫面上。因次我們需要更清晰的了解有關於什麼樣的技術最適切的能解決我們的問題,以及如何解決。

優化關鍵渲染路徑

如同之前所說,沒有一個黃金法則適用於所有問題的優化策略。但有一些我們應該了解的普遍性策略,可以去優化關鍵渲染路徑。

三種優化策略

  1. 減少bytes大小: 最小化,壓縮和暫存JS/CSS/HTML
  2. 減少關鍵資源: 最小化渲染的使用以及讀取需要同步的
  3. 縮短關鍵渲染路徑的長度: 縮小化來回請求的數量 — 最短關鍵路徑長度是在關鍵資源與它的檔案大小之間的相依圖: 也就是說有些檔案資源下載只能在先前一些資源被處理後才能被啟動,當資源檔案越大來回下載的時間也就越久。(優化關鍵渲染路徑)

最小化檔案大小

檔案和HTTP回應壓縮

  • 提交最少的關鍵程式碼,也就是表示你可以最小化,壓縮,和暫存這些程式碼。
  • 使用一些工具像是webpack去最小化你的production下的bundle大小。然後使用程式碼切割技術(code splitting),分別在同步情況下下載關鍵的檔案區塊,以及在非同步下去下載非關鍵的檔案。
  • 去權衡modules的使用 (<script type=”module” src=”/module.js”>)。為了能在更先進的瀏覽器下,使用有更多壓縮或者優化語法的程式碼區塊,因此可以有更少的檔案大小。
  • 使用藉由一些工具像是從babel中的browserslist, core-js和usebuiltIns等,所產生的tanspiled(程式碼轉換技術)的程式碼區塊,與使用針對特定瀏覽器的補丁包。
  • 在你的網站伺服器使用演算法像是Brotli或Gzip來執行檔案的壓縮。

減少關鍵資源

同步與非同步scripts

  • 用內聯關鍵CSS 去最小化同步渲染以及script的執行。
  • 在link標籤上使用media query去延遲不符合你的裝置類型的CSS(media type or dimension)
  • 使用非同步方式如async, defer屬性的script標籤去下載非關鍵的檔案
  • 使用preload, prefetch, 和preconnect標籤,這些標籤可能是你的使用者在體驗你的應用程式中所需要的。這些標籤將避免你的使用者等待這些檔案資源,當他們需要這些資源的時候(特別是當延遲載入 lazy loading時更有幫助。)

縮短關鍵資源路徑長度

這一個項目非常依賴你的應用程式所提供服務的HTTP版本。

HTTP/2採用更有效率的方式使用網路資源,並藉由多工請求與回覆方式、header壓縮、優先權策略,減少網路的延遲(latency)

也就是說,盡量使用HTTP/2去發佈你的應用程式。但是當你無法這麼做時,在使用HTTP/1上也有一些workaround(暫時處理)的方法。

網路來回請求

HTTP/1.x優化:

  • 使用 domain sharding.
  • 打包檔案資源來減少HTTP請求且避免同步(parallelism)限制的相關問題。
  • 內聯化小的檔案資源(最大到1kb)去最小化請求檔案的數目

HTTP/2 優化:

無法優化你無法衡量的部分

如果沒有衡量方法,我們是很難去了解什麼才是你的網站應用程式的主要痛點,且如何去應對它們。

所以,就像由Addy Osmani在“The Cost Of JavaScript In 2018”中所提出的:

測量優化監控,然後重複上述。

開始效能的節能控制

在你的CI/CD流程上測量效能,在里程碑、質量、與規則上定義預算計畫。- 也就是測試區測試。

測試區測試 — 效能的節能控制

使用效能節能控制來避免你的網站效能隨著時間退化。這將幫助你在你接觸到你的用戶之前,對於你的網站應用程式效能有一個清楚的了解。

節能控制可以依據:

  • 里程碑的時間確認: 依照使用者讀取網頁的時間區間來衡量。像是TTI, FCPFMP
  • 量化衡量: 依據raw data像是Javascript的檔案大小,與HTTP請求的數量。
  • 規則衡量: 藉由一些像lighthouse或者WebPageTest等工具所產生的分數來衡量。通常,是用單一分數或者一連串的分數來評量你的網站。

以下有一些工具可以幫助你去衡量如何節能控制,像是webpack中的Performance API, 以及bundlesize, Lighthouse, WebPageTest等.

開始效能的監控:

效能節能控制將幫助你追蹤和突破你的網站效能限制。而且你可以使用這些衡量方式,去突破這些最符合真實情況下的瓶頸。

但為了能追蹤以上這些在你的真實網站中的影響,你需要實作真正的監控系統Real Use Monitoring (RUM)。

一些服務將幫助你取得這些真實的量化資料,像是Calibre, Treo, Webdash, SpeedCurve等.

希望你現在有更好的基礎知識,在有關於瀏覽器如何處裡你的應用程式中的檔案與這些檔案是如何影響你的網站讀取速度。什麼是”最佳的依循”去優化你的網站效能? 如何確保你的網站效能節能控制能符合團隊的需求? 且藉由RUM知道它如何影響你的真實情況的用戶。

以上這些主題是真實的網站輕量全局檢視,用以針對專案的狀態與目標,快速取得的密集成效。因此,依據你的受眾目標,定義你的網站效能策略,並開始實作它、優化它、並且持續監控它。

網站效能調教其實是一個產品文化的議題,而非技術導向來決定。

--

--

Ivan Chiou
Ivan Chiou

Written by Ivan Chiou

Rich experience in multimedia integration, cross-functional collaboration, and dedicated to be a mentor for young developers.

No responses yet