前言css
上週五到的時候,想起以前在手遊平臺上有處理dns-prefetch的優化,那這篇分享的就更仔細了。今日早讀文章由@gy134340翻譯並受權分享。web
正文從這開始~chrome
今天咱們來深刻研究一下 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(視頻)
使用 加載字體。
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.
關於本文
譯者:@gy134340
譯文:https://juejin.im/post/58e8acf10ce46300585a7a42(掘金翻譯)
做者:@Addy Osmani
原文:https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf
校對者:@IridescentMia,@vuuihc