- 原文地址:Preload, Prefetch And Priorities in Chrome
- 原文作者:Addy Osmani
- 譯文出自:掘金翻譯計劃
- 譯者:gy134340
- 校對者:IridescentMia,vuuihc
今天我們來深入研究一下 Chrome 的網路協議棧,來更清晰的描述早期網路載入(像 <link rel=“preload"
和 <link rel=“prefetch”>
)背後的工作原理,讓你對其更加了解。
像其他文章描述的那樣,preload 是宣告式的 fetch,可以強制瀏覽器請求資源,同時不阻塞文件 onload 事件。
Prefetch 提示瀏覽器這個資源將來可能需要,但是把決定是否和什麼時間載入這個資源的決定權交給瀏覽器。
Preload 將 load 事件與指令碼解析過程解耦,如果你還沒有用過它,看看 Yoav Weiss 的文章 Preload: What is it Good For?。
Preload 在生產環境的成功案例
在我們深入細節之前,下面是上一年發現的使用 proload 並對載入產生積極影響的案例總結:
Housing.com 在對他們的漸進式 Web 應用程式的指令碼轉用 proload 看到大約縮短了10%的可互動時間。
Shopify 在轉用 preload 載入字型後在 Chrome 桌面版獲得了 50%(1.2s) 的文字渲染優化,這完全解決了他們的文字閃動問題。
左邊:使用 preload,右邊:不使用 preload(視訊)
使用<link rel=”preload”>
載入字型。
Treebo,印度最大的旅館網站之一,在 3G 網路下對其桌面版試驗,在對其頂部圖片和主要的 Webpack 打包檔案使用 preload 之後,在首屏繪製和可互動延遲分別減少了 1s。
同樣的,在對自己的漸進式 Web 應用程式主要打包檔案使用 preload 之後,Flipkart 在路由解析之前 節省了大量的主執行緒空閒時間(在 3G 網路下的低效能手機下)。
上面:未使用 preload,下面:使用 preload
Chrome 資料保護團隊在對指令碼和 CSS 樣式表使用 preload 之後,發現頁面首次繪製時間獲得平均 12% 的速度提升。
對於 prefetch ,它被廣泛使用,在 Google 我們仍用它來獲取可以加快 搜尋結果頁面 的渲染的關鍵資源。
Preload 在很多大型網站都有實際應用,這點你在接下來的文章裡也可以看到,讓我們來仔細探討下網路協議棧實際上是如何對待 preload 和 prefetch 的。
什麼時候該用 <link rel=”preload”>
? 什麼時候又該用 <link rel=”prefetch”>
?
建議:對於當前頁面很有必要的資源使用 preload,對於可能在將來的頁面中使用的資源使用 prefetch。
preload 是對瀏覽器指示預先請求當前頁需要的資源(關鍵的指令碼,字型,主要圖片)。
prefetch 應用場景稍微又些不同 —— 使用者將來可能在其他部分(比如檢視或頁面)使用到的資源。如果 A 頁面發起一個 B 頁面的 prefetch 請求,這個資源獲取過程和導航請求可能是同步進行的,而如果我們用 preload 的話,頁面 A 離開時它會立即停止。
使用 preload 和 prefetch,我們有了對當前頁面和將來頁面載入關鍵資源的解決辦法。
<link rel="preload">
和 <link rel="prefetch">
的快取行為
Chrome 有四種快取: HTTP 快取,記憶體快取,Service Worker 快取和 Push 快取。preload 和 prefetch 都被儲存在 HTTP 快取中。
當一個資源被 preload 或者 prefetch 獲取後,它可以從 HTTP 快取移動至渲染器的記憶體快取中。如果資源可以被快取(比如說存在有效的cache-control 和 max-age),它被儲存在 HTTP 快取中可以被現在或將來的任務使用,如果資源不能被快取在 HTTP 快取中,作為代替,它被放在記憶體快取中直到被使用。
Chrome 對於 preload 和 prefetch 的網路優先順序?
下面是在 Blink 核心的 Chrome 46 及更高版本中不同資源的載入優先順序情況( Pat Meenan)
preload 用 “as” 或者用 “type” 屬性來表示他們請求資源的優先順序(比如說 preload 使用 as="style" 屬性將獲得最高的優先順序)。沒有 “as” 屬性的將被看作非同步請求,“Early”意味著在所有未被預載入的圖片請求之前被請求(“late”意味著之後),感謝 Paul Irish 更新這張關於開發者工具以及網路層上各種請求優先順序的表。
我們來談一下這張表。
指令碼根據它們在檔案中的位置是否非同步、延遲或阻塞獲得不同的優先順序:
- 網路在第一個圖片資源之前阻塞的指令碼在網路優先順序中是中級
- 網路在第一個圖片資源之後阻塞的指令碼在網路優先順序中是低階
- 非同步/延遲/插入的指令碼(無論在什麼位置)在網路優先順序中是很低階
圖片(視口可見)將會獲得相對於視口不可見圖片(低階)的更高的優先順序(中級),所以某些程度上 Chrome 將會盡量懶載入這些圖片。低優先順序的圖片在佈局完成被視口發現時,將會獲得優先順序提升(但是注意已經在佈局完成後的圖片將不會更改優先順序)。
preload 使用 “as” 屬性載入的資源將會獲得與資源 “type” 屬性所擁有的相同的優先順序。比如說,preload as="style" 將會獲得比 as=“script” 更高的優先順序。這些資源同樣會受內容安全策略的影響(比如說,指令碼會受到其 “src” 屬性的影響)。
不帶 “as” 屬性的 preload 的優先順序將會等同於非同步請求。
如果你想了解各種資源載入時的優先順序屬性,從開發者工具的 Timeline/Performance 區域的 Network 區域都能看到相關資訊:
在 Network 皮膚下的“Priority”部分
當頁面 preload 已經在 Service Worker 快取及 HTTP 快取中的資源時會發生什麼?
這就要說看情況了,但通常來說,會是比較好的情況 —— 如果資源沒有超出 HTTP 快取時間或者 Service Worker 沒有主動重新發起請求,那麼瀏覽器就不會再去請求這個資源了。
如果資源在 HTTP 快取( Service Worker 快取和網路中),那麼 preload 將會獲得一次快取命中。
這將會浪費使用者的頻寬嗎?
**用“preload”和“prefetch”情況下,如果資源不能被快取,那麼都有可能浪費一部分頻寬。
沒有用到的 preload 資源在 Chrome 的 console 裡會在 onload 事件 3s 後發生警告。
原因是你可能為了改善效能使用 preload 來快取一定的資源,但是如果沒有用到,你就做了無用功。在手機上,這相當於浪費了使用者的流量,所以明確你要 preload 物件。
什麼情況會導致二次獲取?
preload 和 prefetch 是很簡單的工具,你很容易不小心二次獲取。
不要用 “prefetch” 作為 “preload” 的後備,它們適用於不同的場景,常常會導致不符合預期的二次獲取。使用 preload 來獲取當前需要任務否則使用 prefetch 來獲取將來的任務,不要一起用。
不要指望 preload 和 fetch() 配合使用,在 Chrome 中這樣使用將會導致二次的下載。這並不只發生在非同步請求的情況我們有一個關於這個問題公開的 bug。
對 preload 使用 “as” 屬性,不然將不會從中獲益。
如果你對你所 preload 的資源使用明確的 “as” 屬性,比如說,指令碼,你將會導致二次獲取。
preload 字型不帶 crossorigin 也將會二次獲取! 確保你對 preload 的字型新增 crossorigin 屬性,否則他會被下載兩次,這個請求使用匿名的跨域模式。這個建議也適用於字型檔案在相同域名下,也適用於其他域名的獲取(比如說預設的非同步獲取)。
存在 intergrity 屬性的資源不能使用 preload 屬性(目前)也會導致二次獲取。連結元素的 [integrity](https://bugs.chromium.org/p/chromium/issues/detail?id=677022)
屬性目前還沒有被支援,目前有一個關於它的開放issue,這意味著存在 integrity 的元素將會丟棄 preload 的資源。往寬了講,這會導致重複的請求,你需要在安全和效能之間作出權衡。
最後,雖然它不會導致二次獲取,還是有下面的建議:
不要 preload 所有東西! 作為替代的,用 preload 來告訴瀏覽器一些本來不能被提早發現的資源,以便提早獲取它們。
我應當在頁面頭部載入所有的資原始檔嗎?有什麼建議比如說限制“只載入6個檔案”?
這是工具而不是規則的好例子。你 preload 的檔案數量取決於載入其他資源時網路內容、使用者的頻寬和其他網路狀況。
儘早 preload 頁面中可能需要的檔案,對於指令碼檔案,preload 關鍵打包檔案很有用因為它將載入與執行分離開來,script async
不好因為它會阻塞 window 的 onload 事件。你可以儘早載入圖片、樣式、字型和媒體資源。大部分的 —— 最重要的是,你作為作者是可以清晰的知道哪些東西是頁面目前需要的。
prefetch 有哪些你需要知道的魔法屬性嗎?當然
在 Chrome 中,如果使用者從一個頁面跳轉到另一個頁面,prefetch 發起的請求仍會進行不會中斷。
另外,prefetch 的資源在網路堆疊中至少快取 5 分鐘,無論它是不是可以快取的。
我在 JS 中使用自定義的 “preload”,它跟原本的 rel="preload" 或者 preload 頭部有什麼不同?
preload 將資源獲取與執行解耦,像這樣,preload 在標記中宣告以被 Chrome preload 掃描器掃描。這意味著,在許多案例中,在 HTML 解析器獲取到標籤之前,preload 就會被獲取(用它宣告的優先順序)。這將會比自定義的 preload 更加強大。
等一下,我不是可以用 HTTP/2 的伺服器推送來代替 preload 嗎?
當你知道資源載入的正確順序時使用推送,用 service worker 來攔截那些可能需要會導致二次獲取的資源請求,用 preload 來加快第一個請求的開始時間 —— 這對所有的資源獲取都有用。
再次說一下,這都要看情況,我們試想一下位 Google Play 商店做購物車,對於一個向購物車的請求:
用 preload 來載入頁面的主要的模組需要瀏覽器等待 play.google.com/cart 有效載荷以便 preload 掃描器發現依賴,但這之後會浸透網路管道可以更好的像資源發起請求,這可能不是最理想的冷啟動,但對於快取記憶體和頻寬的後續請求非常友好。
使用 HTTP/2 的伺服器推送,當請求 play.google.com/cart 我們可以快速浸透網路管道,但如果資源已經在 HTTP 或者 Service Worker 快取中的話我們就浪費了頻寬,兩種方法都需要做出權衡。
雖然推送很有效,但它不像 preload 那樣對所有的情況都適應。
preload 利於下載與執行的解耦,多虧其對文件 onload 事件的支援我們現在可以控制其載入完畢後的事件,獲取 JS 包檔案在空閒快執行或者獲取 CSS 模組在正確的時間點執行,可以說是非常強大的。
推送不能用於第三方資源的內容,通過立即傳送資源,它還有效地縮短瀏覽器自身的資源優先順序情況。在你明確的知道在做什麼時,這應該會提高你的應用效能,如果不是很清晰的話,你也許會損失掉部分的效能。
preload HTTP 頭是什麼?跟 preload 標籤有什麼不同?又跟 HTTP/2 伺服器推送有什麼不同?
跟其他連結不同,preload 連結即可以放在 HTML 標籤裡也可以放在 HTTP 頭部(preload HTTP 頭),每種情況下,都會直接使瀏覽器載入資源並快取在記憶體裡,表明頁面有很高的可能性用這些資源並且不想等待 preload 掃描器或者解析器去發現它。
當金融時報在它們的網站使用 preload HTTP 頭時,他們節約了大約 1s 的顯示片頭圖片時間。
下面的:使用 preload,上面:使用 preload。在 3G 網路下的 Moto G4 測試。
原來:www.webpagetest.org/result/1703…,之後: www.webpagetest.org/result/1703…。你可以使用兩種形式的 preload,但應當知道很重要的一點:根據規範,許多伺服器當它們遇到 preload HTTP 頭會發起 HTTP/2 推送,HTTP/2 推送的效能影響不同於普通的預載入,所以你要確保沒有發起不必要的推送。
你可以使用 preload 標籤來代替 preload 頭以避免不必要的推送,或者在你的 HTTP 頭上加一個 “nopush” 屬性。
我怎樣檢測 link rel=preload 的支援情況呢?
用下面的程式碼段可以檢測<link rel=”preload”>
是否被支援:
const preloadSupported = () => {
const link = document.createElement('link');
const relList = link.relList;
if (!relList || !relList.supports)
return false;
return relList.supports('preload');
};複製程式碼
FilamentGroup 也有一個 preload 檢測器 ,作為他們的非同步 CSS 載入庫 loadCSS 的一部分。
你可以讓 preload的 CSS 樣式表立即生效嗎?
當然,preload 支援基於非同步載入的標記,使用 <link rel=”preload”>
的樣式表使用 onload
事件立即應用到文件:
<link rel="preload" href="style.css" onload="this.rel=stylesheet">複製程式碼
更多相關的例子,看一下 Yoav Weiss 很棒的使用例項。
preload 還有哪些更廣泛的應用?
根據 HTTPArchive,很多網站應用 <link rel=”preload”>
來載入字型,包括 Teen Vogue 和以上提到的其他網站:
其他一些網站,比如 LifeHacker 和 JCPenny 用 FilamentGroup 的 loadCSS 來非同步載入 CSS:
有越來越多的漸進式 Web 應用程式(比如 Twitter.com 移動端, Flipkart 和 Housing)使用它來載入當前連結需要的指令碼:
基本的觀點是要保持高粒度而不是單片,所以任何應用都可以按需載入依賴或者預載入資源並放在快取中。
當前瀏覽器對 preload 和 prefetch 的支援度?
根據 CanIUse 在 Safari Tech Preview的調檢視,<link rel="preload">
大約有 50% 的支援度,<link rel="prefetch">
大約有 70% 的支援度。<link rel="preload">
is available to ~50% of the global population according to CanIUse and is implemented in the Safari Tech Preview. <link rel="prefetch">
is available to 71% of global users.
更多有用的見解
- Yoav Weiss 最近對 Chrome 裡 preload CSS 和 阻塞的指令碼做了更改。
- 他最近還把 preload 媒體分成三個不同的型別:video、audio 和 track。
- Domenic Denicola 正在尋求規格的改變以便支援 ES6 模組。
- Yoav Weiss 最近還增加了在 HTTP 頭部支援 Link header support for “prefetch” 以便更容易的載入下一個頁面的資源。
擴充閱讀
- Preload — what is it good for? — Yoav Weiss
- A study by the Chrome Data Saver team
- Planning for performance — Sam Saccone
- Webpack plugin for auto-wiring up
- What is preload, prefetch and preconnect? — KeyCDN
- Web Fonts preloaded by Zach Leat
- HTTP Caching: cache-control by Ilya Grigorik
感謝 @ShopifyEng、@AdityaPunjani、@HousingEngg、@adgad、@wheresrhys 和 @__lakshya 分享的統計資訊。
非常感謝下列技術稽核與建議人員: Ilya Grigorik, Gray Norton, Yoav Weiss, Pat Meenan, Kenji Baheux, Surma, Sam Saccone, Charles Harrison, Paul Irish, Matt Gaunt, Dru Knox, Scott Jehl.
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃。