使用 Preload&Prefetch 優化前端頁面的資源加載

對於前端頁面來講,靜態資源的加載對頁面性能起着相當重要的做用。本文將介紹瀏覽器提供的兩個資源指令-preload/prefetch,它們可以輔助瀏覽器優化資源加載的順序和時機,提高頁面性能。javascript

1、從一個實例開始

如上圖所示,咱們開發了一個簡單的收銀臺,支付過程當中能夠展開優惠券列表選擇相應的券。從動圖能夠看到,列表第一次展開時,優惠券背景有一個逐漸顯示的過程,體驗上不是很好。css

問題的緣由也很明顯,因爲背景使用了視覺特地設計的圖片,優惠券列表展開時須要去加載圖片,背景漸顯的過程實際上就是圖片加載的過程;當網速慢的時候,這個問題會更加明顯。那麼,怎樣解決這個問題呢?html

仔細分析一下,咱們會發現問題的緣由在於背景圖的加載時機太晚。前端

若是能在優惠券列表渲染前加載好背景圖,這個問題就不會出現。從這個思路出發,咱們可能想到如下兩個方案:vue

  1. 使用內聯圖片,也就是將圖片轉換爲base64編碼的data-url。這種方式,實際上是將圖片的信息集成到css文件中,避免了圖片資源的單獨加載。但圖片內聯會增長css文件的大小,增長首屏渲染的時間。
  2. 使用js代碼對圖片進行預加載
preloadImage() {
    const imgList = [
        require('@/assets/imgs/error.png'),
        require('@/assets/imgs/ticket_bg.png')
    ];
    for (let i = 0; i < imgList.length; i++) {
        const newIMG = new Image();
        newIMG.src = imgList[i];
    }
}

這種方案主要是利用瀏覽器的緩存機制,由js代碼在特定時機提早加載相應圖片,優惠券列表渲染時就能夠直接從緩存獲取。不過,這種方案增長了額外的代碼,須要本身控制好加載時機,而且將圖片的url硬編碼在了邏輯中。 java

能夠看出,以上兩種方案可以解決咱們的問題,但都存在一些缺點。webpack

那麼,有沒有更好的解決方案呢?答案是prefetch-一種由瀏覽器原生提供的預加載方案。web

2、什麼是prefetch?

prefetch(連接預取)是一種瀏覽器機制,其利用瀏覽器空閒時間來下載或預取用戶在不久的未來可能訪問的文檔。網頁向瀏覽器提供一組預取提示,並在瀏覽器完成當前頁面的加載後開始靜默地拉取指定的文檔並將其存儲在緩存中。當用戶訪問其中一個預取文檔時,即可以快速的從瀏覽器緩存中獲得。--MDNvue-cli

具體來講,瀏覽器經過<link rel="prefetch" href="/library.js">標籤來實現預加載。瀏覽器

其中rel="prefetch"被稱爲Resource-Hints(資源提示),也就是輔助瀏覽器進行資源優化的指令。

相似的指令還有rel="preload",咱們會在後文說起。

<head>
    ...
    <link rel="prefetch" href="static/img/ticket_bg.a5bb7c33.png">
    ...
</head>

查看如今優惠券列表的加載效果。

果真,成功達成了咱們指望的效果。那麼瀏覽器是如何作的呢?咱們打開Chrome的Network面板一探究竟:

能夠看到,在首屏的請求列表中已經出現了優惠券背景圖ticket\_bg.png的加載請求,請求自己看起來和普通請求沒什麼不一樣;展開優惠券列表後,network中增長了一次新的ticket\_bg.png訪問請求,咱們很快發現,這個請求的status雖然也是200,但有一個特殊的標記—prefetch cache,代表此次請求的資源來自prefetch緩存。這個表現驗證了上文中prefetch的定義,即瀏覽器在空閒時間預先加載資源,真正使用時直接從瀏覽器緩存中快速獲取。

3、Preload

從上面的案例,咱們體會到了瀏覽器預加載資源的強大能力。實際上,預加載是一個廣義的概念,prefetch只是具體實現方式之一,本節咱們介紹下另一種預加載方式preload。上文咱們提到,preload與prefetch同屬於瀏覽器的Resource-Hints,用於輔助瀏覽器進行資源優化。爲了對二者進行區分,prefetch一般翻譯爲預提取,preload則翻譯爲預加載。

元素的rel屬性的屬性值preload可以讓你在你的HTML頁面中元素內部書寫一些聲明式的資源獲取請求,能夠指明哪些資源是在頁面加載完成後即刻須要的。對於這種即刻須要的資源,你可能但願在頁面加載的生命週期的早期階段就開始獲取,在瀏覽器的主渲染機制介入前就進行預加載。這一機制使得資源能夠更早的獲得加載並可用,且更不易阻塞頁面的初步渲染,進而提高性能。

簡單來講,就是經過<link rel="preload" href="xxx" as="xx">標籤顯式聲明一個高優先級資源,強制瀏覽器提早請求資源,同時不阻塞文檔正常onload。咱們一樣用一個實際案例進行詳細介紹。

上圖是咱們開發的另一個收銀臺,出於本地化的考慮,設計上使用了自定義字體。開發完成後咱們發現,頁面首次加載時文字會出現短暫的字體樣式閃動(FOUT,Flash of Unstyled Text),在網絡狀況較差時比較明顯(如動圖所示)。究其緣由,是字體文件由css引入,在css解析後纔會進行加載,加載完成以前瀏覽器只能使用降級字體。也就是說,字體文件加載的時機太遲,須要告訴瀏覽器提早進行加載,這偏偏是preload的用武之地。

咱們在入口html文件head加入preload標籤:

<head>
    ...
    <link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Demi.otf') %>" crossorigin>
    <link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Regular.otf') %>" crossorigin>
    ...
</head>

再次查看頁面首次加載的效果:

字體樣式閃動的現象沒有了!咱們對比下使用preload先後的network面板。

使用前:

使用後:

能夠發現字體文件的加載時機明顯提早了,在瀏覽器接收到html後很快就進行了加載。

注意:preload link必須設置as屬性來聲明資源的類型(font/image/style/script等),不然瀏覽器可能沒法正確加載資源。

4、Preload 和 Prefetch 的具體實踐

一、preload-webpack-plugin

前文中咱們舉的兩個例子,都是在入口html手動添加相關代碼:

<head>
    ...
    <link rel="prefetch" href="static/img/ticket_bg.a5bb7c33.png">
    ...
</head>
<head>
    ...
    <link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Demi.otf') %>" crossorigin>
    <link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Regular.otf') %>" crossorigin>
    ...
</head>

這顯然不夠方便,並且將資源路徑硬編碼在了頁面中(實際上,ticket_bg.a5bb7c33.png後綴中的hash是構建過程自動生成的,因此硬編碼的方式不少場景下自己就行不通)。webpack插件preload-webpack-plugin能夠幫助咱們將該過程自動化,結合htmlWebpackPlugin在構建過程當中插入link標籤。

const PreloadWebpackPlugin = require('preload-webpack-plugin');
...
plugins: [
  new PreloadWebpackPlugin({
    rel: 'preload',
    as(entry) {  //資源類型
      if (/\.css$/.test(entry)) return 'style';
      if (/\.woff$/.test(entry)) return 'font';
      if (/\.png$/.test(entry)) return 'image';
      return 'script';
    },
    include: 'asyncChunks', // preload模塊範圍,還可取值'initial'|'allChunks'|'allAssets',
    fileBlacklist: [/\.svg/] // 資源黑名單
    fileWhitelist: [/\.script/] // 資源白名單
  })
]

PreloadWebpackPlugin配置整體上比較簡單,須要注意的是include屬性。該屬性默認取值'asyncChunks',表示僅預加載異步js模塊;若是須要預加載圖片、字體等資源,則須要將其設置爲'allAssets',表示處理全部類型的資源。

但通常狀況下咱們不但願把預加載範圍擴得太大,因此須要經過fileBlacklist或fileWhitelist進行控制。

對於異步加載的模塊,還能夠經過webpack內置的/_ webpackPreload: true _/標記進行更細粒度的控制。

如下面的代碼爲例,webpack會生成<link rel="preload" href="chunk-xxx.js" as="script">標籤添加到html頁面頭部。

import(/* webpackPreload: true */ 'AsyncModule');
備註:prefetch的配置與preload相似,但無需對as屬性進行設置。

二、使用場景

從前文的介紹可知,preload的設計初衷是爲了儘早加載首屏須要的關鍵資源,從而提高頁面渲染性能。

目前瀏覽器基本上都具有預測解析能力,能夠提早解析入口html中外鏈的資源,所以入口腳本文件、樣式文件等不須要特地進行preload。

可是一些隱藏在CSS和JavaScript中的資源,如字體文件,自己是首屏關鍵資源,但當css文件解析以後纔會被瀏覽器加載。這種場景適合使用preload進行聲明,儘早進行資源加載,避免頁面渲染延遲。 

與preload不一樣,prefetch聲明的是未來可能訪問的資源,所以適合對異步加載的模塊、可能跳轉到的其餘路由頁面進行資源緩存;對於一些未來大機率會訪問的資源,如上文案例中優惠券列表的背景圖、常見的加載失敗icon等,也較爲適用。

三、最佳實踐

基於上面對使用場景的分享,咱們能夠總結出一個比較通用的最佳實踐:

  • 大部分場景下無需特地使用preload
  • 相似字體文件這種隱藏在腳本、樣式中的首屏關鍵資源,建議使用preload
  • 異步加載的模塊(典型的如單頁系統中的非首頁)建議使用prefetch
  • 大機率即將被訪問到的資源可使用prefetch提高性能和體驗

四、vue-cli3的默認配置

  • preload

默認狀況下,一個Vue CLI應用會爲全部初始化渲染須要的文件自動生成preload提示。這些提示會被@vue/preload-webpack-plugin注入,而且能夠經過chainWebpack的config.plugin('preload')進行修改和刪除。

  • prefetch

默認狀況下,一個Vue CLI應用會爲全部做爲async chunk生成的JavaScript文件(經過動態import()按需code splitting的產物)自動生成prefetch提示。這些提示會被@vue/preload-webpack-plugin注入,而且能夠經過chainWebpack的config.plugin('prefetch')進行修改和刪除。

5、總結和踩坑

一、preload和prefetch的本質都是預加載,即先加載、後執行,加載與執行解耦。

二、preload和prefetch不會阻塞頁面的onload。

三、preload用來聲明當前頁面的關鍵資源,強制瀏覽器儘快加載;而prefetch用來聲明未來可能用到的資源,在瀏覽器空閒時進行加載。

四、不要濫用preload和prefetch,須要在合適的場景中使用。

五、preload的字體資源必須設置crossorigin屬性,不然會致使重複加載。 

緣由是若是不指定crossorigin屬性(即便同源),瀏覽器會採用匿名模式的CORS去preload,致使兩次請求沒法共用緩存。

六、關於preload和prefetch資源的緩存,在Google開發者的一篇文章中是這樣說明的:若是資源能夠被緩存(好比說存在有效的cache-control和max-age),它被存儲在HTTP緩存(也就是disk cache)中,能夠被如今或未來的任務使用;若是資源不能被緩存在HTTP緩存中,做爲代替,它被放在內存緩存中直到被使用。 

然而咱們在Chrome瀏覽器(版本號80)中進行測試,結果卻並不是如此。將服務器的緩存策略設置爲no-store,觀察下資源加載狀況。

能夠發現ticket_bg.png第二次加載並未從本地緩存獲取,仍然是從服務器加載。所以,若是要使用prefetch,相應的資源必須作好合理的緩存控制。

七、沒有合法https證書的站點沒法使用prefetch,預提取的資源不會被緩存(實際使用過程當中發現,緣由未知)。

八、最後咱們來看下preload和prefetch的瀏覽器兼容性。

能夠看到,二者的兼容性目前都還不是太好。好在不支持preload和prefetch的瀏覽器會自動忽略它,所以能夠將它們做爲一種漸進加強功能,優化咱們頁面的資源加載,提高性能和用戶體驗。

做者: Sha Chaoheng
相關文章
相關標籤/搜索