(譯)2019年前端效能優化清單 — 中篇

單車runner發表於2019-01-22

目錄

資源優化

17. 使用 Brotli 或 Zopfli 進行純文字壓縮

在 2005 年,Google推出Brotli,一個新的開源無損資料壓縮格式,現在已經被 所有的現代瀏覽器所支援。實際上,Brotli 比 Gzip 和 Deflate 更有效。壓縮速度可能會非常慢,但這取決於設定資訊,可是緩慢的壓縮過程會提高壓縮率。它仍然可以快速解壓縮,並且您還可以估算您網站的Brotli壓縮成本

只有當使用者通過 HTTPS 訪問網站時,瀏覽器才會採用。Brotli 現在還不能預裝在某些伺服器上,而且如果不自己構建 NGINX 和 UBUNTU 的話很難部署。不過這也並不難,而且它的支援即將到來,例如,從Apache 2.4.26開始 就可以使用它了。Brotli 得到了廣泛的支援,許多 CDN 支援它( AkamaiAWSKeyCDNFastlyCloudlareCDN77),您甚至可以在 還不支援它的CDN上 啟用Brotli(與 service worker 一起)。

在最高階別的壓縮下,Brotli 的速度會變得非常慢,以至於伺服器在等待動態壓縮資源時開始傳送響應所花費的時間可能會使我們對檔案大小的優化無效。但是,對於靜態壓縮,高壓縮比的設定比較受歡迎—— (感謝 Jeremy!)

或者,你可以考慮使用 Zopfli的壓縮演算法,將資料編碼為 DeflateGzipZlib 格式。Zopfli 改進的 Deflate 編碼使得任何使用 Gzip 壓縮的檔案受益,因為這些檔案大小比 用Zlib 最強壓縮後還要小 3% 到 8%。問題在於壓縮檔案的時間是原來的大約 80倍。這就是為什麼雖然 使用 Zopfli 是一個好主意但是變化並不大,檔案都需要設計為只壓縮一次可以多次下載的。

比較好的方法是你可以繞過動態壓縮靜態資源的成本。Brotli 和 Zopfli 都可以用於明文傳輸 —— HTML,CSS,SVG,JavaScript 等。

有什麼方法呢?在最高等級和 Brotli 的 1-4 級動態壓縮 HTML 使用Brotli+Gzip 預壓縮靜態資源。同時,檢查 Brotli 是否支援 CDN,(例如KeyCDN,CDN77,Fastly)。確保伺服器能夠使用 Brotli 或 gzip 處理內容。如果你不能安裝或者維護伺服器上的 Brotli,那麼請使用 Zopfli。

18. 使用 響應式影像WebP

儘可能通過 srcsetsizes<picture> 元素使用響應式圖片。也可以通過 <picture> 元素使用 WebP格式的影像(Chrom,Opera,Firefox soon支援),或者一個 JPEG 的回撥(見 Andreas Bovens 的 code snippet)或者通過使用內容協商(使用 Accept 頭資訊)。Ire Aderinokun 中也有一個關於 將影像轉換為WebP非常詳細的教程

Sketch 本身就支援 WebP,並且 WebP 影像可以通過使用 WebP外掛 從 PhotoShop 中匯出。您也有 其他選擇 可以使用。你可以使用 WordPress 或者 Joomla,也有其他可以輕鬆支援 WebP 的擴充套件,例如 OptimusCache Enabler 以及 Joomla自己支援的擴充套件(通過 Cody Arsenault)。

您需要注意的是,雖然WebP影像檔案大小與 Guetzli和Zopfli相比大小都差不多,但格式 不支援像 JPEG 這樣的漸進式渲染,這就是為什麼使用者在使用良好的舊 JPEG 時會更快地看到實際影像,儘管 WebP 影像可能在網路中載入速度會更快。使用 JPEG,我們可以用一半甚至四分之一的資料保證良好的使用者體驗,然後再載入其餘的資料,而不會像WebP那樣顯示半空的影像。您的決定取決於您的目標:如果您使用WebP,那麼將會減少網路的負載,如果使用JPEG,您將提高使用者體驗。

在 Smashing Magazine 上,我們使用字尾-opt作為影像名稱—例如brotli-compression-opt.png;每當影像包含該字尾時,團隊中的成員就會知道該影像已經被優化。還有 —— shamleless plug!-Jeremy Wagner 在 WebP上發表了一本Smashing書

(譯)2019年前端效能優化清單 — 中篇
響應式影像斷點生成器 自動生成影像和標記生成。

19. 影像是否已恰當優化

現在有一個至關重要著陸頁,有一個特定的圖片的載入速度非常關鍵,確保 JPEGs 是漸進式的並且使用Adept、mozJPEG(通過操縱掃描級來改善開始渲染時間)或者 Guetzli 壓縮,谷歌新的開源編碼器重點是能夠感官的效能,並借鑑 Zopfli 和 WebP。唯一的不足是:處理的時間慢(每百萬畫素 CPU 一分鐘)。至於 png,我們可以使用 Pingo 和 svgo,對於 SVG 的處理,我們使用 SVGOSVGOMG

每一個影像優化的文章會說明,但始終會提醒要保持向量資源乾淨和緊密。確保清理未使用的資源,刪除不必要的後設資料,並減少圖稿中的路徑點數量(從而減少SVG程式碼)。(感謝,Jeremy!)

不過下面還有一些更好的方法:

  • 使用 Squoosh 以最佳壓縮級別(有損或無損)壓縮,調整大小和操作影像。
  • 使用 響應式影像斷點生成器CloudinaryImgix 等服務來自動優化圖片。同樣,在許多情況下,單獨使用 srcsetsize 也會有很不錯的效果。
  • 可以使用 映像堆 檢查響應式標記的效率,這是一種命令列工具,也可以用來測量視口大小和裝置畫素比率的效率。
  • 延遲載入帶有 lazysizes 的影像和iframe,這是一個庫,可以檢測頁面上通過使用者互動(或者我們將在稍後探索的IntersectionObserver)而觸發的任何改變。
  • 格外注意那些會預設載入但可能永遠不會顯示的影像——例如carousels、accordions和image galleries。
  • 考慮通過根據媒體查詢指定不同的影像顯示尺寸來 交換具有sizes屬性 的影像,例如操縱sizes以交換放大鏡元件中的源。
  • 檢查影像 下載的不一致性,以防止對前景和背景圖片的意外下載。
  • 為了優化內部儲存,你可以使用 Dropbox 新的 Lepton格式 進行壓縮,平均將jpeg壓縮22%。
  • 注意 aspect-ratio CSSintrinsicsize-attribute 中的屬性,它們允許我們為影像設定寬高比和size,因此瀏覽器可以提前預留一個預定義的佈局槽,以 避免 在頁面載入期間出現 佈局跳轉
  • 如果你想使用其他方式,你可以嘗試 Edge worker (一種基於CDN的實時過濾器)來剪下和重新排列HTTP/2流,從而更快地通過網路傳送圖片。Edge workers 使用您可以控制的塊的JavaScript流(基本上它們是在CDN邊緣上執行的可以修改流式響應的JavaScript),所以可以控制影像的傳遞。對於server worker 來說,為時已晚,因為您無法控制線上上的內容,但Edge workers 來說確實有效。因此,您可以在針對特定登入頁面逐步儲存的靜態JPEG之上使用它們。

(譯)2019年前端效能優化清單 — 中篇
影像堆疊 輸出的一個示例,這是一個命令列工具,用於測量檢視大小和裝置畫素比的效率。

隨著 客戶端提示(client-hints) 的採用,響應影像的未來可能會發生巨大的變化。客戶端提示是一個HTTP請求頭欄位,例如 DPRViewport-WidthWidthSave-DataAccept (指定影像格式首選項)等。它們通過告知伺服器關於使用者瀏覽器、螢幕、連線等的詳細資訊,然後伺服器可以按照詳細資訊提供如合適格式、合適大小的圖片來填充佈局。通過客戶端提示,我們可以傳送一個將資源選擇從HTML標記轉移到客戶端和伺服器之間的請求-響應協商。

正如 Ilya Grigorik所指出的,客戶端提示完成了影像——它們不是響應影像的替代方案。<picture>元素在HTML標記中提供必要的藝術方向控制。客戶端提示為生成的影像請求提供註釋,從而支援資源選擇自動化。Service Worker在客戶端上提供完整的請求和響應管理功能。例如,service Worker可以向請求新增新的客戶端提示標頭值,以重寫URL並將影像請求指向CDN,根據連線和使用者首選項等來調整響應。它不僅適用於影像資源,而且還是適用於幾乎其他的所有請求。

對於支援客戶端提示的客戶端,可以在影像上 節省42%的位元組,在70%以上的百分比上節省1MB以上的位元組。在Smashing Magazine上,我們也可以測量 19-32%的改進。糟糕的是,客戶端提示仍然需要 獲得一些瀏覽器支援Firefox正在考慮中。但是,如果同時提供正常響應影像標記和 <meta> 客戶端提示標記,那麼瀏覽器將評估響應影像標記並使用客戶端提示HTTP頭請求適當的影像資源。

如果這樣子做還不夠的話。您還可以使用 背景 影像 技術 提高影像的效能。請記住,使用對比度 和模糊不必要的細節(或刪除顏色)也可以減少檔案大小。如果你需要在不影響圖片質量的情況下放大一張小照片的話,可以考慮使用 Letsenhance.io

到目前為止,這些優化只覆蓋了基礎。Addy Osmani已經發布了一份非常詳細的 關於基本影像優化的指南,其中深入介紹了影像壓縮和顏色管理的細節。例如,您可以模糊掉影像中不必要的部分(通過對它們應用高斯模糊過濾器)以減小檔案大小,甚至可以刪除顏色或將影像變為黑白,以進一步縮小影像尺寸。如果是背景影像,從Photoshop中匯出0到10%質量的照片也是絕對可以接受的。奉勸大家 不要在網上使用JPEG-XR

20. 視訊是否已恰當優化

到目前為止,我們已經談論了一些圖片,但是我們一直沒有說到GIF圖片。坦白的說,如果不是載入影響渲染效能和寬頻的重型動畫GIF,建議最好切換到動畫WebP(GIF是後備)或者用 迴圈HTML5 video 替換它們。是的,瀏覽器 處理 video 的速度很慢,而且與影像不同的是,瀏覽器不會預載入 video 內容,但 video 往往要比gif更輕更小。至少我們可以用 Lossy GIFgifsiclegiflossy 來給GIF新增有失真壓縮。

早期測試表明,img 標籤內的內嵌視訊 顯示速度提高了20倍,解碼速度比相同大小的 GIF動圖要 快7倍,另外檔案大小尺寸也會優於GIF。儘管對 Safari技術預覽版 的支援 <img src=".mp4"> 已經 落實,但還遠未被廣泛採用,因為它 不會很快進入Blink

(譯)2019年前端效能優化清單 — 中篇
Addy Osmani 建議用迴圈的內聯視訊來替換動畫GIF。檔案大小的差異很明顯(節省了80%)。

然而,在這片充滿好訊息的土地上,視訊格式多年來一直在大規模發展。很長一段時間以來,我們一直希望WebM能夠成為規範所有這些格式的格式,而 WebP (基本上是 WebM 視訊容器中的一個靜態影像)將成為過時的影像格式的替代品。但是,儘管 WebP 和 WebM 最近 獲得了 支援,可是他們沒有什麼實質性的突破。

2018年,開放媒體聯盟(Alliance of Open Media)釋出了一種名為 AV1 的新型視訊格式。AV1 的壓縮類似於 H.265 編解碼器(H.264的演變),與後者不同的是,AV1是免費的。H.265的收費制使得瀏覽器廠商開始採用效能 相對較高 的 AV1: AV1(類似H.265)的壓縮效果是WebP的兩倍

(譯)2019年前端效能優化清單 — 中篇
AV1很有可能成為網路視訊的終極標準。(圖片來源: Wikimedia.org)

事實上,蘋果目前使用的是 HEIF 格式和 HEVC (H.265),最新iOS上的所有照片和視訊都以這些格式儲存,而非JPEG格式。雖然 HEIFHEVC (H.265) 還沒有完全公開到網路上,但是 AV1 已經完全公開在網路上並且 正在獲得瀏覽器支援。因此,AV1在您的 <video>標記中新增源是合理的,因為所有瀏覽器供應商都在為支援AV1做準備。

目前,最廣泛使用和支援的編碼是H.264,由MP4檔案提供服務,因此在提供檔案之前,請確保使用 多通道編碼 處理MP4 ,模糊了frei0r iirblur效果(如果適用)和 moov atom metadata 移動到檔案的頭部,而伺服器 接受位元組服務。Boris Schapira 為 FFmpeg 提供了最大限度優化視訊的 標準說明。當然,提供WebM格式作為替代方案也會有所幫助。

視訊播放效能本身就是一個故事,如果您想深入瞭解它,請閱讀Doug Sillar關於 視訊視訊傳輸最佳實踐的最新實踐系列,其中包括關於視訊傳輸度量、視訊預載入、壓縮和流媒體的詳細資訊。

(譯)2019年前端效能優化清單 — 中篇
Zach Leatherman 的 字型載入策略綜合指南 為更好的 web 字型交付提供了十幾種選擇。

21. Web 字型是否已恰當優化

首先需要問一個問題,你是否能不使用 UI 系統字型。 如果不可以,那麼你有很大可能使用 Web 網路字型,會包含字形和額外的功能以及用不到的加粗。如果您使用的是開源字型,您可以向字型設計公司 獲取 網路字型子集或子集,也可以使用 GlyphhangerFontsquirrel 對其進行子集化。您甚至可以使用 Peter Muller 的 subfont 將整個流程自動化,因為他是一個命令列工具,它通過靜態地分析您的頁面,然後生成最優的web字型子集,最後將它們注入到您的頁面中。

WOFF2的支援 非常好,對於不支援WOFF2的瀏覽器,你可以使用 WOFF 和 OTF 作為不支援它的瀏覽器的備選。另外,從 Zach Leatherman 的 《字型載入策略綜合指南》(程式碼片段也 可以作為Web字型 載入片段)中選擇一種策略,並使用伺服器快取持久地快取字型。

可能今天要考慮的更好的選擇是 關鍵FOFT預載入折中 方法。它們都使用兩階段渲染來逐步提供Web字型——首先是使用web字型快速準確地呈現頁面所需的小超子集,然後載入其餘的非同步操作。不同之處在於,折中 技術僅在不支援 字型載入事件 時才非同步載入polyfill ,因此預設情況下不需要載入polyfill。需要速戰速決嗎?Zach Leatherman有一個 23分鐘的快速教程 和案例研究,以使您的字型優化。

通常,使用 preload 資源提示來預載入字型是一個好辦法,但在標記中要包含關鍵CSS和JavaScript連結後的提示。否則,字型載入將在第一次渲染時造成損失。不過,有 選擇性 地選擇最重要的檔案可能是一個好主意,比如那些對渲染至關重要的檔案,或者那些可以幫助您避免可見和破壞性文字重拍的檔案。一般來說,Zach建議 預載入每個系列的一到兩種字型——如果字型不是很重要,那麼建議延遲載入。

我相信沒有人喜歡等待內容的顯示。使用 font-display CSS描述符,我們可以控制字型載入行為,並使內容立即可讀 (font-display: optional) 或幾乎立即可讀 (font-display: swap)。但是,如果您想 避免文字重排,我們仍然需要使用字型載入API,特別是對 重組 進行 分組,或者當您使用第三方主機時,除非您可以 將Google字型與Cloudflare Workers一起使用。談到谷歌字型::可以考慮使用 Google -webfonts-helper,這是一種輕鬆自我託管Google字型的方式。如果可以的話,採用 始終自行託管自身專案的字型 的方式來獲得最大程度的控制。

一般情況下,如果您使用font-display: optional,也可能不是一個好主意,因為preload會提前觸發Web字型請求(如果您有其他需要獲取的關鍵路徑資源,則會導致網路擁塞)。使用preconnect可以實現更快的跨域字型的請求,但需要注意的是,preload從不同來源預載的字型wlll會導致網路佔用。所有這些技術都包含在 Zach 的 Web字型載入方式 中。

此外,如果使用者在可訪問性首選項中啟用了 Reduce Motion,或者選擇了Data Saver模式(請參見 Save-Data header),或者當使用者連線速度較慢時(通過 網路資訊API),則最好選擇退出Web字型(或至少是第二階段渲染)。

要測量Web字型載入效能,請考慮 所有文字可見 度量標準(所有字型已載入且所有內容以 Web字型顯示的時刻),以及首次渲染後的 Web字型重排計數。顯然,兩個指標越低,效能越好。重要的是要注意 可變 字型 可能需要 顯著的效能考慮。它們為設計人員提供了更廣闊的設計空間來選擇字型,但它的代價是單個序列請求,而不是單個檔案請求。單個請求可能會阻礙頁面上的整個排版外觀。但好訊息是,當我們有了可變字型,在預設情況下只需要獲得一個 reflow,而不需要 JavaScript 對重新繪製進行分組。

怎麼才能是一個無漏洞的字型載入策略? 從font-display開始,然後到 Font Loading API,然後到 Bram Stein 的 Font Face Observer(感謝 Jeremy!)如果你有興趣從使用者的角度來衡量字型載入的效能, Andreas Marschke 探索了 使用 Font API 和 UserTiming API 進行 效能跟蹤

此外,不要忘記包含 font-display:optional 描述符來提供彈性和快速的字型回退,unicode-range 將大字型分解成更小的語言特定的字型,以及 Monica Dinculescu 的 字型樣式匹配器 用來解決由於兩種字型之間的大小差異,最大限度地減少了佈局上的震動的問題。

構建優化

22. 分清輕重緩急

你應該知道優先處理什麼。執行你所有靜態資源(JavaScript、圖片、字型、第三方指令碼和頁面中“昂貴的”模組,比如:輪播圖、複雜的圖表和多媒體內容),並將它們劃分成組。

建立一個電子表格。針對傳統瀏覽器定義基本的核心體驗(即完全可訪問的核心內容),針對多功能的瀏覽器定義增強的體驗(即豐富的、完整的體驗)和額外的體驗(不是絕對需要的並且可以延遲載入的資源,如web字型、不必要的樣式、輪播圖、視訊播放器、社交媒體按鈕、大圖片等)。不久前,我們發表了一篇關於 Improving Smashing Magazine的效能 的文章,上面有該方法的詳細介紹。

我們在優化效能時候的優先順序是:首先載入核心體驗,然後是增強功能,最後才是附加功能。

23. 考慮使用 cutting-the-mustard 技術

現在,我們仍然可以使用 cut -the-mustard 技術 將核心體驗傳遞給傳統瀏覽器,並提高對現代瀏覽器的體驗。該技術的 下一版本 將使用 ES2015 + <script type="module">。現代瀏覽器會將指令碼解釋為JavaScript模組並按預期執行,而傳統瀏覽器不會識別該屬性並忽略它,因為它是未知的 HTML 語法。

目前我們需要記住的是,僅僅是特徵檢測不足以做出關於將有效載荷傳送到瀏覽器的決定。從其自身來看,cutting-the-mustard 可以從瀏覽器版本中推斷出裝置的能力,這已經不是我們今天能夠做到的事情了。

例如:在發展中國家,廉價的安卓手機主要執行 Chrome,雖然它們的記憶體和 CPU 有限,但仍能滿足要求。最終,使用 Device Memory Client Hints Header,我們就能夠更可靠地識別出低端裝置。現在,只有在 Blink 中才支援 header (通常用於 client hints)。因為裝置儲存也有一個在Chrome 中可以呼叫的 JavaScript API,一種選擇是基於 API 的特性檢測,只在不支援的情況下回退到 符合標準 技術(謝謝,Yoav!)。

24. 解析 JavaScript 是昂貴的,所以保持小

在處理單頁應用程式時,我們需要一些時間來初始化應用程式,然後才能呈現頁面。您的設定將需要定製的解決方案,但是您可以注意使用模組和技術來加快初始呈現時間。例如,下面是 如何除錯 React 效能消除常見的 React 效能問題,以及 如何在 Angular 中提高效能。通常,大多數效能問題來自於啟動應用程式的初始解析時間。

JavaScript 有成本,但不一定是檔案大小影響效能。解析和執行時間的不同很大程度依賴裝置的硬體。在一臺普通的手機上(Moto G4),僅解析 1MB (未壓縮的)的 JavaScript 大概需要 1.3-1.4 秒,會有 15 - 20% 的時間耗費在手機的解析上。在執行編譯過程中,只是用在 JavaScript 準備平均需要 4 秒,在手機上頁面展示其主要內容所需的時間(First Meaningful Paint)需要 11 秒。解釋:在低端移動裝置上,解析和執行時間可以輕鬆提高 2 至 5 倍

作為一個開發人員,為了保證高效能,我們需要找到編寫和部署更少 JavaScript 的方法,這就是詳細檢查每個 JavaScript 依賴項的好處所在。

下面這些工具可以幫助您對依賴關係和可行替代方案的影響做出明智的決定:

使用 Ember 在 2017 年引入的 二進位制模板( binary templates) 可以巧妙的避免解析開銷過大。使用它們,Ember的建議:使用二進位制模板將 JavaScript 解析替換為 JSON 解析,解析速度可能會更快。(謝謝,Leonardo,Yoav !)

衡量 JavaScript 的解析和編譯時間。我們可以使用綜合測試工具和瀏覽器跟蹤來跟蹤解析時間,瀏覽器開發商正在討論在將來 公開 RUM-based 的處理時間。或者,你也可以考慮使用 Etsy 的 DeviceTiming,一個讓你可以指導你的 JavaScript 在任何裝置或瀏覽器上測量解析和執行時間的小工具。

最重要的是:雖然指令碼大小很重要,但它並不是一切。因為當指令碼大小增加時,解析和編譯時間 不一定會隨著指令碼的變大而相應的增加

25. 使用 無用程式碼移除(Tree-shaking) ,作用域提升(Scope hoisting)和程式碼分割(Code-splitting)來減少有效負載

Tree-shaking是一種清理構建過程的方法,通過只載入生產中實際使用的程式碼並清除在 Webpack 中 未使用的 import。使用 Webpack 和 Rollup,當然我們還可以使用 scope hoisting(作用域提升),scope hoisting 允許工具檢測哪些 import 可以被提升或者可以轉換成一個行內函數。有了 Webpack ,你現在可以使用 JSON Tree Shaking

而且,你需要考慮如何 編寫高效的 CSS 選擇器 以及如何 避免編寫臃腫和開銷浪費的樣式。你也可以使用 Webpack 縮短類名和在編譯時使用獨立作用域來 動態地重新命名 CSS 類

Code-splitting 是 Webpack 的另一個特性,可將你的程式碼分解為按需載入的 。並不是所有的 JavaScript 都是必須下載、解析和編譯的。一旦你在程式碼中確定了分割點,Webpack 會處理這些依賴關係和輸出檔案。這樣,在應用傳送請求的時候,基本上確保初始的下載足夠小並且實現按需載入。Alexander Kondrov 對 使用 Webpack 和 React 進行程式碼拆分 做了精彩的介紹。

另外,考慮使用 preload-webpack-plugin 獲取程式碼拆分的路徑,然後使用 <link rel="preload"> 或者 <link rel="prefetch"> 提示瀏覽器預載入它們。Webpack 內聯指令 還提供了一些對 preload / prefetch 的控制。

在哪裡定義分離點?通過追蹤哪些 CSS/JavaScript 塊被使用和哪些沒有被使用。Umar Hansa 解釋 了你如何使用 Devtools 的 Code Coverage 來實現。

如果你沒有使用 Webpack,那麼相比於 Browserify 的輸出結果,Rollup 的輸出更好一些。我們在此過程中,可以檢視 rollup-plug -closure-compilerRollupify,它們將 ECMAScript 2015 模組轉換成一個大的 CommonJS module —— 因為根據您對 bundler 和 module system 的選擇,小模組的 效能成本會高得驚人

26. 能否將 JavaScript 解除安裝到 Web Worker 中

為了減少對時間與互動的影響,可以考慮將繁重的 JavaScript 解除安裝到 Web Worker 或通過 Service Worker 進行快取。

隨著程式碼庫的程式碼量不斷增長會導致UI效能出現瓶頸,這會直接導致使用者體驗降低。這是因為 DOM 操作在主執行緒上與 JavaScript 一起執行。通過 web workers,我們可以將這些繁瑣的操作轉移到不同執行緒上的後臺程式上執行。web workers 的典型用例是通過 預取資料和漸進式 web 應用程式,來提前載入和儲存一些資料,以便在需要的時候使用。您還可以使用 Comlink 來簡化主頁和 worker 之間的通訊。我們正在無限接近目標,但在這個過程中我們還有一些工作要做。

Workerize 允許你將一個模組移動到一個Web Worker 中,自動將匯出的函式反映為非同步代理。您可以在 Webpack 中使用 workerize-loader 或者 worker-plugin

請注意,Web Worker 不能訪問 DOM,因為 DOM 不是一個 安全執行緒,並且執行的程式碼需要包含在一個單獨的檔案中。

27. 能否將 JavaScript 解除安裝到 WebAssembl 中

我們還可以將 JavaScript 轉換成 WebAssembly(一種二進位制指令格式),然後設計成一個行動式目標,用於編譯如 C/ c++ /Rust 這種高階語言。它的 瀏覽器支援也是非常好,而且隨著 JavaSript 和 WASM 之間的函式呼叫越來越快(至少在Firefox中是這樣),它最近已經可以實現了。

在實際場景中,如果一個在一個較小的陣列上,JavaScript 的效能要優於 WebAssembly,反之則是 WebAssembly 的效能優於 JavaScript)。對於大多數 web 應用程式而言,JavaScript是更好的選擇,而 WebAssembly 更適合用於類似 web 遊戲這種計算密集型的 web 應用程式。所以,切換到 WebAssembly 是否會帶來顯著的效能提升是值得研究去好好研究一番。

如果你想了解更多關於 WebAssembly 的資訊:

(譯)2019年前端效能優化清單 — 中篇
Milica Mihajlija 為我們提供了 一個關於WebAssembly的工作原理以及它的用處的概述

28. 您使用的是預編譯器嗎

使用 預編譯器 可以將一些客戶端渲染解除安裝到 伺服器,從而快速輸出可用的結果。最後,可以考慮使用 [optimization.js(github.com/nolanlawson…) 來封裝急切呼叫的函式(不過 可能不再需要它了)來加快初始載入速度。

(譯)2019年前端效能優化清單 — 中篇
Addy Osmani 的 從快速預設到現代載入的最佳實踐

29. 僅將遺留程式碼提供給傳統瀏覽器

隨著 現代瀏覽器 對 ES2015 的支援越來越好,考慮 使用 babel-preset-env 只轉換現代瀏覽器不支援的 ES2015+ 的特性。然後 設定兩個構建,一個為 ES6 一個為 ES5。就像上面所說的那樣,現在所有 主流瀏覽器都支援 JavaScript 模組,我們可以 使用 script type="module" 讓具有 ES 模組支援的瀏覽器載入檔案,而老的瀏覽器可以載入傳統的 script nomodule。我們可以使用 Webpack ESNext Boilerplate 自動完成整個流程。

請注意,現在我們可以在不需要編譯器或繫結器的情況下編寫在瀏覽器中本地執行的基於模組的 JavaScript。<link rel="modulepreload"> header 提供一種方法來啟動模組指令碼的早期(高優先順序)載入。基本上,這是一種有助於最大化頻寬使用的有效方式,通過告知瀏覽器它需要獲取什麼,以使其不會在那些長時間往返的過程中被卡住。Jake Archibald 也發表了一篇詳細的文章,其中 介紹了 ES 模組中需要記住的問題和內容,值得一看。

對於 loadsh,使用 babel-plugin-lodash 將只載入你僅在原始碼中使用的模組。您的依賴項可能還依賴於 Lodash 的其他版本,因此您需要 將通用 lodash 轉換成適合自己專案的 loadsh,以避免程式碼重複。這可能會為您節省相當多的 JavaScript 負載。

Shubham Kanodia 編寫了一份 關於智慧捆綁的詳細低維護指南:將遺留程式碼與您可以立即使用的程式碼片段一起交付到生產環境中的傳統瀏覽器。

(譯)2019年前端效能優化清單 — 中篇
Jake Archibald 釋出了一篇詳細的文章,介紹了 ES 模組中需要記住的問題和內容,例如,內聯指令碼被推遲到阻塞外部指令碼和內聯指令碼執行時才執行。

30. 您是否在JavaScript中使用差異化服務

我們想通過網路傳送必要的 JavaScript 程式碼,但這意味著對這些資源的交付要更加專注和細緻。不久前,Philip Walton 提出了差異化服務 的概念。其思想是編譯並提供兩個獨立的 JavaScript 包:一個是 常規 (帶有 Babel-transforms 和 polyfill )的構建包,另一個是沒有 Babel-transforms 或 polyfill 的包,這兩個包都是隻提供給實際需要它們的傳統瀏覽器。

因此,我們通過降低瀏覽器需要處理的指令碼的數量來減少阻塞主執行緒的程式。Jeremy Wagner 發表了一篇文章,全面的介紹了差異化服務 以及如何在2019年的構建管道中設定它,從設定 Babel,到需要在 Webpack 中進行哪些調整,以及做這些工作的好處。

31. 通過增量解耦識別和重寫遺留程式碼

長期存在的專案有收集灰塵和過時程式碼的趨勢。重新考慮你的依賴,評估需要多少時間來重構或重寫那些最近一直在導致問題的遺留程式碼。如果您瞭解了遺留程式碼的影響,您就可以從 增量解耦 開始解決這項艱鉅的任務。

首先,建立一個度量標準,跟蹤遺留程式碼呼叫的比例,看看是保持不變還是下降,如果呼叫不是上升,那就公開阻止團隊使用這個庫,並確保 CI 在 pull 請求中使用得時候可以 通知 到開發人員。polyfills 可以使用標準瀏覽器功能幫助遺留程式碼重寫程式碼庫。

32. 識別並刪除未使用的 CSS / JavaScript

Chrome 中的 CSS 和 JavaScript 程式碼覆蓋 允許您瞭解哪些程式碼已經執行/應用,哪些程式碼還沒有執行。您可以開始記錄覆蓋率,在頁面上執行操作,然後研究程式碼覆蓋率結果。檢測到未使用的程式碼後,使用 import() 查詢這些模組和延遲載入(參見整個執行緒)。然後重複覆蓋率配置檔案,並驗證它現在在初始載入時提供的程式碼更少。

您可以使用 Puppeteer 以程式設計方式收集程式碼覆蓋率,Canary 已經允許您 匯出程式碼覆蓋率結果。正如 Andy Davies 所指出的,您可能希望 為現代瀏覽器和傳統瀏覽器收集程式碼覆蓋率。對於 Puppeteer,還有許多 其他的用例,例如,在每次構建時自動地對未使用的CSS進行視覺區分監視

此外,purgecssUnCSSHelium 可以幫助您從 CSS 中刪除未使用的樣式。如果你不確定程式碼是否被使用,您可以按照 Harry Roberts的建議:為一個特定類建立一個 1×1 px 透明的 GIF 圖片並將其放在 dead/ 目錄下,例如:/assets/img/dead/comments.gif。然後,在CSS中相應的選擇器上將特定的影像設定為背景,如果檔案出現在日誌中,則等待幾個月。如果檔案沒有出現在日誌中,那就是沒有人在螢幕上渲染該遺留元件,您可以把它全部刪除。

對於一個有想法的小夥伴來說,可以考慮通過 使用並監視DevTools,在一組頁面上自動收集未使用的 CSS。

33. 修剪 JavaScript 依賴項的大小

正如 Addy Osmani 所指出的,當您只需要一小部分 JavaScript庫時,您很可能會提供完整的JavaScript 庫,以及不需要這些庫的瀏覽器的過時 polyfill,或者只是重複程式碼。為了避免這種開銷,可以考慮使用 webpack-libs-optimization來刪除構建過程中未使用的方法和 polyfill

將捆綁稽核對於您多年前新增的大型庫,可能有一些輕量級的替代方案,例如Moment.js可以用 date-fnsLuxon 代替。BenediktRötsch 的研究 表明,從 Moment.jsdate-fns 的轉換可能會使 3G 和低端手機上的首次使用時間節省大約 300ms。

這就是像 Bundlephobia 這樣的工具可以幫助我們監測到向包中新增 npm 包的成本。您甚至可以 將這些成本與 Lighthouse Custom Audit 相結合。這也適用於框架。通過刪除或修剪 Vue MDC介面卡(Vue的材料元件),樣式大小從 194KB 下降到 10KB。

喜歡冒險嗎?你可以看看 Prepack。它將JavaScript 編譯成等效的 JavaScript 程式碼,但與 Babel 或 Uglify 不同的是,它允許編寫正常的 JavaScript 程式碼,並輸出執行速度更快的等效 JavaScript 程式碼。

作為整個框架的替代,您甚至可以修剪框架並將其編譯為不需要額外程式碼的原始JavaScript包。Svelte 做到了,還有 Rawact Babel plugin 也做到了。在構建時將 React.js 元件轉換為本地 DOM 操作。為什麼?正如維護者所解釋的,react-dom 包含了所有可能被渲染的元件/HTMLElement 的程式碼,包括用於增量渲染、排程、事件處理等的程式碼。但是有些應用程式(在初始頁面載入時)不需要所有這些特性。對於這樣的應用程式,使用原生DOM操作來構建互動式使用者介面可能更好。

(譯)2019年前端效能優化清單 — 中篇
Benedikt Rotsch 的文章 中,展示了一個從 Moment.jsdate-fns,在 3G 和低端手機上首次使用時,可以節省大約 300ms 的時間的轉變。

34. 您是否正在使用 JavaScript 塊的預測預取

我們可以使用 heuristics 來決定何時預載入 JavaScript 塊。Guess.js 是一套使用谷歌分析資料的工具和庫,用來確定使用者最可能從特定頁面訪問的頁面。根據從谷歌分析或其他來源收集的使用者導航模式,Guess.js 構建了一個機器學習模型來預測和預取每個後續頁面所需的 JavaScript。

因此,每個互動元素都會收到一個參與概率得分,基於這個分數,客戶端指令碼決定提前預取資源。你可以把這項技術應用到 Next.js applicationAngular和React,還有一個 Webpack 外掛,它也可以自動完成設定過程。

顯然,很明顯,你可能會讓瀏覽器讀取不需要的資料並提前獲取不需要的頁面,所以在這些預先獲取的請求的數量上做個相對保守的選擇是個好主意。一個好的用例在檢查過程中預取所需的驗證指令碼,或者在關鍵的動作呼叫進入檢視時進行預取。

需要不那麼複雜的東西嗎?Quicklink 是一個小庫,它在空閒時間自動預取檢視中的連結,以加快載入下一頁導航的速度。不過,它也考慮到了資料,因此它不會在 2G 或 Data-Saver 時預取。

####35. 利用針對目標 JavaScript 引擎的優化

研究 JavaScript 引擎在使用者基礎中佔的比例,然後探索優化它們的方法。例如,當優化的 V8 引擎是用在 Blink 瀏覽器,Node.js 執行和 Electron 的時候,對每個指令碼使用指令碼流。一旦下載開始,它允許 asyncdefer scripts在一個單獨的後臺執行緒進行解析,因此在某些情況下,提高 10% 的頁面載入時間。實際上,在 <head> 中使用 <script defer>,以便 瀏覽器更早地可以發現資源,然後在後臺執行緒中解析它。

警告:Opera Mini 不支援 defement 指令碼,如果你正在印度和非洲從事開發工作,defer 將會被忽略,導致阻塞渲染直到指令碼載入(感謝 Jeremy)!。

(譯)2019年前端效能優化清單 — 中篇
漸進引導:使用伺服器端渲染獲得首次有效繪製,但也包含一些最小必要的 JavaScript 來保持實時互動來接近首次有效繪製。

36. 客戶端渲染還是伺服器端渲染

在兩種場景下,我們的目標應該是建立 漸進引導:使用服務端呈現獲得首次有效繪製,而且還要包含一些最小必要的 JavaScript 來保持實時互動來接近首次有效繪製。如果 JavaScript 在首次有效繪製沒有獲取到,那麼瀏覽器可能會在解析時 鎖住主執行緒,編譯和執行最新發現的 JavaScript,從而對 站點或應用程式互動性 造成限制。

為了避免這樣做,總是將執行函式分離成一個個,非同步任務和可能用到 requestIdleCallback 的地方。考慮 UI 的懶載入部分使用 WebPack 動態 import()支援,避免載入、解析和編譯開銷直到使用者真的需要他們(感謝 Addy!)。

在本質上,互動時間(TTI)告訴我們導航和互動之間的時間。度量是通過在視窗初始內容呈現後的第一個五秒來定義的,在這個過程中,JavaScript 任務都不超過 50ms。如果發生超過 50ms 的任務,則重新開始搜尋五秒鐘的視窗。因此,瀏覽器首先會假定它達到了互動式(Interactive),只是切換到凍結狀態(Frozen),最終切換回互動式(Interactive)。

一旦我們達到互動式(Interactive),然後,我們可以按需或等到時間允許,啟動應用程式的非必需部分。不幸的是,隨著 Paul Lewis 提到的,框架通常沒有優先順序的概念,因此漸進式引導很難用大多數庫和框架實現。如果你有時間和資源,使用該策略可以極大地改善前端效能。

是在客戶端渲染還是伺服器端渲染?如果對使用者的體驗沒有明顯的提升,那麼 可能我們不會在客戶端渲染,因為在實際情況中,伺服器端渲染的HTML可能更快。或許,您可以 用靜態站點生成器預先載入一些內容 直接推到 CDN 上,並在頂部使用一些 JavaScript。

將客戶端框架的使用限制在絕對需要它們的頁面上。為了防止伺服器渲染比客戶端渲染慢,可以考慮 在構建時預渲染 和動態 CSS 內聯,以生成可用於生產的靜態檔案。Addy Osmani 做了一個關於 JavaScript 成本精彩演講,值得一看。

37. 限制第三方指令碼的影響

隨著所有效能優化的到位,我們常常無法控制來自業務需求的第三方指令碼。第三方指令碼的度量不受使用者體驗的影響,所以,一個單一的指令碼常常會以呼叫令人討厭的,長長的第三方指令碼為結尾,因此,破壞了為效能專門作出的努力。為了控制和減輕這些指令碼帶來的效能損失,僅非同步載入(可能通過 defer)和通過資源提示,如:dns-prefetch 或者 preconnect 加速他們是不足夠的。

正如 Yoav Weiss 在他的 必須關注第三方指令碼的通訊 中解釋的,在很多情況下,下載資源的這些指令碼是動態的。頁面負載之間的資源是變化的,因此我們不知道主機是從哪下載的資源以及這些資源是什麼。

這時,我們有什麼選擇?考慮通過一個超時來使用 service workers 下載資源,如果在特定的時間間隔內資源沒有響應,返回一個空的響應告知瀏覽器執行解析頁面。你可以記錄或者限制那些失敗的第三方請求和沒有執行特定標準請求。您還可以選擇,從 您自己的伺服器 而不是從供應商的伺服器 載入第三方指令碼

(譯)2019年前端效能優化清單 — 中篇
Casper.com 發表了一篇詳細的案例研究,講述了他們是如何通過自我託管的優化,使網站縮短了 1.7s。這也許是值得的。

另一個選擇是建立一個 內容安全策略(CSP) 來限制第三方指令碼的影響,比如:不允許下載音訊和視訊。最好的選擇是通過 <iframe> 嵌入指令碼使得指令碼執行在 iframe 環境中,因此如果沒有接入頁面 DOM 的許可權,在你的域下不能執行任何程式碼。Iframe 可以 使用 sandbox 屬性進一步限制,因此你可以禁止 iframe 的任何功能,比如阻止指令碼執行,阻止警告、表單提交、外掛、訪問頂部導航等等。

例如,它可能必須要允許指令碼執行 <iframe sandbox="allow-scripts">。每一個限制都可以通過多種 [allowsandbox 屬性中(幾乎處處支援)解除,所以將它們限制在允許做的最低限度。

考慮使用Intersection Observer;這將使廣告嵌入 iframe 的同時仍然排程事件或需要從 DOM 獲取資訊(例如廣告知名度)。注意新的策略如 功能策略、資源的大小限制、CPU 和頻寬優先順序限制損害的網路功能和會減慢瀏覽器的指令碼,例如:同步指令碼,同步 XHR 請求,document.write 和超時的實現。

對第三方進行壓力測試,在 DevTools 上自底向上概要地檢查頁面的效能,測試在請求被阻止或超時後會發生什麼情況,對於後者,你可以使用 WebPageTest 的 Blackhole 伺服器 Blackhole .webpagetest.org,你可以在你的 hosts 檔案中指定特定的域名。最好是 自主主機並使用單個主機名,但是同時 生成一個請求對映,當指令碼變化時,暴露給第四方呼叫和檢測。您可以使用 Harry Roberts 的方法來 審計第三方,並生成 這樣的 電子表格。Harry 還在他 關於第三方效能和審計的演講 中解釋了審計工作流。

(譯)2019年前端效能優化清單 — 中篇
圖片來源: Harry Roberts

38. 正確設定 HTTP 快取頭

再次檢查一遍 expirescache-controlmax-age 和其他 HTTP cache 頭部都是否設定正確。通常,資源應該是可快取的,不管是短時間的(如果它們很可能改變),還是無限期的(如果它們是靜態的)——你可以在需要更新的時候,改變它們 URL 中的版本即可。在任何資源上禁止頭部 Last-Modified 都會導致一個 If-Modified-Since 條件查詢,即使資源在快取中。與 Etag 一樣,即使它在使用中。

使用 Cache-control: immutable,其實是為了解決fingerprinted靜態資源的快取問題而被設計出來的,解決了客戶端revalidation問題(截至 2017年12月,在 FireFox,Edge 和 Safari 中支援;只有 FireFox 在 https:// 中支援)。實際上,HTTP存檔中的所有頁面,2% 的請求和 30% 的站點幾乎都包含 至少1個不可更改的響應。此外,大多數使用它的網站都有一個指令,這個指令是對資源具有較長的新鮮度生命週期。

還記得 stale-while-revalidate 嗎?您可能知道,我們使用 Cache-Control 響應頭指定快取時間,例如:Cache-Control: max-age=604800。等到 604800s 過後,快取會重新獲取請求的內容,這直接導致頁面載入速度變慢。我們可以使用 stale-while-revalidate 來避免這種情況的發生:它基本上是定義了一個額外的時間視窗,只要在此期間後臺重新驗證它的非同步性,那麼快取久可以直接使用過期的資源。因此,它在客戶端可以 隱藏 延遲(包括網路和伺服器上的延遲)。

在2018年10月,Chrome 釋出了一個 意圖,打算在 HTTP Cache-Control 報頭中提供 stale-while-revalidate,因此,隨著過期資源不再處於關鍵的狀態,它將會提高後續頁面的載入時間。結果:重複檢視的RTT為零

你也可以使用 Heroku 的 HTTP 快取頭部,Jake Archibald 的 快取最佳實踐(Caching Best Practices) ,以及 Ilya Grigorik 的 HTTP快取入門(HTTP caching primer) 作為指導。而且,注意 不同的頭部,尤其是在 關係到 CDN 時,並且要注意 關鍵標頭檔案,有助於避免在新請求稍有差異時進行額外的驗證,但從以前請求標準,並不是必要的(感謝,Guy!)。

另外,要仔細檢查您是否傳送了 不必要的標題 (例如,x-power -bypragmax-ua-compatibleexpires等),以及是否包含了 有用的安全性和效能標題 (例如 Content-Security-PolicyX-XSS-ProtectionX-Content-Type-Options等)。最後,請記住單頁面應用程式中 CORS請求的效能成本

交付優化

39. 非同步載入 JavaScript

當使用者請求頁面時,瀏覽器獲取 HTML 並構造 DOM,然後獲取 CSS 並構造 CSSOM,然後通過匹配 DOM 和 CSSOM 生成一個渲染樹。如果有任何的 JavaScript 需要解決,瀏覽器將不會開始渲染頁面,直到 JavaScript 解決完畢,這樣就會延遲渲染。 作為開發人員,我們必須明確告訴瀏覽器不要等待並立即開始渲染頁面。 為指令碼執行此操作的方法是使用 HTML 中的 deferasync 屬性。

事實證明,我們 應該 把 async 改為 defer(因為 ie9 及以下 不支援 async)。根據 Steve Souders 的說法,一旦載入到 async 指令碼時,它們就會立即執行。如果這種情況發生得非常快,並且 async 指令碼已經處於快取中時,它實際上會阻塞 HTML 解析器。所以我們可以使用延遲,來保證瀏覽器在解析 HTML 之前不會執行指令碼。因此,除非您在開始渲染之前需要執行 JavaScript ,否則最好使用 defer

另外,從上面的描述來看,我們需要限制第三方庫和指令碼的影響,特別是使用社交共享按鈕和嵌入的 <iframe>(如地圖)。大小限制 有助於 防止 JavaScript 庫過大:如果您不小心新增了大量依賴項,該工具將通知您並丟擲錯誤。您可以使用靜態社交分享按鈕(如通過 SSBG)和 靜態連結來代替互動式地圖。您還可以 修改非阻塞指令碼載入程式以實現 CSP 合規性

40. 使用 IntersectionObserver 延遲載入昂貴的元件

如果您需要延遲載入圖片、視訊、廣告指令碼、A/B 測試指令碼或任何其他資源,最有效的方法是使用 Intersection Observer API,該 API 提供了一種 非同步觀察目標元素與祖先元素或頂級文件視口的交集變化的方法。基本上,您需要建立一個新的 IntersectionObserver 物件,它接收回撥函式和一組選項。然後我們新增一個目標來觀察。

當目標變得可見或不可見時,回撥函式就會執行,因此當它攔截viewport時,您可以在元素變得可見之前開始執行一些操作。事實上,我們可以精確地控制觀察者的回撥何時被呼叫,使用 rootMarginthreshold(一個數字或者一個數字陣列來表示目標可見度的百分比)。

Alejandro Garcia Anglada 發表了一個關於如何在實際應用場景當中去實現它的 簡易教程,Rahul Nanwani 寫了一篇詳細介紹 關於延遲載入前景和背景影像文章,Google Fundamentals 也提供了一篇 關於 Intersection Observer 延遲載入影像和視訊詳細教程。您也可以 使用 Intersection Observer 實現 高效的滾動測試(performant scrollytelling)

另外,請了解一下 lazyload,它是一個允許我們指定哪些影像和 iframes 應該本地延遲載入的 attribute。LazyLoad 將提供一種允許我們在每個域的基礎上強制選擇是否使用 LazyLoad 功能的機制(類似於 內容安全策略 的工作方式)。額外的好處:一旦釋出,優先順序提示將允許我們在標題中指定指令碼和預載入的重要性(目前在 Chrome Canary 中)。

41. 逐步載入影像

你甚至可以通過向你的網頁新增 漸進式圖片載入 來將延遲載入提升到新的水平。 與 Facebook,Pinterest 和 Medium 類似,你可以先載入低質量或模糊的影像,然後當頁面繼續載入時,使用 Guy Podjarny 提出的 LQIP (Low Quality Image Placeholders) technique(低質量影像佔位符)技術 替換它們的清晰版本。

如果技術改善了使用者體驗,觀點就不一樣了,但它肯定會提高第一次有意義的繪畫的時間。我們甚至可以通過使用 SQIP 建立影像的低質量版本作為 SVG 佔位符或者使用帶有 CSS 線性漸變的 漸變影像佔位符 來實現自動化。 這些佔位符可以嵌入 HTML 中,因為它們自然可以用文字壓縮方法壓縮。 Dean Hume 在 他的文章 中描述瞭如何使用 Intersection Observer 來實現這種技術。

瀏覽器支援嗎?可以說相當好,Chrome、Firefox、Edge 和 Samsung Internet 這些瀏覽器都已經支援。WebKit 目前 正在開發中。如果瀏覽器不支援呢? 如果不支援Intersection Observer,我們仍然可以 延遲載入 一個 polyfill 或立即載入影像。甚至還有一個 庫(library)

想把延遲載入做到極致?您可以嘗試著 跟蹤影像,並使用原始形狀和邊緣建立輕量級SVG佔位符,先載入它,然後從佔位符向量影像過渡到(載入的)點陣圖影像。

(譯)2019年前端效能優化清單 — 中篇
Jose M. Perez的 SVG 延遲載入技術

42. 快速推送關鍵 CSS

為確保瀏覽器儘快開始渲染頁面,通常 會收集開始渲染頁面的第一個可見部分所需的所有 CSS(稱為 關鍵CSS首屏 CSS)並將其內聯新增到頁面的 <head> 中,從而減少往返。 由於在慢啟動階段交換包的大小有限,所以關鍵 CSS 的預算大約是 14 KB。

如果超出這個範圍,瀏覽器將需要額外往返取得更多樣式。CriticalCSSCritical 可以做到這一點。 你可能需要為你使用的每個模板執行此操作。 如果可能的話,考慮使用 Filament Group 使用的 條件內聯方法,或者 動態地將內聯程式碼轉換為靜態資產

使用 HTTP/2,關鍵 CSS 可以儲存在一個單獨的 CSS 檔案中,並通過 伺服器推送 來傳遞,而不會增大 HTML 的大小。 問題在於,伺服器推送是很 麻煩,因為瀏覽器中存在許多問題和競爭條件。 它一直不被支援,並有一些快取問題(參見 [Hooman Beheshti介紹的文章](Hooman Beheshti's presentation) 114 頁內容)。事實上,這種影響可能是 負面的,會使網路緩衝區膨脹,從而阻止文件中的真實幀被傳送。 而且,由於 TCP 啟動緩慢,似乎伺服器推送 在熱連線上更加有效

即使使用 HTTP/1,將關鍵 CSS 放在根目錄上的單獨檔案中也是 有好處 的,有時甚至比快取和內聯更為有效。 Chrome 請求這個頁面的時候會再傳送一個 HTTP 連線到根目錄,從而不需要 TCP 連線來獲取這個 CSS。(感謝 Philip!)

需要注意的一些問題是:和 preload 不同的是,preload 可以觸發來自任何域的預載入,而你只能從你自己的域或你所授權的域中推送資源。 一旦伺服器得到來自客戶端的第一個請求,就可以啟動它。 伺服器將資源壓入快取,並在連線終止時被刪除。 但是,由於可以在多個選項卡之間重複使用 HTTP/2 連線,所以推送的資源也可以被來自其他選項卡的請求宣告。(感謝 Inian!)

目前,伺服器並沒有一個簡單的方法得知被推送的資源是否已經存在於 使用者的快取中,因此每個使用者的訪問都會繼續推送資源。因此,您可能需要建立一個快取 監測 HTTP/2 伺服器推送機制。如果被提取,您可以嘗試從快取中獲取它們,這樣可以避免再次推送。

但請記住,新的 cache-digest 規範 無需手動建立這樣的 快取感知 的伺服器,基本上在 HTTP/2 中宣告的一個新的幀型別就可以表達該主機的內容。因此,它對於 CDN 也是特別有用的。

對於動態內容,當伺服器需要一些時間來生成響應時,瀏覽器無法發出任何請求,因為它不知道頁面可能引用的任何子資源。 在這種情況下,我們可以預熱連線並增加 TCP 擁塞視窗大小,以便將來的請求可以更快地完成。 而且,所有內聯配置對於伺服器推送都是較好的選擇。事實上,Inian Parameshwaran 對 HTTP/2 Push 和 HTTP Preload 進行了比較 深入的研究,內容很不錯,其中包含了您可能需要的所有細節。伺服器到底是推送還是不推送呢?你可以閱讀一下 Colin Bendell 的 Should I Push?。可能會給你指出正確的方向。

一句話:正如 Sam Saccone 所說preload 有利於將資源的開始下載時間更接近初始請求, 而伺服器推送是一個完整的 RTT(或 更多,這取決於您的伺服器反應時間) —— 如果你有一個伺服器可以防止不必要的推送。

43. 嘗試重新組合 CSS 規則

我們經常要使用到關鍵的 CSS,但是還有一些優化可以做得更好。哈里·羅伯茨進行了一項引人注目的研究,得出了相當驚人的結果。例如,將主CSS檔案拆分為獨立的媒體查,這樣,瀏覽器將檢索具有高優先順序的關鍵CSS,然後把具有低優先順序的其他所有內容分離到非關鍵CSS當中。

此外,避免 <link rel="stylesheet" />async 程式碼片段之前放置。如果一個指令碼不依賴於任何樣式表,可以考慮將獨立指令碼放在獨立樣式之上。如果是這樣,我們可以將這個指令碼分割成一個單獨的模組,並將其載入到 CSS 的任何一側。

Scott Jehl 通過 使用 service worker 快取內聯CSS檔案 解決了另一個有趣的問題。基本上,我們在 style 元素上新增一個 ID 屬性,以便使用 JavaScript 快速引用,然後一小段 JavaScript 找到目標 CSS 並使用 Cache API 將其儲存在本地瀏覽器快取中(內容型別為 text/css),以便在後續頁面上使用。所以我們為了避免在後續頁面上引用內聯 CSS 並在外部引用快取資產,就會在第一次訪問站點時設定 cookie。

你使用 流響應 嗎?通過流,在初始導航請求中呈現的 HTML 可以充分利用瀏覽器的流式 HTML 解析器。

44. 流響應

streams 經常被遺忘和忽略,它提供了非同步讀取或寫入資料塊的介面,在任何給定的時間內,只有一部分資料可能在記憶體中可用。基本上,只要第一個資料塊可用,它們就允許原始請求的頁面開始處理響應,並使用針對流進行優化的解析器逐步顯示內容。

我們可以從多個源建立一個流。例如,您可以讓 service worker 構建一個 streams,其中框架 (shell)來自快取,內容來自網路的流,而不是提供一個空的 UI 外殼並讓JavaScript填充它。正如 Jeff Posnick 指出的,如果您的 web 應用程式由 CMS 提供支援的,那麼伺服器渲染 HTML 是通過將部分模板拼接在一起來呈現的,該模型將直接轉換為使用流式響應,而模板邏輯將從伺服器複製而不是你的伺服器。Jake Archibald 的 The Year of Web Streams 文章重點介紹瞭如何構建它。對於效能的提升是非常明顯

流化整個 HTML 響應的一個重要優點是,在初始導航請求期間呈現的HTML可以充分利用瀏覽器的流化HTML解析器。載入頁面後插入文件的 HTML 塊(通過 JavaScript 填充的內容很常見)不能利用這種優化。

流式傳輸整個 HTML 響應的一個重要優點是,在初始導航請求期間呈現的 HTML 可以充分利用瀏覽器的流式 HTML 解析器。但是在頁面載入之後插入到文件中的 HTML 塊(與通過 JavaScript 填充的內容一樣常見)無法利用此優化。

瀏覽器支援程度如何呢?使用 Chrome 52 +,Firefox 57+,Safari 和 Edge,支援所有現代瀏覽器 支援的API和 service worker。

45. 考慮使元件連線/裝置記憶體感知

特別是在新興市場工作時,你可能需要考慮優化使用者選擇節省資料的體驗。儲存資料客戶端提示請求頭 允許我們為成本和效能受限的使用者定製應用程式和有效載荷。實際上,您可以 將高DPI影像的請求重寫為低DPI影像,刪除Web字型和花哨的特效,預覽縮圖和無限滾動,關閉視訊自動播放,伺服器推送,減少顯示專案數量,降低影像質量,甚至更改 提供標記的方式。Tim Vereecke 發表了一篇關於 Data-s(h)aver 策略 的詳細文章,其中包含許多 Data-saver 選項。

該頭部目前僅支援 Chromium,Android 版 Chrome 或 桌面裝置上的 Data Saver 擴充套件。當然,您還可以使用 Network Information API 根據網路型別交付 低/高解析度的影像 和視訊。網路資訊API 和 navigator.connection.effectiveType (Chrome 62+),都是使用 RTT 值、下行(downlink)值、有效型別(effectiveType)值(和其他一些值)來表示連線和使用者可以處理的資料。

在這種情況下,Max Stoiber 談到了 連線感知元件。例如,使用 React,我們編寫一個元件,在不同的渲染方式下可能會載入成不同的元素。Max建議,一個 <Media /> 元件在新聞板塊的輸出可能會出現情況:

  • Offline:帶 alt 文字的佔位符
  • 2G / save-data mode:低解析度影像
  • 3G 在非Retina螢幕上:中等解析度影像
  • 3G 在Retina螢幕上:高解析度Retina影像
  • 4G 高清視訊

Dean Hume使用service worker提供了一個 類似邏輯的實際實現。對於視訊,我們可以預設顯示一個視訊海報,然後在更好的連線上顯示 播放 圖示以及視訊播放器外殼、視訊的後設資料等。對於不支援的瀏覽器,我們可 以偵聽canplaythrough事件,如果 canplaythrough 事件在 2s 內沒有觸發,則使用 Promise.race() 來超時載入資源。

46. 考慮使您的元件裝置具有記憶體感知能力

網路連線只是為使用者提供了一個視口。如果想更進一步,您還可以使用 Device Memory API(Chrome 63+)根據可用的裝置記憶體動態調整資源。返回裝置有多少RAM(千兆位元組),向下舍入到最接近的2的冪。navigator.deviceMemory 返回裝置的RAM大小(千兆位元組),四捨五入到最接近的2次方。該API還具有一個客戶端提示頭,Device-Memory,並且給出相同的值。

(譯)2019年前端效能優化清單 — 中篇
DevTools 中的 Priority 列。圖片來源:Ben Schwarz,重要的請求

47. 預熱連線以加快傳輸速度

使用 資源提示 來節約時間,如 dns-prefetch(在後臺執行 DNS 查詢),preconnect(告訴瀏覽器在後臺進行連線握手(DNS, TCP, TLS)),prefetch(告訴瀏覽器請求一個資源) 和 preload(預先獲取資源而不執行他們)。

大多數時候,我們至少會使用 preconnectdns-prefetch,我們會小心使用 prefetchpreload;前者只能在你非常確定使用者後續需要什麼資源的情況下使用(類似於採購渠道)。注意,prerender 已被棄用,不再被支援。

請注意,即使使用 preconnectdns-prefetch,瀏覽器也會對它將並行查詢或連線的主機數量進行限制,因此最好是將它們根據優先順序進行排序(感謝 Philip!)。

事實上,使用資源提示可能是最簡單的提高效能的方法,它確實很有效。什麼時候該使用呢?Addy Osmani已經做了 解釋,我們應該預載入確定將在當前頁面中使用的資源。預獲取可能用於未來頁面的資源,例如使用者尚未訪問的頁面所需的 Webpack 包。

Addy 的 關於 Chrome 中載入優先順序 的文章展示了Chrome 是如何精確地解析資源提示的,因此一旦你決定哪些資源對頁面渲染比較重要,你就可以給它們賦予比較高的優先順序。你可以在 Chrome DevTools 網路請求表格(或者 Safari Technology Preview)中啟動 priority 列來檢視你的請求的優先順序。

例如,由於字型通常是頁面上的重要資源,所以最好使用 preload 請求瀏覽器下載字型。你也可以 動態載入 JavaScript,從而有效的執行延遲載入。同樣的,因為 <link rel="preload"> 接收一個 media 屬性,你可以基於媒體查詢規則來 有選擇性地優先載入資源

需要注意的一些問題是:preload 可以將 資源的下載時間 移到請求開始時,但是這些快取在記憶體中的預先載入的資源是繫結在所傳送請求的頁面上,也就是說預先載入的請求不能被頁面所共享。而且,preload 與 HTTP 快取配合得也很好:如果快取命中則不會傳送網路請求。

因此,它對後發現的資源也非常有用,如:通過 background-image 載入的一幅 hero image,內聯關鍵 CSS (或 JavaScript),並預先載入其他 CSS (或 JavaScript)。

此外,只有當瀏覽器從伺服器接收 HTML,並且前面的解析器找到了 preload 標籤後,preload 標籤才可以啟動預載入。

由於我們不等待瀏覽器解析 HTML 以啟動請求,所以通過 HTTP 頭進行預載入要快一些。Early Hints 將進一步提供幫助,甚至可以在傳送HTML的響應標頭之前啟用預載入,而 Priority Hints即將推出)將幫助我們指示指令碼的載入優先順序。

請注意:如果你正在使用 preloadas 必須定義,否則什麼都不會載入;還有,預載入字型時如果沒有 crossorigin 屬性將會獲取兩次。

48. 使用 Service workers 進行快取和網路回退

網路上的任何效能優化都不會比使用者計算機上的本地儲存快取更快。如果您的網站是通過 HTTPS 執行的,請使用 Service Workers 實用指南 將靜態資源快取在 service worker 快取中,並儲存離線回退(甚至離線頁面),然後從使用者的計算機中檢索它們,而不是轉到網路。另外,檢視 Jake 的 Offline Cookbook 和免費的 Udacity課程 離線Web應用程式

瀏覽器支援嗎?如上所述,它得到了廣泛的支援(Chrome、Firefox、Safari TP、三星網際網路、Edge 17+),無論如何,網路是它的退路。它有助於提高效能嗎?是的,而且它正在變得更好,例如使用Background Fetch允許service worker進行後臺上傳/下載,運送到Chrome 71

service worker有許多用例。例如,您可以 實現 儲存為離線 特性處理損壞的影像在選項卡之間引入訊息傳遞,或者 根據請求型別提供不同的快取策略。通常,一種常見的可靠策略是將應用程式 shell 與一些關鍵頁面一起儲存在 service worker 的快取中,例如離線頁面、首頁和其他可能對您的應用程式很重要的頁面。

但是要記住一些問題。在有了service worker之後,我們需要 在Safari中提防範圍請求(如果您正在為服務工作者使用 Workbox,它有一個範圍請求模組)。如果您偶然發現控臺出現 DOMException:Quota exceeded. 這樣的錯誤提示,那麼請檢視 Gerardo 的文章 當7KB等於7MB時

正如 Gerardo 所寫的一樣,如果您正在構建一個漸進式Web應用程式,並且當您的 service worker 快取來自 CDN 的靜態資源時,這時候您的快取儲存空間會非常大。所以請確保對於跨源資源 存在正確的 CORS 響應頭,這樣您就不會在無意中使用 Service Workers 快取不透明的響應,您還可以向 < img > 標記新增 crossorigin 屬性來 選擇將跨源影像資源快取到 CORS 模式中

使用 service worker 的一個很好的起點是 Workbox,這是一組專門用於構建漸進式 Web 應用程式的 service worker 庫。

49. 您是否使用 CDN / Edge 上的 Service workers(例如,進行A / B測試)

這時候,我們已經習慣於在客戶端上執行 Service workers,但是隨著 CDNs 在伺服器上實現它們,我們還可以使用它來調整邊緣效能。

例如,在A / B測試中,當 HTML 需要為不同使用者改變其內容時,我們可以 使用 CDN 伺服器上的 Service Workers 來處理邏輯。我們還可以對 HTML 重寫 進行 流式處理,以加快使用 Google 字型的網站的速度。

50. 您是否優化了渲染效能

使用 CSS containment 隔離昂貴的元件 - 例如,限制瀏覽器樣式、用於非畫布導航的佈局和繪畫工作,第三方元件的範圍。確保在滾動頁面沒有延遲,或者當一個元素進行動畫時,持續地達到每秒 60 幀。如果這是不可能的,那麼至少要使每秒幀數持續保持在 60 到 15 的範圍。使用 CSS 的 will-change 通知瀏覽器哪個元素的哪個屬性將要發生變化。

此外,評估 執行時渲染效能(例如,使用 DevTools)。可以通過學習 Paul Lewis 的 關於瀏覽器渲染優化的 Udacity 課程(免費)和 Georgy Marchuk 關於瀏覽器繪製和web效能注意事項 的文章。

如果你想更深入地瞭解這個主題,Nolan Lawson 分享了一篇文章叫做 準確測量佈局效能的技巧,而 Jason Miller 也給出了一個建議 替代的技術。同樣,我們還有一篇由 Sergey Chikuyonok 寫的關於 如何正確使用 GPU 動畫 的文章。注意:對 GPU-composited 層的更改是代價最小 的,如果你能通過 opacitytransform 來觸發合成,那麼你就是在正確的道路上。Anna Migas 在她關於 除錯UI渲染效能 的演講中也提供了很多實用的建議。

51. 您是否優化了渲染體驗

元件以何種順序顯示在頁面上以及我們如何給瀏覽器提供資源固然重要,但是我們也不應該低估了 感知效能 的作用。這一概念涉及到等待的心理學,主要是讓使用者在其他事情發生時保持忙碌。這就涉及到了 感知管理優先開始提前完成寬容管理

這一切意味著什麼?在載入資源時,我們可以嘗試始終領先於客戶一步,所以將很多處理放置到後臺,響應會很迅速。讓客戶參與進來,我們可以用 骨架螢幕(例項演示),而不是當沒有更多優化可做時,用載入指示或者新增一些動畫/過渡來 欺騙使用者體驗。但是要注意:在部署之前應該對骨架螢幕進行測試,因為一些 測試顯示,從所有指標來看,骨架螢幕的 效能是最差的

相關文章