[譯] 2019 前端效能優化年度總結 — 第四部分

清秋發表於2019-02-03

讓 2019 來得更迅速吧!你現在閱讀的是 2019 年前端效能優化年度總結,始於 2016。

目錄

構建優化

22. 確定優先順序

要了解你首先要處理什麼。列出你全部的靜態資源清單(JavaScript、圖片、字型、第三方指令碼以及頁面上的大模組:如輪播圖、複雜的資訊圖表和多媒體內容),並將它們分組。

新建一個電子表格。定義舊版瀏覽器的基本核心體驗(即完全可訪問的核心內容)、現代瀏覽器的增強體驗(即更加豐富的完整體驗)以及額外功能(可以延遲載入的非必需的資源:例如網頁字型、不必要的樣式、輪播指令碼、視訊播放器、社交媒體按鈕和大圖片)。不久前,我們發表了一篇關於“提升 Smashing 雜誌網站效能”的文章,文中詳細描述了這種方法。

在優化效能時,我們需要確定我們的優先事項。立即載入核心體驗,然後載入增強體驗,最後載入額外功能

23. 重溫優秀的“符合最低要求”技術

如今,我們仍然可以使用符合最低要求(cutting-the-mustard)技術 將核心體驗傳送到舊版瀏覽器,併為現代瀏覽器提供增強體驗。(譯者注:關於 cutting-the-mustard 出處可以參考這篇文章。)該技術的一個更新版本將使用 ES2015 + 語法 <script type="module">。現代瀏覽器會將指令碼解釋為 JavaScript 模組並按預期執行它,而舊版瀏覽器無法識別該屬性並忽略它,因為它是未知的 HTML 語法。

現在我們需要謹記的是,單獨的功能檢測不足以做出該傳送哪些資源到該瀏覽器的明智決定。就其本身而言,符合最低要求 從瀏覽器版本中推斷出裝置的能力,今天已經不再有效了。

例如,發展中國家的廉價 Android 手機主要使用 Chrome 瀏覽器,儘管裝置的記憶體和 CPU 功能有限,但其仍然達到了使用符合最低要求技術的標準。最終,使用裝置記憶體客戶端提示報頭,我們將能夠更可靠地定位低端裝置。在本文寫作時,僅在 Blink 中支援該報頭(通常用於客戶端提示)。由於裝置記憶體還有一個已在 Chrome 中提供的 JavaScript API,因此基於該 API 進行功能檢測是一個選擇,並且只有在不支援時才會再來使用符合最低要求技術(感謝 Yoav!)。

24. 解析 JavaScript 是耗時的,所以讓它體積小

在處理單頁面應用程式時,我們需要一些時間來初始化應用程式,然後才能渲染頁面。你的設定需要你的自定義解決方案,但可以留意能夠加快首次渲染的模組和技術。例如,如何除錯 React 效能消除常見的 React 效能問題,以及如何提高 Angular 的效能。通常,大多數效能問題都來自啟動應用程式的初始解析時間。

JavaScript 有一個解析的成本,但很少僅是由於檔案大小一個因素影響效能。解析和執行時間根據裝置的硬體的不同有很大差異。在普通電話(Moto G4)上,1MB(未壓縮)JavaScript 的解析時間約為 1.3-1.4s,移動裝置上有 15-20% 的時間用於解析。在遊戲中編譯,僅僅在準備 JavaScript 就平均耗時 4 秒,在移動裝置上首次有效繪製(First Meaningful Paint )之前大約需要 11 秒。原因:在低端移動裝置上,解析和執行時間很容易高出 2-5 倍

為了保證高效能,作為開發人員,我們需要找到編寫和部署更少量 JavaScript 的方法。這就是為什麼要詳細檢查每一個 JavaScript 依賴關係的原因。

有許多工具可以幫助你做出有關依賴關係和可行替代方案影響的明智決策:

有一種有趣方法可以用來避免解析成本,它使用了 Ember 在 2017 年推出的二進位制模板。使用該模板,Ember 用 JSON 解析代替 JavaScript 解析,這可能更快。(感謝 Leonardo,Yoav!

衡量 JavaScript 解析和編譯時間。我們可以使用綜合測試工具和瀏覽器跟蹤來跟蹤解析時間,瀏覽器實現者正在談論將來把基於 RUM 的處理時間暴露出來。也可以考慮使用 Etsy 的 DeviceTiming,這是一個小工具,它允許你使用 JavaScript 在任何裝置或瀏覽器上測量解析和執行時間。

底線:雖然指令碼的大小很重要,但它並不是一切。隨著指令碼大小的增長,解析和編譯時間不一定會線性增加

25. 使用了搖樹、作用域提升和程式碼分割嗎

搖樹(tree-shaking)是一種在 webpack 中清理構建過程的方法,它僅將實際生產環境使用的程式碼打包,並排除沒有使用的匯入模組。使用 webpack 和 rollup,還可以使用作用域提升(scope hoisting),作用域提升使得 webpack 和 rollup 可以檢測 import 鏈可以展開的位置,並將其轉換為一個行內函數,並且不會影響程式碼。使用 webpack,我們也可以使用 JSON Tree Shaking

此外,你可能需要考慮學習如何編寫高效的 CSS 選擇器,以及如何避免臃腫且耗時的樣式。如果你希望更進一步,你還可以使用 webpack 來縮短 class 名,並使用作用域隔離在編譯時動態重新命名 CSS class 名

程式碼拆分(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 的程式碼覆蓋率工具來實現它。

如果你沒有使用 webpack,請注意 rollup 顯示的結果明顯優於 Browserify 匯出。雖然我們參與其中,但你可能需要檢視 rollup-plugin-closure-compilerrollupify,它將 ECMAScript 2015 模組轉換為一個大型 CommonJS 模組 —— 因為根據你的包和模組系統的選擇,小模組可能會有驚人高的成本

26. 可以將 JavaScript 切換到 Web Worker 中嗎?

為了減少對首次可互動時間(Time-to-Interactive)的負面影響,考慮將高耗時的 JavaScript 放到 Web Worker 或通過 Service Worker 來快取。

隨著程式碼庫的不斷增長,UI 效能瓶頸將會出現,進而會降低使用者的體驗。主要原因是 DOM 操作與主執行緒上的 JavaScript 一起執行。通過 web worker,我們可以將這些高耗時的操作移動到後臺程式的另一執行緒上。Web worker 的典型用例是預獲取資料和漸進式 Web 應用程式,提前載入和儲存一些資料,以便你在之後需要時使用它。而且你可以使用 Comlink 簡化主頁面和 worker 之間的通訊。仍然還有一些工作要做,但我們已經做了很多了。

Workerize 讓你能夠將模組移動到 Web Worker 中,自動將匯出的函式對映為非同步代理。如果你正在使用 webpack,你可以使用 workerize-loader。或者,也可以試試 worker-plugin

請注意,Web Worker 無權訪問 DOM,因為 DOM 不是“執行緒安全的”,而且它們執行的程式碼需要包含在單獨的檔案中。

27. 可以將 JavaScript 切換到 WebAssembly 中嗎?

我們還可以將 JavaScript 轉換為 WebAssembly,這是一種二進位制指令格式,可以使用 C/C++/Rust 等高階語言進行編譯。它的瀏覽器支援非常出色,最近它變得可行了,因為 JavaSript 和 WASM 之間的函式呼叫速度變得越來越快,至少在 Firefox 中是這樣。

在實際場景中,JavaScript 似乎在較小的陣列大小上比 WebAssembly 表現更好,而 WebAssembly 在更大的陣列大小上比 JavaScript 表現更好。對於大多數 Web 應用程式,JavaScript 更適合,而 WebAssembly 最適合用於計算密集型 Web 應用程式,例如 Web 遊戲。但是,如果切換到 WebAssembly 能否獲得顯著的效能改進,則可能值得研究。

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

WebAssembly 如何工作,以及它為什麼有用的概述。

Milica Mihajlija 提供了 WebAssembly 的工作原理及其有用的原因的概述。 (預覽大圖

28. 是否使用了 AOT 編譯?

使用 AOT(ahead-of-time)編譯器將一些客戶端渲染放到伺服器,從而快速輸出可用結果。最後,考慮使用 Optimize.js 來加速初始化載入時間,它包裝了需要立即呼叫的函式(儘管現在這可能不是必需的了)。

“預設快速:現代載入最佳實踐”,作者 Addy Osmani

來自預設快速:現代載入最佳實踐,作者是獨一無二的 Addy Osmani。幻燈片第 76 頁。

29. 僅將遺留程式碼提供給舊版瀏覽器

由於 ES2015 在現代瀏覽器中得到了非常好的支援,我們可以使用 babel-preset-env ,僅轉義尚未被我們的目標瀏覽器支援的那些 ES2015 + 特性。然後設定兩個構建,一個在 ES6 中,一個在 ES5 中。如上所述,現在所有主流瀏覽器都支援 JavaScript 模組,因此使用 script type =“module” 讓支援 ES 模組的瀏覽器載入支援 ES6 的檔案,而舊瀏覽器可以使用 script nomodule 載入支援 ES5 的檔案。我們可以使用 Webpack ESNext Boilerplate 自動完成整個過程。

請注意,現在我們可以編寫基於模組的 JavaScript,它可以原生地在瀏覽器裡執行,無需編譯器或打包工具。<link rel="modulepreload"> header 提供了一種提前(和高優先順序)載入模組指令碼的方法。基本上,它能夠很好地最大化使用頻寬,通過告訴瀏覽器它需要獲取什麼,以便在這些長的往返期間不會卡頓。此外,Jake Archibald 釋出了一篇詳細的文章,其中包含了需要牢記的 ES 模組相關內容,值得一讀。

對於 lodash,使用 babel-plugin-lodash,通過它可以只載入你在原始碼中使用的模組。你的其他依賴也可能依賴於其他版本的 lodash,因此將通用 lodash requires 轉換為特定需要的功能,以避免程式碼重複。這可能會為你節省相當多的 JavaScript 負載。

Shubham Kanodia 撰寫了一份詳細的關於智慧打包的低維護指南:如何在生產環境中實現僅僅將遺留程式碼推送到老版本瀏覽器上,裡面還有一些你可以直接拿來用的程式碼片段。

正如 Jake Archibald 的文章中所解釋的那樣,內聯指令碼會被推遲,直到正在阻塞的外部指令碼和內聯指令碼得到執行。

Jake Archibald 釋出了一篇詳細的文章,其中包含了 需要牢記的 ES 模組相關內容,例如:內聯指令碼會被推遲,直到正在阻塞的外部指令碼和內聯指令碼得到執行。(預覽大圖

30. 是否使用了 JavaScript 差異化服務?

我們希望通過網路傳送必要的 JavaScript,但這意味著需要更加集中精力並且細粒度地關注這些靜態資源的傳送。前一陣子 Philip Walton 介紹了差異化服務的想法。該想法是編譯和提供兩個獨立的 JavaScript 包:“常規”構建,帶有 Babel-transforms 和 polyfill 的構建,只提供給實際需要它們的舊瀏覽器,以及另一個沒有轉換和 polyfill 的包(具有相同功能)。

結果,通過減少瀏覽器需要處理的指令碼數量來幫助減少主執行緒的阻塞。Jeremy Wagner 在 2019 年釋出了一篇關於差異服務以及如何在你的構建管道中進行設定的綜合文章,從設定 babel 到你需要在 webpack 中進行哪些調整,以及完成所有這些工作的好處。

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

老專案充斥著陳舊和過時的程式碼。重新檢視你的依賴項,評估重構或重寫最近導致問題的遺留程式碼所需的時間。當然,它始終是一項重大任務,但是一旦你瞭解了遺留程式碼的影響,就可以從增量解耦開始。

首先,設定指標,跟蹤遺留程式碼呼叫的比率是保持不變或是下降,而不是上升。公開阻止團隊使用該庫,並確保你的 CI 能夠警告開發人員,如果它在拉取請求(pull request)中使用。Polyfill 可以幫助將遺留程式碼轉換為使用標準瀏覽器功能的重寫程式碼庫。

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

Chrome 中的 CSS 和 JavaScript 程式碼覆蓋率可以讓你瞭解哪些程式碼已執行/已應用,哪些程式碼尚未執行。你可以開始記錄覆蓋範圍,在頁面上執行操作,然後瀏覽程式碼覆蓋率結果。一旦你檢測到未使用的程式碼,找到那些模組並使用 import() 延遲載入(參見整個執行緒)。然後重複覆蓋配置檔案並驗證它現在在初始載入時傳送的程式碼是否變少了。

你可以使用 Puppeteer程式設計方式收集程式碼覆蓋率,Canary 也能夠讓你匯出程式碼覆蓋率結果。正如 Andy Davies 提到的那樣,你可能希望同時收集現代和舊版瀏覽器的程式碼覆蓋率。Puppeteer 還有許多其他用例,例如,自動視差監視每個構建的未使用的 CSS

此外,purgecssUnCSSHelium 可以幫助你從 CSS 中刪除未使用的樣式。如果你不確定是否在某處使用了可疑的程式碼,可以遵循 Harry Roberts 的建議:為該 class 建立 1×1px 透明 GIF 並將其放入 dead/ 目錄,例如:/assets/img/dead/comments.gif。然後,將該特定影像設定為 CSS 中相應選擇器的背景,然後靜候幾個月,檢視該檔案能否出現在你的日誌中。如果日誌裡沒出現該條目,則沒有人使用該遺留元件:你可以繼續將其全部刪除。

對於愛冒險的人,你甚至可以通過使用 DevTools 監控 DevTools,通過一組頁面自動收集未使用的 CSS。

33. 減小 JavaScript 包的大小

正如 Addy Osmani 指出的那樣,當你只需要一小部分時,你很可能會傳送完整的 JavaScript 庫,以及提供給不需要它們的瀏覽器的過時 polyfill,或者只是重複程式碼。為避免額外開銷,請考慮使用 webpack-libs-optimization,在構建過程中刪除未使用的方法和 polyfill。

將打包審計新增到常規工作流程中。有一些你在幾年前新增的重型庫的輕量級替代品,例如:Moment.js 可以用 date-fnsLuxon 代替。Benedikt Rötsch 的研究表明,從 Moment.js 到 date-fns 的轉換可能會使 3G 和低端手機上的首次繪製時間減少大約 300ms。

這就是 Bundlephobia 這樣的工具可以幫助你找到在程式包中新增 npm 包的成本。你甚至可以將這些成本與 Lighthouse Custom Audit 相結合。這也適用於框架。通過刪除或減小 Vue MDC 介面卡(Vue 的 Material 元件),樣式可以從 194KB 降至 10KB。

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

除了傳送整個框架包之外,你甚至可以修剪框架並將其編譯為不需要額外程式碼的原始 JavaScript 包。Svelte 做到了Rawact Babel 外掛也是如此,它在構建時將 React.js 元件轉換為原生 DOM 操作。 為什麼?好吧,正如維護者解釋的那樣:“React-dom 包含可以渲染的每個可能元件/ HTMLElement 的程式碼,包括用於增量渲染、排程、事件處理等的程式碼。但是有些應用程式不需要所有這些功能(在初始頁面載入時)。對於此類應用程式,使用原生 DOM 操作構建互動式使用者介面可能是有意義的。”

Webpack 比較

Benedikt Rötsch 的文章中,他表示,從 Moment.js 到 date-fns 的轉換會使 3G 和低端手機上的首次繪製時間減少大約 300ms。(預覽大圖

34. 是否使用了 JavaScript 程式碼塊的預測預獲取?

我們可以使用啟發式方法來決定何時預載入 JavaScript 程式碼塊。Guess.js 是一組工具和庫,它使用 Google Analytics 的資料來確定使用者最有可能從給定頁面訪問哪個頁面。根據從 Google Analytics 或其他來源收集的使用者導航模式,Guess.js 構建了一個機器學習模型,用於預測和預獲取每個後續頁面中所需的 JavaScript。

因此,每個互動元素都接收參與的概率評分,並且基於該評分,客戶端指令碼決定提前預獲取資源。你可以將該技術整合到 Next.js 應用程式、Angular 和 React 中,還有一個 webpack 外掛能夠自動完成設定過程。

顯然,你可能會讓瀏覽器預測到使用不需要的資料從而預獲取到不需要的頁面,因此最好在預獲取請求的數量上保持絕對保守。一個好的用例是預獲取結賬中所需的驗證指令碼,或者當一個關鍵的 CTA(call-to-action)進入視口時的推測性預獲取。

需要不太複雜的東西?Quicklink 是一個小型庫,可在空閒時自動預獲取視口中的連結,以便加快下一頁導航的載入速度。但是,它也考慮了資料流量,因此它不會在 2G 網路或者 Data-Saver 開啟時預獲取資料。

35. 從針對你的目標 JavaScript 引擎進行優化中獲得好處

研究哪些 JavaScript 引擎在你的使用者群中占主導地位,然後探索針對這些引擎的優化方法。例如,在為 Blink 核心瀏覽器、Node.js 執行時和 Electron 中使用的 V8 進行優化時,使用指令碼流來處理龐大的指令碼。它允許在下載開始時在單獨的後臺執行緒上解析 asyncdefer scripts,因此在某些情況下可以將頁面載入時間減少多達 10%。實際上,在 <head>使用 <script defer>,以便瀏覽器可以提前發現資源,然後在後臺執行緒上解析它。

警告Opera Mini 不支援指令碼延遲,所以如果你正在為印度或非洲開發defer 將被忽略,這會導致阻止渲染,直到指令碼執行完為止(感謝 Jeremy!)

漸進式啟動

漸進式啟動意味著使用伺服器端渲染來獲得快速的首次有效繪製,但也包括一些最小的 JavaScript,以保持首次互動時間接近首次有效繪製時間。

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

在這兩種情況下,我們的目標應該是設定漸進式啟動:使用伺服器端渲染來獲得快速的首次有效繪製,但也包括一些最小的必要 JavaScript,以保持首次互動時間接近首次有效繪製時間。如果 JavaScript 在首次有效繪製之後來得太晚,瀏覽器可能會在解析、編譯和執行後期發現的 JavaScript 時鎖定主執行緒,從而給站點或應用程式的互動帶來枷鎖。

為避免這種情況,請始終將函式執行分解為獨立的非同步任務,並儘可能使用 requestIdleCallback。考慮使用 webpack 的動態 import() 支援,延遲載入 UI 的部分,降低載入、解析和編譯成本,直到使用者真正需要它們(感謝 Addy!)。

從本質上講,首次可互動時間(TTI)告訴我們導航和互動之間的時間。通過檢視初始內容渲染後的前五秒視窗來定義度量標準,其中任何 JavaScript 任務都不會超過 50 毫秒。如果發生超過 50 毫秒的任務,則重新開始搜尋五秒鐘視窗。因此,瀏覽器將首先假設它已到達互動狀態,然後切換到凍結狀態,最終切換回互動狀態。

一旦我們到達互動狀態,在按需或在時間允許的情況下,就可以啟動應用程式的非必要部分。不幸的是,正如 Paul Lewis 所注意到的那樣,框架通常沒有提供給開發者優先順序的概念,因此大多數庫和框架都難以實現漸進式啟動。如果你有時間和資源,請使用此策略最終提升效能。

那麼,客戶端還是伺服器端?如果使用者沒有明顯的好處,客戶端渲染可能不是真正必要的 —— 實際上,伺服器端渲染的 HTML 可能更快。也許你甚至可以使用靜態站點生成器預渲染一些內容,並將它們直接推送到 CDN,並在頂部新增一些 JavaScript。

將客戶端框架的使用限制為絕對需要它們的頁面。如果做得不好,伺服器渲染和客戶端渲染是一場災難。考慮在構建時預渲染動態 CSS 內聯,以生成生產就緒的靜態檔案。Addy Osmani 就可能值得關注的 JavaScript 成本發表了精彩的演講

37. 約束第三方指令碼的影響

通過所有效能優化,我們通常無法控制來自業務需求的第三方指令碼。第三方指令碼指標不受終端使用者體驗的影響,因此通常一個指令碼最終會呼叫令人討厭的冗長的第三方指令碼,從而破壞了專門的效能工作。為了控制和減輕這些指令碼帶來的效能損失,僅僅非同步載入它們(可能是通過延遲)並通過資源提示(如 dns-prefetchpreconnect)加速它們是不夠的。

正如 Yoav Weiss 在他關於第三方指令碼的必讀觀點中所解釋的那樣,在許多情況下,這些指令碼會下載動態的資源。資源在頁面載入之間發生變化,因此我們沒有必要知道從哪些主機下載資源以及這些資源是什麼。

你有哪些選擇方案?考慮使用 service worker,通過超時競爭資源下載,如果資源在特定超時內沒有響應,則返回空響應以告知瀏覽器繼續解析頁面。你還可以記錄或阻止未成功或不符合特定條件的第三方請求。如果可以,請從你自己的伺服器而不是從供應商的伺服器載入第三方指令碼。

Casper.com 釋出了一個詳細的案例研究,說明他們如何通過自我託管的 Optimizely 將網站響應時間減少了 1.7 秒。它可能是值得的。

Casper.com 釋出了一個詳細的案例研究,說明他們如何通過自託管的 Optimizely 網站響應時間減少了 1.7 秒。這可能是值得的。(圖片來源)(預覽大圖

另一種選擇是建立內容安全策略(CSP)以限制第三方指令碼的影響,例如:不允許下載音訊或視訊。最好的選擇是通過 <iframe> 嵌入指令碼,以便指令碼在 iframe 的上下文中執行,因此第三方指令碼無法訪問頁面的 DOM,也無法在你的域上執行任意程式碼。使用 sandbox 屬性可以進一步約束 iframe,那樣你就可以禁用一切 iframe 可能執行的任何功能,例如:防止指令碼執行、阻止警報、表單提交、外掛、訪問頂部導航等。

比如,可能必須使用 <iframe sandbox="allow-scripts"> 來執行指令碼。每個限制都可以通過 sandbox 屬性上的各種 allow 值來解除(幾乎所有的瀏覽器都受支援),因此將它們限制在應該允許的最低限度。

考慮使用 Intersection Observer;這將使廣告仍然在 iframe 中,但是可以排程事件或從 DOM 獲取所需資訊(例如,廣告可見性)。可以關注一些新的策略,例如功能策略,資源大小限制和 CPU/頻寬優先順序,以限制可能會降低瀏覽器速度的有害 Web 功能和指令碼,例如:同步指令碼、同步 XHR 請求、document.write 和過時的實現。

要對第三方進行壓力測試,請檢查 DevTools 中效能配置檔案頁面中的自下而上的摘要,測試如果請求被阻止或超時的情況會發生什麼 —— 對於後者,你可以使用 WebPageTest 的 Blackhole 伺服器 blackhole.webpagetest.org,它可以將特定域指向你的 hosts 檔案。最好是自託管並使用單一主機名,但也可以生成一個請求對映,該對映公開第四方呼叫並檢測指令碼何時更改。你可以使用 Harry Roberts 的方法稽核第三方,並生成類似這樣的電子表格。Harry 還在他關於第三方效能和審計的討論中解釋了審計工作流程。

請求阻止

圖片來源: Harry Roberts

38. 設定 HTTP 快取標頭

仔細檢查是否已正確設定 expiresmax-agecache-control 和其他 HTTP 快取頭。通常,資源無論在短時間內(如果它們可能會更改)還是無限期(如果它們是靜態的)情況下都是可快取的 —— 你只需在需要時在 URL 中更改它們的版本。禁用 Last-Modified 標頭,因為任何帶有它的靜態資源都將導致帶有 If-Modified-Since 標頭的條件請求,即使資源位於快取中也是如此。Etag 也是如此。

使用使用專為指紋靜態資源設計的 Cache-control:immutable,以避免重新驗證(截至 2018 年 12 月,Firefox、Edge 和 Safari 都已經支援該功能; Firefox 僅支援 https:// 事務)。事實上,“在 HTTP 存檔中的所有頁面中,2% 的請求和 30% 的網站似乎包含至少 1 個不可變響應。此外,大多數使用它的網站都設定了具有較長新鮮生命週期的靜態資源。”

還記得 stale-while-revalidate 嗎?你可能知道,我們使用 Cache-Control 響應頭指定快取時間,例如:Cache-Control: max-age=604800。經過 604800 秒後,快取將重新獲取所請求的內容,從而導致頁面載入速度變慢。通過使用 stale-while-revalidate 可以避免這種速度變慢的問題。它本質上定義了一個額外的時間視窗,在此期間快取可以使用舊的靜態資源,只要它在非同步地在後臺重新驗證自己。因此,它“隱藏了”來自客戶端的延遲(在網路和伺服器上)。

在 2018 年 10 月,Chrome 釋出了一個意圖 在 HTTP Cache-Control 標頭中對 stale-while-revalidate 的處理,因此,它應該會改善後續頁面載入延遲,因為舊的靜態檔案不再位於關鍵路徑中。結果:重複訪問頁面的 RTT 為零

你可以使用 Heroku 的 HTTP 快取標頭入門,Jake Archibald 的“快取最佳實踐”和Ilya Grigorik 的 HTTP 快取入門作為指南。另外,要注意標頭的變化,特別是與 CDN 相關的標頭,並注意 Key 標頭,這有助於避免當新請求與先前請求略有差異(但不顯著)時,需要進行額外的往返驗證(感謝 Guy!)。

另外,請仔細檢查你是否傳送了不必要的標頭(例如 x-powered-bypragmax-ua-compatibleexpires 等),並且包含有用的安全性和效能標頭(例如 Content-Security-Policy, X-XSS-Protection, X-Content-Type-Options 等)。最後,請記住單頁應用程式中 CORS 請求的效能成本

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章