在 2018 年 Google I/O 大會上,我們介紹了各種工具、程式庫和最佳化技術,讓您更輕鬆地提升網頁效能。我們將以 The Oodles Theater 應用程式為例,說明這些概念,並介紹我們在預測載入和新 Guess.js 計畫方面的實驗。
過去一年來,我們一直忙於研究如何提升網頁速度和效能。因此我們開發了新的工具、方法和程式庫,並在本文中與您分享。在第一部分,我們會說明開發 Oodles Theater 應用程式時,我們實際使用的最佳化技術。第二部分則會介紹我們對預測載入進行的實驗,以及新的 Guess.js 計畫。
效能需求
網際網路每年都會變得越來越龐大。如果我們查看網路狀態,會發現行動裝置上網頁的中位數大小約為 1.5 MB,其中大部分是 JavaScript 和圖片。
網站大小不斷增加,加上網路延遲、CPU 限制、導致轉譯遭到封鎖的模式或多餘的第三方程式碼等其他因素,使得效能問題更加複雜。
大多數使用者都認為速度是使用者體驗需求層級中最重要的因素。這並不令人意外,因為網頁載入完成前,您無法進行太多操作。您無法從網頁衍生價值,也無法欣賞其美學。
我們瞭解效能對使用者來說很重要,但要找出最佳化起點,可能就像在發掘秘密。幸好,有許多工具可協助您完成這項作業。
Lighthouse - 效能工作流程的基礎
Lighthouse 是 Chrome 開發人員工具的一部分,可稽核網站並提供改善建議。
我們最近推出了一系列全新效能稽核,在日常開發工作流程中非常實用。
讓我們透過實際範例,瞭解如何善用這些功能: Oodles Theater 應用程式。這是一個小型示範網頁應用程式, 您可以在這裡試用我們最喜歡的幾個互動式 Google Doodle,甚至玩一兩款遊戲。
在建構應用程式時,我們希望確保應用程式的效能盡可能達到最佳狀態。最佳化作業的起點是 Lighthouse 報表。
如 Lighthouse 報告所示,我們應用程式的初始效能相當糟糕。在 3G 網路上,使用者需要等待 15 秒,才能看到第一個有意義的繪製內容,或與應用程式互動。Lighthouse 醒目顯示了我們網站的許多問題,而 23 分的整體效能分數也反映了這一點。
該網頁約 3.4 MB,我們迫切需要減少一些負擔。
這就是我們第一個效能挑戰的開始:找出可輕鬆移除的項目,且不會影響整體體驗。
成效最佳化商機
移除不必要的資源
有些明顯的項目可以安全移除:空白字元和註解。
Lighthouse 會在未縮排的 CSS 和 JavaScript 稽核中,強調這項改善機會。我們在建構程序中使用 webpack,因此為了進行壓縮,我們只使用了 Uglify JS 外掛程式。
縮減是常見的工作,因此您應該可以找到適用於任何建構程序的現成解決方案。
另一個實用的稽核項目是「啟用文字壓縮」。沒有理由傳送未壓縮的檔案,而且現在大多數 CDN 都支援這項功能。
我們使用 Firebase Hosting 代管程式碼,而 Firebase 預設會啟用 gzip 壓縮,因此只要在合理的 CDN 上代管程式碼,就能免費取得這項功能。
雖然 gzip 是非常熱門的壓縮方式,但 Zopfli 和 Brotli 等其他機制也越來越受歡迎。大多數瀏覽器都支援 Brotli,您可以使用二進位檔預先壓縮資產,再傳送至伺服器。
使用有效率的快取政策
我們的下一步是確保不會重複傳送資源 (如果不需要的話)。
Lighthouse 的「無效率的快取政策」稽核功能,協助我們發現可以最佳化快取策略,我們在伺服器中設定了 max-age 到期標頭,確保使用者重複造訪時,可以重複使用先前下載的資源。
理想情況下,您應盡可能安全地快取最多資源,並盡可能延長快取時間,同時提供驗證權杖,以便有效重新驗證已更新的資源。
移除未使用的程式碼
到目前為止,我們已移除不必要下載的明顯部分,但較不明顯的部分呢?例如未使用的程式碼。
有時我們會在應用程式中加入非必要程式碼。如果您長時間開發應用程式、團隊或依附元件有所變更,有時就會留下孤立程式庫。我們就是這樣。
一開始,我們使用 Material 元件庫快速製作應用程式原型。後來,我們改用更自訂的外觀和風格,完全忘記了這個程式庫。幸好,程式碼涵蓋範圍檢查協助我們在套件中重新發現這個問題。
您可以在開發人員工具中查看程式碼涵蓋率統計資料,包括應用程式的執行階段和載入時間。從底部螢幕截圖中可以看到兩條紅色長條,代表我們有超過 95% 的 CSS 未使用,以及大量 JavaScript。
Lighthouse 也在未使用的 CSS 規則稽核中發現這個問題。結果顯示可節省超過 400 KB 的空間。因此我們回到程式碼,並移除了該程式庫的 JavaScript 和 CSS 部分。
這讓我們的 CSS 組合縮減了 20 倍,以只有兩行的提交內容來說,這相當不錯。
當然,這也提升了我們的成效分數,互動時間也大幅縮短。
不過,在這種情況下,光是查看指標和分數並不夠。 移除實際程式碼絕非毫無風險,因此請務必留意潛在的回歸問題。
我們的程式碼有 95% 未使用,但仍有 5% 存在於某處。顯然我們的其中一個元件仍在使用該程式庫的樣式,也就是塗鴉滑桿中的小箭頭。不過,由於樣式很小,我們可以手動將這些樣式重新併入按鈕。
因此,如果移除程式碼,請務必建立適當的測試工作流程,協助您防範潛在的視覺迴歸。
避免耗用大量網路酬載
我們知道大型資源可能會導致網頁載入速度變慢。這類應用程式可能會導致使用者花費金錢,並對資料方案造成重大影響,因此請務必留意。
Lighthouse 透過「Enormous network payload」(龐大的網路有效負載) 稽核,偵測到部分網路有效負載有問題。
我們發現有超過 3 MB 的程式碼要傳送下來,這相當於大量資料,尤其是在行動裝置上。
在這份清單的最上方,Lighthouse 醒目顯示我們有一個 JavaScript 供應商套件,其中包含 2 MB 的未壓縮程式碼。這也是 webpack 強調的問題。
俗話說:「最快的請求就是沒有請求」。
理想情況下,您應該評估提供給使用者的每項資產價值、評估這些資產的成效,並判斷是否值得在初始體驗中實際運送。因為有時這些資產可能會延遲或延遲載入,或是在閒置時間處理。
就我們的情況而言,由於要處理大量 JavaScript 套件,因此很幸運地,JavaScript 社群提供了一整套 JavaScript 套件稽核工具。
我們首先使用 webpack 組合分析器,得知我們納入的依附元件名為 unicode,且已剖析的 JavaScript 達 1.6 MB,因此相當龐大。
接著,我們前往編輯器,並使用 Visual Code 的 Import Cost 外掛程式,這讓我們得以找出包含參照這個模組程式碼的元件。
接著,我們改用另一項工具 BundlePhobia。這項工具可讓您輸入任何 NPM 套件的名稱,並查看預估的縮小和壓縮大小。我們發現一個不錯的替代方案,可取代原本使用的 slug 模組,而且只有 2.2 KB,因此我們改用這個方案。
這對我們的成效造成重大影響。除了這項變更,我們也發現其他可減少 JavaScript 套件大小的機會,最後節省了 2.1 MB 的程式碼。
考量這些套件的 gzip 壓縮和縮小大小後,我們發現整體改善幅度達 65%。我們發現這個過程非常值得。
因此,一般來說,請盡量避免在網站和應用程式中進行不必要的下載。 清查資產並評估其對成效的影響,可帶來顯著差異,因此請務必定期稽核資產。
透過程式碼分割縮短 JavaScript 啟動時間
雖然大型網路有效負載可能會對應用程式造成重大影響,但還有另一項因素的影響力更大,那就是 JavaScript。
JavaScript 是您最昂貴的資產。在行動裝置上,如果傳送大量 JavaScript 組合,可能會延遲使用者與使用者介面元件互動的時間。也就是說,使用者可能會輕觸 UI,但實際上不會發生任何有意義的動作。因此,瞭解 JavaScript 為何會耗用這麼多資源非常重要。
瀏覽器就是這樣處理 JavaScript。
首先,我們必須下載該指令碼,然後 JavaScript 引擎需要剖析、編譯及執行該程式碼。
現在,這些階段在高階裝置 (例如桌上型電腦、筆電,甚至是高階手機) 上,都不會花費太多時間。但如果使用中階手機,這個程序可能需要五到十倍的時間。這會延遲互動,因此我們必須盡量縮減這類資源。
為協助您找出應用程式的這類問題,我們在 Lighthouse 中推出了新的 JavaScript 啟動時間稽核。
以 Oodle 應用程式為例,這項工具顯示我們在 JavaScript 啟動時花費了 1.8 秒。我們在所有路徑和元件中靜態匯入一個單體式 JavaScript 套件,這就是問題所在。
解決這個問題的方法之一是使用程式碼分割。
程式碼分割的概念是,與其提供給使用者一整份 JavaScript,不如在他們需要時一次只提供一小部分。
程式碼分割可套用至路徑層級或元件層級。可與 React 和 React Loadable、Vue.js、Angular、Polymer、Preact,以及其他多個程式庫搭配使用。
我們在應用程式中加入程式碼分割功能,並從靜態匯入切換至動態匯入,因此可以視需要非同步延遲載入程式碼。
這項措施不僅縮減了套件大小,也減少了 JavaScript 啟動時間。降至 0.78 秒,應用程式速度提升 56%。
一般來說,如果您要建構大量使用 JavaScript 的體驗,請務必只將使用者需要的程式碼傳送給他們。
善用程式碼分割等概念,探索樹狀結構修剪等想法,並查看 webpack-libs-optimizations 存放區,瞭解如何縮減程式庫大小 (如果您使用 webpack)。
最佳化圖片
在 Oodle 應用程式中,我們使用了許多圖片。很遺憾,Lighthouse 對這項功能並不買單。事實上,我們在所有三項圖片相關稽核中都失敗了。
我們忘了最佳化圖片,圖片大小也不正確,而且使用其他圖片格式也能獲得一些增益。
我們首先著手最佳化圖片。
如要進行一次性最佳化,可以使用 ImageOptim 或 XNConvert 等視覺化工具。
如要採用更自動化的方法,請使用 imagemin 等程式庫,在建構程序中加入圖片最佳化步驟。
這樣一來,日後新增的圖片就會自動最佳化。部分 CDN (例如 Akamai) 或第三方解決方案 (如 Cloudinary、Fastly 或 Uploadcare) 提供完整的圖片最佳化解決方案,因此您也可以直接在這些服務上代管圖片。
如果不想這麼做 (因為費用或延遲問題),可以考慮使用 Thumbor 或 Imageflow 等專案,這些專案提供自架主機的替代方案。
我們的背景 PNG 在 webpack 中標示為大型,這是正確的。將大小調整為符合可視區域,並透過 ImageOptim 執行後,大小降至 100 KB,這是可接受的大小。
對網站上的多張圖片重複執行這項操作後,我們大幅降低了整體網頁大小。
使用動畫內容的正確格式
GIF 的費用可能非常高。令人意外的是,GIF 格式最初並非動畫平台,因此,改用更合適的影片格式可大幅節省檔案大小。
在 Oodle 應用程式中,我們使用 GIF 做為首頁的簡介序列。根據 Lighthouse,改用更有效率的影片格式後,我們可節省超過 7 MB 的空間。我們的短片約 7.3 MB,對任何合理的網站來說都太大了,因此我們將其轉換為影片元素,並提供兩個來源檔案:mp4 和 WebM,以支援更多瀏覽器。
我們使用 FFmpeg 工具將動畫 GIF 轉換為 MP4 檔案。 WebM 格式可節省更多空間,ImageOptim API 可為您執行這類轉換。
ffmpeg -i animation.gif -b:v 0 -crf 40 -vf scale=600:-1 video.mp4
我們透過這項轉換,成功節省了 80% 以上的整體重量。這讓我們的檔案大小降至約 1 MB。
不過,1 MB 仍是透過網路推送的大型資源,尤其是頻寬受限的使用者。幸好我們可以使用有效類型 API,瞭解使用者頻寬較慢,並改為提供較小的 JPEG。
這個介面會使用有效來回時間和下載值,估算使用者使用的網路類型。這項屬性只會傳回字串,也就是 slow 2G、2G、3G 或 4G。因此,如果使用者使用 4G 以下的網路,我們可以根據這個值將影片元素替換成圖片。
if (navigator.connection.effectiveType) { ... }
雖然會稍微影響體驗,但至少網站在連線速度緩慢時仍可使用。
延遲載入畫面外圖片
輪播、滑桿或超長網頁通常會載入圖片,即使使用者無法立即在網頁上看到這些圖片也一樣。
Lighthouse 會在螢幕外圖片稽核中標示這項行為,您也可以在開發人員工具的網路面板中自行查看。如果看到大量傳入的圖片,但網頁上只顯示少數圖片,表示您可以考慮延遲載入這些圖片。
瀏覽器目前尚未原生支援延遲載入,因此我們必須使用 JavaScript 新增這項功能。我們使用 Lazysizes 程式庫,為 Oodle 封面新增延遲載入行為。
<!-- Import library -->
import lazysizes from 'lazysizes' <!-- or -->
<script src="lazysizes.min.js"></script>
<!-- Use it -->
<img data-src="image.jpg" class="lazyload"/>
<img class="lazyload"
data-sizes="auto"
data-src="image2.jpg"
data-srcset="image1.jpg 300w,
image2.jpg 600w,
image3.jpg 900w"/>
Lazysizes 不僅會追蹤元素的顯示狀態變化,還會主動預先擷取檢視區塊附近的元素,提供最佳使用者體驗,因此非常聰明。此外,您也可以選擇整合 IntersectionObserver,大幅提升可視度查詢效率。
這項變更完成後,系統就會視需要擷取圖片。如要深入瞭解這個主題,請參閱 images.guide,這項實用且內容豐富的資源。
協助瀏覽器提早傳送重要資源
傳輸到瀏覽器的每個位元組重要程度不盡相同,瀏覽器也知道這一點。許多瀏覽器都有啟發式方法,可決定應優先擷取哪些內容。因此有時會先擷取 CSS,再擷取圖片或指令碼。
對我們來說,實用的做法是身為網頁作者,告知瀏覽器我們真正重視的內容。幸好,過去幾年來,瀏覽器供應商已新增多項功能來協助我們,例如 資源提示 (如 link rel=preconnect、preload 或 prefetch)。
這些功能引進網路平台後,有助於瀏覽器在適當時間擷取正確內容,與使用指令碼完成的自訂載入、邏輯式方法相比,效率更高。
接著來看看 Lighthouse 如何引導我們有效使用這些功能。
Lighthouse 建議我們做的第一件事,就是避免多次往返任何來源,因為這會耗費大量資源。
以 Oodle 應用程式為例,我們大量使用 Google 字型。每當您將 Google 字體樣式表放入網頁時,系統最多會連線至兩個子網域。而 Lighthouse 告訴我們,如果可以預先建立連線,初始連線時間最多可節省 300 毫秒。
善用連結 rel 預先連線,我們就能有效遮蓋連線延遲。
特別是 Google 字體這類服務,因為我們的字體 CSS 位於 googleapis.com,字體資源則位於 Gstatic,這可能會造成很大的影響。因此我們套用了這項最佳化措施,減少了幾百毫秒。
Lighthouse 建議的下一個做法是預先載入重要要求。
<link rel=preload> 非常強大,可通知瀏覽器目前導覽需要資源,並盡快擷取瀏覽器。
現在 Lighthouse 告訴我們,應該預先載入重要的網頁字型資源,因為我們載入了兩種網頁字型。
網頁字型的預先載入方式如下:指定 rel=preload 時,您會傳遞 as 和字型類型,然後指定要載入的字型類型,例如 woff2。
這對網頁的影響相當明顯。
一般來說,如果不使用連結 rel 預先載入,如果網頁字型對網頁來說至關重要,瀏覽器必須先擷取 HTML、剖析 CSS,然後在稍晚的時候,才會擷取網頁字型。
使用連結 rel 預先載入後,瀏覽器剖析 HTML 後,就能更早開始擷取這些網頁字型。就我們的應用程式而言,這能減少一秒的時間,讓我們使用網頁字型算繪文字。
如果您要使用 Google 字型預先載入字型,就沒那麼簡單了, 有一個需要注意的地方。
我們在樣式表中的字型臉孔上指定的 Google 字型網址,剛好是字型團隊相當規律更新的內容。這些網址可能會過期或定期更新,因此如果想全面掌控字型載入體驗,建議自行代管網路字型。這項功能非常實用,因為您可存取連結 rel 預先載入等項目。
以我們的情況來說,我們發現 Google Web Fonts Helper 這項工具非常實用,可協助我們離線處理部分網頁字型並在本機設定,建議您試試這項工具。
無論您是將網路字型做為重要資源的一部分,還是使用 JavaScript,請盡量協助瀏覽器盡快提供重要資源。
實驗功能:優先順序提示
我們今天有特別消息要分享。除了資源提示和預先載入等功能,我們也一直在開發全新的實驗性瀏覽器功能,稱為優先順序提示。
這項新功能可讓您向瀏覽器提示資源的重要性。這項功能會公開新的屬性 (重要性),值為 low、high 或 auto。
這樣一來,我們就能傳達降低較不重要資源優先順序的訊息,例如非重要樣式、圖片或擷取 API 呼叫,以減少爭用。我們也可以提高更重要項目的優先順序,例如主打圖片。
以 Oodle 應用程式為例,這實際上引導我們找到一個可進行最佳化的實用位置。
在圖片中加入延遲載入功能前,瀏覽器會在一開始就擷取輪轉介面中的所有塗鴉圖片,並優先處理。可惜的是,使用者最重視的是輪轉介面中間的圖片。因此我們將背景圖片的重要性設為非常低,前景圖片則設為非常高,結果在緩慢的 3G 網路下,我們擷取及算繪這些圖片的速度快了兩秒。因此整體體驗相當良好。
我們希望在幾週內將這項功能帶到 Canary,敬請期待。
制定網頁字型載入策略
排版是良好設計的基礎,如果您使用網路字型,最好不要封鎖文字的算繪作業,也絕對不要顯示隱形文字。
我們現在會在 Lighthouse 中強調這一點,並提供「避免在載入網頁字型時顯示隱形文字」稽核。
如果您使用字型臉孔區塊載入網頁字型,瀏覽器會決定在網頁字型擷取時間過長時該怎麼做。部分瀏覽器會等待最多三秒,然後改用系統字型,並在字型下載完成後換回。
我們盡量避免使用這類隱形文字,因此如果網頁字型載入時間過長,我們就無法顯示本週的經典塗鴉。幸好,有了名為「font-display」的新功能,您就能進一步控管這個程序。
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-display: swap;
font-weight: 400;
src: local('Montserrat Regular'), local('Montserrat-Regular'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('montserrat-v12-latin-regular.woff2') format('woff2'),
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('montserrat-v12-latin-regular.woff') format('woff');
}
字型顯示可協助您根據網頁字型切換所需的時間,決定網頁字型的轉譯或備用方式。
在本例中,我們使用的是字型顯示交換。「Swap」會為字型提供零秒的區塊週期,以及無限的交換週期。這表示如果字型需要一段時間才能載入,瀏覽器會立即使用備用字型繪製文字。並在字型可用時進行替換。
就我們的應用程式而言,這項功能很實用,因為我們很早就顯示了一些有意義的文字,並在準備就緒後轉換為網頁字型。
一般來說,如果您使用網路字型 (這是大多數網站的做法),請採用良好的網路字型載入策略。
您可以使用許多網頁平台功能,最佳化字型的載入體驗,但同時也請查看 Zach Leatherman 的 Web Font Recipes 存放區,因為這個存放區非常實用。
減少會妨礙顯示的指令碼
我們可以將應用程式的其他部分提早推送至下載鏈,至少能提早提供一些基本的使用者體驗。
在 Lighthouse 時間軸上,您可以看到在載入所有資源的前幾秒,使用者其實看不到任何內容。
下載及處理外部樣式表會阻礙我們的算繪程序,導致無法取得任何進展。
我們可以嘗試提早傳送部分樣式,藉此最佳化重要算繪路徑。
如果我們擷取負責初始算繪的樣式,並將其內嵌在 HTML 中,瀏覽器就能立即算繪這些樣式,不必等待外部樣式表送達。
在本例中,我們使用名為 Critical 的 NPM 模組,在建構步驟中將重要內容內嵌至 index.html。
雖然這個模組為我們完成大部分的繁重工作,但要讓這個模組在不同路徑之間順暢運作,還是有點棘手。
如果沒有仔細規劃,或網站結構非常複雜,如果一開始沒有規劃應用程式外殼架構,可能很難導入這類模式。
因此,請務必及早考量效能。如果一開始沒有針對效能進行設計,之後很可能會遇到問題。
最後,我們的冒險嘗試獲得回報,成功讓應用程式運作,並提早開始提供內容,大幅縮短畫面首次有效顯示所需時間。
結果
以上是我們對網站進行的一長串效能最佳化作業。讓我們看看結果。這是應用程式在 3G 網路中型行動裝置上的載入情形,最佳化前後的差異如下。
Lighthouse 效能分數從 23 分提高到 91 分。就速度而言,這項進展相當不錯。我們持續檢查並遵循 Lighthouse 報表,因此進行了所有變更。如要瞭解我們在技術上如何實作所有改善項目,歡迎查看我們的存放區,尤其是已併入其中的 PR。
預測成效 - 資料導向的使用者體驗
我們相信機器學習在許多領域的未來發展中,都代表著令人振奮的機會。 我們希望這項概念能激發更多實驗,也就是說,真實資料確實能引導我們打造的使用者體驗。
目前,我們對使用者可能想要或需要的內容,以及值得預先擷取、預先載入或預先快取的內容,做了許多任意決定。如果猜對,我們就能優先處理少量資源,但很難將這項做法擴展到整個網站。
我們現在有資料可供參考,有助於改善最佳化作業。 使用 Google Analytics Reporting API,我們可以查看網站上任何網址的下一個熱門網頁和跳出百分比,進而判斷應優先處理哪些資源。
如果我們將這項技術與良好的機率模型結合,就能避免過度預先擷取內容,進而浪費使用者資料。我們可以善用 Google Analytics 資料,並使用機器學習和 Markov 鏈或神經網路等模型,實作這類模型。
為促進這項實驗,我們很高興宣布推出名為 Guess.js 的新計畫。
Guess.js 專案著重於為網路提供資料驅動的使用者體驗。我們希望這項工具能激發您運用資料提升網頁效能,並進一步發揮創意。這一切都是開放原始碼,今天已在 GitHub 上提供。這項功能是由 Minko Gechev、Gatsby 的 Kyle Matthews、Katie Hempenius 和其他許多人,與開放原始碼社群合作建構而成。
歡迎試用 Guess.js,並分享你的想法。
摘要
分數和指標有助於提升網頁速度,但這些只是手段,並非目標本身。
我們都有在行動裝置上遇到網頁載入緩慢的情況,但現在有機會為使用者提供載入速度極快的優質體驗。
提升成效是一段歷程。許多小變更加總起來,就能帶來大幅收益。只要使用適當的優化工具,並密切關注 Lighthouse 報表,就能為使用者提供更優質、更包容的體驗。
特別感謝:Ward Peeters、Minko Gechev、Kyle Mathews、Katie Hempenius、Dom Farolino、Yoav Weiss、Susie Lu、Yusuke Utsunomiya、Tom Ankers、Lighthouse 和 Google Doodles。