前端緩存策略與基於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緩存的實現方案主要分兩種:
- 利用服務端語言直接在返回頭中加上響應頭欄位。
- 修改伺服器配置,全局設置或添加響應頭欄位。
下面是我在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不被緩存),如下圖:
另外,可以利用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請求
※頁面白屏有哪些檢測手段?
※前端瀏覽器緩存及代碼部署