前端緩存策略與基於Webpack的靜態資源版本管理

為什麼要做HTTP緩存

自Web2.0開始,隨著Web產品和服務花樣不斷增加,網站的體積也開始變得越來越大。今天,體積過M的網站早已屢見不鮮。像facebook、twitter、淘寶、京東這樣的網站,首屏的體積甚至接近10M(其中包含不少的部分是懶惰載入的圖片)。如果不做前端緩存,每次打開網站都去伺服器拉取,一個是絕大部分為重複的靜態文件,浪費伺服器網路資源;二是響應時間往往要在十幾秒甚至二十秒(親測,4M帶寬下,淘寶首屏載入HTML,css,js的響應時間為15~20s),用戶體驗極不友好。在前端這個性能要求苛刻的領域,HTTP緩存便成為了解決這一問題的關鍵所在。

本文基於我在2016下半年學雷鋒項目教學輔助系統,描述如何實現利用瀏覽器強緩存(disk cache&memory cache),同時結合Webpack實現應對緩存的版本更新。

基本知識

強緩存利用HTTP Response Header中的Expires或Cache-Control欄位來實現,它們都用來表示資源在客戶端緩存的有效期。兩個header可以只啟用一個,也可同時啟用。Cache-Control優先順序高於Expires。

瀏覽器第一次跟伺服器請求一個資源,伺服器返回資源的響應頭如上(Chrome)。瀏覽器解析後,會將這個資源連同header一起緩存起來。當下次瀏覽器再請求這個資源時,將先在緩存中尋找,找到後再根據Expires或Cache-Control來計算是否過期。若未過期,則緩存命中,直接載入緩存資源。若已過期,則直接從伺服器載入資源,同時重新緩存新的資源包。

需要注意的是,Cache-Control:max-age=0時是不啟用緩存。另外,目前所有瀏覽器直接輸入網址跳轉都是利用緩存的,強制刷新ctrl+F5均放棄緩存,而刷新F5動作則有細微區別(Chrome使用緩存,Edge、Firefox放棄緩存)。

協商緩存利用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】兩對Header欄位來管理。在此不做過多贅述,詳細請閱讀瀏覽器緩存知識小結。

實現方案

HTTP緩存的實現方案主要分兩種:

  1. 利用服務端語言直接在返回頭中加上響應頭欄位。
  2. 修改伺服器配置,全局設置或添加響應頭欄位。

下面是我在Apache伺服器上的實現,做法是直接修改Apache的http.conf配置文件:

<IfModule mod_expires.c> ExpiresActive On #過期時間設為訪問日期起1個月 ExpiresByType text/css "access plus 1 months" ExpiresByType application/javascript "access plus 1 months" ExpiresByType image/jpeg "access plus 1 months" ExpiresByType image/bmp "access plus 1 months" ExpiresByType image/x-icon "access plus 1 months" ExpiresByType image/png "access plus 1 months"</IfModule>

注意,需要開啟mod_expires模塊。可以看到,代碼還是非常語義化的。

重啟伺服器後,先後兩次打開頁面(Chrome),在F12控制台的網路監控台下可以看到如下信息:

首屏:

二次載入:

HTTP包:

可以看到,資源都從本地緩存中載入,載入時間大大縮短(靜態資源越多,優化效果越好)。

但與之而來的一個問題是:如果在緩存有效期更新了伺服器資源,如何通知瀏覽器放棄本地緩存,從伺服器拉取更新後的資源呢?

利用Webpack實現資源增量更新

一個比較好的解決方案是利用Webpack在編譯階段為輸出文件加上hash指紋(根據模塊內容計算得出),從而實現前端靜態資源的增量更新。

例如之前的截圖中,index.86191.js文件的86191即為一個hash指紋。當該文件內容或其依賴的模塊的內容發生變化時,這個數字將發生變化。在生成帶有新的hash指紋的資源文件後,我們只須要將它們放入入口Html(入口Html不被緩存),如下圖:

瀏覽器解析新的Html後將去請求新的資源文件(index.53214.js),而非載入本地舊文件的緩存(index.86191.js)。

另外,可以利用HtmlWebpackPlugin把新生成的js自動加入到Html中。

下面是Webpack關於自動構建添加hash指紋與自動生成Html的配置代碼主要部分:

module.exports={ entry: { index:./frontend/index, vendor:[vue,vuex,vue-router] }, output:{ path:./build, filename:debug?[name].min.js:[name].[chunkhash:5].js, //這裡 publicPath:debug?/build/:, chunkFilename:[id].[chunkhash:5].chunk.js //這裡 }, module:{ loaders:[ { test:/.(png|jpe?g|svg|gif)(?S*)?$/, loader:file-loader, query:{ name:[name].[ext]?[hash] //還有這裡 } }, { test:/.vue$/, loader:vue } ] }, resolve:{ extensions:[,.js,.jsx], alias:{ vue$:vue/dist/vue.min.js } }, plugins:[ new HtmlWebpackPlugin({ //自動生成Html template:index.html, inject:body }) ]}

下面是截自學雷鋒項目中,帶有hash指紋的資源文件:

如此,我們便可以完成在強緩存下的資源增量更新。

相關閱讀

瀏覽器緩存知識小結

使用Apache Httpd實現http緩存

關於Webpack的hash指紋


推薦閱讀:

LsLoader 移動WEB工程化緩存方案
LsLoader 專註移動web的工程化本地緩存前端組件
網站性能優化——DNS預熱與合併HTTP請求
頁面白屏有哪些檢測手段?
前端瀏覽器緩存及代碼部署

TAG:前端性能优化 | 浏览器缓存 | Web开发 |