[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

guoyang134340發表於2017-04-08

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

今天我們來深入研究一下 Chrome 的網路協議棧,來更清晰的描述早期網路載入(像 <link rel=“preload"<link rel=“prefetch”>)背後的工作原理,讓你對其更加了解。

其他文章描述的那樣,preload 是宣告式的 fetch,可以強制瀏覽器請求資源,同時不阻塞文件 onload 事件。

Prefetch 提示瀏覽器這個資源將來可能需要,但是把決定是否和什麼時間載入這個資源的決定權交給瀏覽器。

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

Preload 將 load 事件與指令碼解析過程解耦,如果你還沒有用過它,看看 Yoav Weiss 的文章 Preload: What is it Good For?

Preload 在生產環境的成功案例

在我們深入細節之前,下面是上一年發現的使用 proload 並對載入產生積極影響的案例總結:

Housing.com 在對他們的漸進式 Web 應用程式的指令碼轉用 proload 看到大約縮短了10%的可互動時間

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

Shopify 在轉用 preload 載入字型後在 Chrome 桌面版獲得了 50%(1.2s) 的文字渲染優化,這完全解決了他們的文字閃動問題。

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

左邊:使用 preload,右邊:不使用 preload(視訊

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

使用<link rel=”preload”> 載入字型。

Treebo,印度最大的旅館網站之一,在 3G 網路下對其桌面版試驗,在對其頂部圖片和主要的 Webpack 打包檔案使用 preload 之後,在首屏繪製和可互動延遲分別減少了 1s

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

同樣的,在對自己的漸進式 Web 應用程式主要打包檔案使用 preload 之後,Flipkart 在路由解析之前 節省了大量的主執行緒空閒時間(在 3G 網路下的低效能手機下)。

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

上面:未使用 preload,下面:使用 preload

Chrome 資料保護團隊在對指令碼和 CSS 樣式表使用 preload 之後,發現頁面首次繪製時間獲得平均 12% 的速度提升。

對於 prefetch ,它被廣泛使用,在 Google 我們仍用它來獲取可以加快 搜尋結果頁面 的渲染的關鍵資源。

Preload 在很多大型網站都有實際應用,這點你在接下來的文章裡也可以看到,讓我們來仔細探討下網路協議棧實際上是如何對待 preload 和 prefetch 的。

建議:對於當前頁面很有必要的資源使用 preload,對於可能在將來的頁面中使用的資源使用 prefetch。

preload 是對瀏覽器指示預先請求當前頁需要的資源(關鍵的指令碼,字型,主要圖片)。

prefetch 應用場景稍微又些不同 —— 使用者將來可能在其他部分(比如檢視或頁面)使用到的資源。如果 A 頁面發起一個 B 頁面的 prefetch 請求,這個資源獲取過程和導航請求可能是同步進行的,而如果我們用 preload 的話,頁面 A 離開時它會立即停止。

使用 preload 和 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,Prefetch 和它們在 Chrome 之中的優先順序

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 區域都能看到相關資訊:

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

在 Network 皮膚下的“Priority”部分

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

當頁面 preload 已經在 Service Worker 快取及 HTTP 快取中的資源時會發生什麼?

這就要說看情況了,但通常來說,會是比較好的情況 —— 如果資源沒有超出 HTTP 快取時間或者 Service Worker 沒有主動重新發起請求,那麼瀏覽器就不會再去請求這個資源了。

如果資源在 HTTP 快取( Service Worker 快取和網路中),那麼 preload 將會獲得一次快取命中。

這將會浪費使用者的頻寬嗎?

**用“preload”和“prefetch”情況下,如果資源不能被快取,那麼都有可能浪費一部分頻寬。

沒有用到的 preload 資源在 Chrome 的 console 裡會在 onload 事件 3s 後發生警告。

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

原因是你可能為了改善效能使用 preload 來快取一定的資源,但是如果沒有用到,你就做了無用功。在手機上,這相當於浪費了使用者的流量,所以明確你要 preload 物件。

什麼情況會導致二次獲取?

preload 和 prefetch 是很簡單的工具,你很容易不小心二次獲取

不要用 “prefetch” 作為 “preload” 的後備,它們適用於不同的場景,常常會導致不符合預期的二次獲取。使用 preload 來獲取當前需要任務否則使用 prefetch 來獲取將來的任務,不要一起用。

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

不要指望 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,Prefetch 和它們在 Chrome 之中的優先順序

下面的:使用 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”>是否被支援:

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 和以上提到的其他網站:

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

其他一些網站,比如 LifeHacker 和 JCPenny 用 FilamentGroup 的 loadCSS 來非同步載入 CSS:

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

有越來越多的漸進式 Web 應用程式(比如 Twitter.com 移動端, Flipkart 和 Housing)使用它來載入當前連結需要的指令碼:

[譯]Preload,Prefetch 和它們在 Chrome 之中的優先順序

基本的觀點是要保持高粒度而不是單片,所以任何應用都可以按需載入依賴或者預載入資源並放在快取中。

當前瀏覽器對 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” 以便更容易的載入下一個頁面的資源。

擴充閱讀

感謝 @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.


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章