預載入與快取

波比小金剛發表於2018-06-06

最近的專案中為了能夠提升那麼一丟丟效能,嘗試了一下對 chunks 進行預載入處理。雖然做了非同步載入的處理,但是專案大小決定了還是有多個非同步的 chunk.js 需要進行預載入,這裡我指的是 preload prefetch

這裡推薦一個 GoogleChromeLabs 團隊推出的外掛:preload-webpack-plugin

A webpack plugin for injecting <link rel='preload|prefecth'> into HtmlWebpackPlugin pages, with async chunk support

預載入與快取

使用大致如下,具體參考其文件:

config = rewirePreloadPlugin(config, env, {  
  rel: 'preload',  
  include: 'initial',  
  as(entry) {    
    if (/\.(css|less)$/.test(entry)) return 'style';    
    if (/\.woff$/.test(entry)) return 'font';    
    if (/\.(png|jpg|jpeg|svg)$/.test(entry)) return 'image';    
    return 'script';  
  }});
複製程式碼

這個外掛對字型元素( woff ),自動加了跨域:

原始碼:

const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '';

filesToInclude+= `<link rel="${options.rel}" as="${asValue}" ${crossOrigin}href="${entry}">\n`複製程式碼

使用後:

預載入與快取

將會在你的 index.html 中的 Head 標籤裡面新增一堆 <link rel='preload' as='script' href='xxx.chunk.js'>

這就意味著這些資源都會被預載入,然後從快取中去獲取。將會提升網頁開啟的效率,但是濫用也會浪費頻寬。

實際使用中有一個問題,就是我使用的是 create-react-app 並且沒有 eject 的情況下,目前這個外掛對於 preload 的模式包括 include: 'initial' | 'allChunks' | 'asyncChunks' |'allAssets' | ['chunkName'],(ps: initial 模式還是尤雨溪大大提的 PR )並且提供了黑白名單,應該說是很細粒度的控制我們去按需設定檔案為 preload 的。

但是實際上還是會造成有我不需要其 preload 的檔案被設定了,這就造成了浪費。

Preload 與 Prefetch

<link rel='prefetch'> 出現的更早,瀏覽器支援度也挺不錯,通俗的理解是著眼於下一個頁面資源的預載入,所以在當前頁面的優先順序是很低的。

prefetch 在瀏覽器空閒時間下載或預取使用者在不久的將來可能訪問的文件,在當前的頁面載入完成後預取資源放進快取中,在之後的呼叫就直接從快取中獲取,從而提升效能。

<link rel='preload' as=''> 則是著眼於現在(當前頁面)。瀏覽器遇到 rel= 'preload' 的標籤就會將其推入到預載入器中,這個預載入器也將用於其他我們所需要的,各種各樣的,任意型別的資源。為了完成基本的配置,你還需要通過 hrefas 屬性指定需要被預載入資源的資源路徑及其型別。

引用一段 MDN 的描述:

<link> 元素的 rel 屬性的屬性值preload能夠讓你在你的HTML頁面中 <head>元素內部書寫一些宣告式的資源獲取請求,可以指明哪些資源是在頁面載入完成後即刻需要的。對於這種即刻需要的資源,你可能希望在頁面載入的生命週期的早期階段就開始獲取,在瀏覽器的主渲染機制介入前就進行預載入。這一機制使得資源可以更早的得到載入並可用,且更不易阻塞頁面的初步渲染,進而提升效能。本文提供了一個如何有效使用preload機制的基本說明

其特點是 宣告式的資源獲取請求(fetch)、不易(注意不是不會)阻塞 onLoad、as 提供的細粒度的控制

總結其優勢:

  1. 更精確地優化資源載入優先順序,這種方式可以確保資源根據其重要性依次載入。
  2. 匹配未來的載入需求,在適當的情況下(相同的資源),重複利用同一資源。
  3. 為資源應用正確的內容安全策略
  4. 為資源設定正確的 Accept 請求頭。

注意:忽略 as 屬性,或者錯誤的 as 屬性會使 preload 等同於 XHR 請求,瀏覽器不知道載入的是什麼,因此會賦予此類資源非常低的載入優先順序。

preload 還支援 onload 預載入完成回撥、MIME、跨域獲取、響應式的預載入、指令碼化載入等,更多可以參考 MDN,以及這裡,還有這裡

快取

既然 preload 以及 prefetch 都是優先從 HTTP 快取中獲取資源,我們必然要接觸很多 304 Not Modified 響應。

我們先來看一個 304 響應:

預載入與快取

304 中最重要的兩個請求頭就是 If-None-MatchIf-Modified-Since

前者的值是上一次響應中返回的 ETagETag 是對該資源的一種唯一標識,只要資源有變化,Etag 就會重新生成。伺服器接收到請求頭中的 If-None-Match 之後就和檔案資源的 Etag 做比較,如果一樣,說明資源沒有變化,就返回一個 304 Not Modified 並且沒有響應體。客戶端收到 304 響應後,就會從快取中讀取對應的資源,如果不相同,則表示資源發生了改變,那麼伺服器就會返回 HTTP/200 OK 響應,響應體就是該資源當前最新的內容.客戶端收到 200 響應後,就會用新的響應體覆蓋掉舊的快取資源。

後者對應的上一次響應返回的 Last-ModifiedLast-Modified 是該資原始檔最後一次更改時間,伺服器會在 response header 裡返回,同時瀏覽器會將這個值儲存起來,在下一次傳送請求時,放到 request header 裡的 If-Modified-Since 裡,伺服器在接收到後也會做比對,如果沒過期則返回304,如果過期則返回200 ok。同樣會更新舊的檔案。

如果兩個頭部都不帶的請求就是無條件(unconditionally)請求該資源,伺服器也就必須返回完整的資源資料。

上面所述的幾個頭部就是協商快取的關鍵人物了。

這裡還要說明一個概念就是條件請求,所謂條件就是無法確定前端快取資源是否最新,所以通過上述的頭部來做一個驗證,返回 304 或者 200。通過條件請求我們可以節省出一個響應體,但是請求還是發出去了。

這裡如果連請求都不想發出去怎麼辦呢?

強快取、協商快取

瀏覽器快取主要有兩類:快取協商和徹底快取,也有稱之為協商快取強快取

1.強快取:不會向伺服器傳送請求,直接從快取中讀取資源,在chrome控制檯的network選項中可以看到該請求返回200的狀態碼;

2.協商快取:向伺服器傳送請求,伺服器會根據這個請求的request header的一些引數來判斷是否命中協商快取,如果命中,則返回304狀態碼並帶上新的response header通知瀏覽器從快取中讀取資源;

兩者的共同點是,都是從客戶端快取中讀取資源;區別是強快取不會發請求,協商快取會發請求。

瀏覽器快取流程圖:

預載入與快取

所以我們可以通過設定 Expires( HTTP1.0 ) 以及 Cache-Control( HTTP1.1 ),來命中強快取,從而跳過傳送請求的過程。

Expires:response header裡的過期時間,瀏覽器再次載入資源時,如果在這個過期時間內,則命中強快取。

Cache-Control:當值設為max-age= 600 時,則代表在這個請求正確返回時間(瀏覽器也會記錄下來)的 10 分鐘內再次載入資源,就會命中強快取。

使用者行為對瀏覽器快取的控制:

  1. F5 重新整理,瀏覽器會設定max-age=0,跳過強快取判斷,會進行協商快取判斷。
  2. Ctrl + F5, 跳過協商快取與強快取,直接從伺服器拉取資源。
  3. 位址列訪問,連結跳轉是正常使用者行為,將會觸發瀏覽器快取機制。

未完待續....


相關文章