最近的專案中為了能夠提升那麼一丟丟效能,嘗試了一下對 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' 的標籤就會將其推入到預載入器中,這個預載入器也將用於其他我們所需要的,各種各樣的,任意型別的資源。為了完成基本的配置,你還需要通過 href
和as
屬性指定需要被預載入資源的資源路徑及其型別。
引用一段 MDN 的描述:
<link>
元素的rel
屬性的屬性值preload
能夠讓你在你的HTML頁面中<head>
元素內部書寫一些宣告式的資源獲取請求,可以指明哪些資源是在頁面載入完成後即刻需要的。對於這種即刻需要的資源,你可能希望在頁面載入的生命週期的早期階段就開始獲取,在瀏覽器的主渲染機制介入前就進行預載入。這一機制使得資源可以更早的得到載入並可用,且更不易阻塞頁面的初步渲染,進而提升效能。本文提供了一個如何有效使用preload
機制的基本說明
其特點是 宣告式的資源獲取請求(fetch)、不易(注意不是不會)阻塞 onLoad、as 提供的細粒度的控制
總結其優勢:
- 更精確地優化資源載入優先順序,這種方式可以確保資源根據其重要性依次載入。
- 匹配未來的載入需求,在適當的情況下(相同的資源),重複利用同一資源。
- 為資源應用正確的內容安全策略。
- 為資源設定正確的
Accept
請求頭。
注意:忽略 as 屬性,或者錯誤的 as 屬性會使 preload 等同於 XHR 請求,瀏覽器不知道載入的是什麼,因此會賦予此類資源非常低的載入優先順序。
preload
還支援 onload
預載入完成回撥、MIME、跨域獲取、響應式的預載入、指令碼化載入等,更多可以參考 MDN,以及這裡,還有這裡。
快取
既然 preload
以及 prefetch
都是優先從 HTTP
快取中獲取資源,我們必然要接觸很多 304 Not Modified
響應。
我們先來看一個 304 響應:
304 中最重要的兩個請求頭就是 If-None-Match
和 If-Modified-Since
。
前者的值是上一次響應中返回的 ETag
,ETag
是對該資源的一種唯一標識,只要資源有變化,Etag
就會重新生成。伺服器接收到請求頭中的 If-None-Match
之後就和檔案資源的 Etag
做比較,如果一樣,說明資源沒有變化,就返回一個 304 Not Modified
並且沒有響應體。客戶端收到 304 響應後,就會從快取中讀取對應的資源,如果不相同,則表示資源發生了改變,那麼伺服器就會返回 HTTP/200 OK
響應,響應體就是該資源當前最新的內容.客戶端收到 200 響應後,就會用新的響應體覆蓋掉舊的快取資源。
後者對應的上一次響應返回的 Last-Modified
。Last-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 分鐘內再次載入資源,就會命中強快取。
使用者行為對瀏覽器快取的控制:
- F5 重新整理,瀏覽器會設定max-age=0,跳過強快取判斷,會進行協商快取判斷。
- Ctrl + F5, 跳過協商快取與強快取,直接從伺服器拉取資源。
- 位址列訪問,連結跳轉是正常使用者行為,將會觸發瀏覽器快取機制。
未完待續....