webpack實現靜態資源緩存的那點事

引言

靜態資源緩存是前端性能優化的一個點,因此在前端開發過程當中,通常會最大限度的利用緩存(這裏主要是強緩存)。回到本文主題,在使用webpack構建的項目中,稍有不慎的話,即便服務器設置了緩存策略,可能構建的項目沒法實現靜態資源緩存。那麼webpack怎樣才能達到使用緩存的效果呢,下面就來談談這個問題。javascript

區分一下幾種不一樣的hash

咱們都知道,webpack有各類hash值,包括每次項目構建hash,不一樣入口的chunkhash、文件的內容contenthash,這麼多hash,它們有什麼區別呢?css

hash

hash是跟整個webpack構建項目相關的,每次項目構建hash對應的值都是不一樣的,即便項目文件沒有作「任何修改」html

實際上是有修改的,由於每次webpack打包編譯都會注入webpack的運行時代碼,致使整個項目有變化,因此每次hash值都會變化的。前端

以本人項目代碼爲例,代碼兩次構建先後沒有作任何修改的對比圖
java

能夠看出,先後兩次對應項目構建hash改變了。由此推斷使用該方式是沒法達到緩存的,由於每次hash都會變化。node

chunkhash

chunkhash,從字面上就能猜出它是跟webpack打包的chunk相關的。具體來講webpack是根據入口entry配置文件來分析其依賴項並由此來構建該entry的chunk,並生成對應的hash值。不一樣的chunk會有不一樣的hash值。通常在項目中把公共的依賴庫和程序入口文件隔離並進行單獨打包構建,用chunkhash來生成hash值,只要依賴公共庫不變,那麼其對應的chunkhash就不會變,從而達到緩存的目的。react

通常在項目中對webpack的entry使用chunkhash,具體表如今output配置項上:webpack

moudule.exports = {
  entry: {
   app: './src/main.js',
   vendor: ['react', 'redux', 'react-dom', 'react-redux', 'react-router-redux']
  },
  output: {
    path:path.join(__dirname, '/dist/js'),
    filename: '[name].[chunkhash].js'
  }
 ...
}

最後appvendor的chunkhash編譯結果以下圖git

contenthash

contenthash表示由文件內容產生的hash值,內容不一樣產生的contenthash值也不同。在項目中,一般作法是把項目中css都抽離出對應的css文件來加以引用。比方在webpack配置這樣來用:github

module.exports = {
  ...
  plugins: [
     new ExtractTextPlugin({
    filename: 'static/[name]_[chunkhash:7].css',
    disable: false,
    allChunks: true
     })
  ...
  ]

上面配置有一個問題,由於使用了chunkhash,它與依賴它的chunk共用chunkhash。

比方在上面app chunk例子中依賴一個index.css文件,index.css的hash是跟着app的chunkhash走的,只要app文件變動的話,那麼即便index.css文件沒有變化,它的hash值也是會跟着變化的,致使緩存失效。

那麼這時咱們可使用extra-text-webpack-plugin裏的contenthash值,保證即便css文件所處的模塊裏就算其餘文件內容改變,只要css文件內容不變,它的hash值就不會變。

實現js緩存

webpack插件CommonsChunkPlugin的主要做用是抽取webpack項目入口chunk的公共部分,具體的用法就不作過多介紹,不太瞭解能夠參考webpack官網介紹;

該插件是webpack項目經常使用的一個優化功能,幾乎在每一個webpack項目中都會用到。使用該插件帶來的好處:

  • 提高webpack打包速度和項目體積:將webpack入口的chunk文件中全部公共的代碼提取出來,減小代碼體積;同時提高webpack打包速度。

  • 利用緩存機制:依賴的公共模塊文件通常不多更改或者不會更改,這樣獨立模塊文件提取出能夠長期緩存。

可是在項目中,若插件打開方式不正確的話,上面的第二點實際上是沒法實現,由於這種狀況下:

沒有被修改過的公有代碼或庫代碼打包出的Entry Chunk,會隨着其餘業務代碼的變化而變化,致使頁面上的長緩存機制失效。

那麼,下面就來開啓CommonsChunkPlugin正確的打開方式。

CommonsChunkPlugin不正確用法

假如將咱們項目的公共庫如react、react-dom、react-router與業務代碼隔離,將其提取爲vendor chunk,webpack配置以下:

const webpack = require("webpack");
const path = require('path');
module.exports = {
  entry: {
    app: "./src/main.js",
    vendor: ["react","react-dom", "redux", "react-redux", "react-router-redux"]
  },
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: "[name].[chunkhash].js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({names: ["vendor"]})
  ]
};

上面將項目一些基礎庫打包成一個名爲vendor的chunk中,並將業務相關的代碼打包到一個名爲app的chunk中;

webpack打包編譯後的結果以下:

咱們對其中的業務代碼app.js進行修改後,從新編譯結果以下:

能夠發現,在CommonsChunkPlugin這種配置下,當業務代碼app發生變化,而庫代碼也跟着變化,vender的chunkhash也跟着變化,這樣vendor的引用的名稱跟着變化,致使瀏覽器端的長緩存機制失效。

引發問題的緣由

引發webpack每次打包編譯時vendor跟着變化的緣由:

webpack每次build的時候都會生成一些運行時代碼。當只有一個文件時,運行時代碼直接塞到這個文件中。當有多個文件時,運行時代碼會被提取到公共文件中,也就是上面CommonsChunkPlugin配置的vendor chunk中。

webpack每次編譯時產生的運行時代碼,包括全局webpackJsonp方法的定義和維護模塊依賴關係,具體能夠參考這裏>>

因此,上面webpack的CommonsChunkPlugin配置中,每次編譯時這些代碼都會打包到vendor中,致使每次vendor的chunkhash每次都會變化。

那麼,咱們能夠在對vendor chunk進行配置,抽取其中的公共代碼,即webpack運行時代碼,這樣就能夠將項目依賴的基礎庫模塊與業務模塊隔離開來,由於不會對這些文件進行修改,因此這些文件可達到長緩存的做用。具體配置以下:

module.exports = {
  entry: {
    app: "./app.js",
    vendor: ["react","react-dom", "redux", "react-redux", "react-router-redux"]
  },
  ....
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({names: ["vendor"]}),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'manifest',
        chunks: ['vendor']
    })
  ]
};

這樣,即便修改業務app代碼,項目依賴的基礎庫vendor chunk也不會發生變化;只是抽取的manifest chunk每次還會變化,可是這個文件體積很是小,相比vendor來講這種方式的收益更大。以下圖:

修改app代碼後的打包編譯結果以下,能夠看到vendor的chunkhash沒有變化

在webpack中配置CommonsChunkPlugin時須要注意幾點:

一、 配置webpack的output項時,其filenamechunkFilename必須使用chunkhash。不要使用hash,不然即便按照上面的配置也不能達到預期的效果。至於hash與chunkhash的區別,可參考github的回答

二、對於圖片、字體等靜態資源抽離使用的file-loader,其配置的hash表示的是靜態文件的內容hash值,不是webpack每次打包編譯生成的hash值, 切記!!!

三、對於抽取的css樣式文件,須要使用contenthash, 與file-loader中的hash意義相同。此處不能爲chunkhash,不然其與抽取該樣式文件的entry chunk的chunkhash保持一致,打不到緩存的目的。

實現css的緩存

webpack實現css的緩存,就是使用上面介紹過的contenthash,該hash屬性值實際上是extra-text-webpack-plugin計算的。具體實現css的緩存,其實就像下面同樣使用contenthash便可

module.exports = {
  ...
  plugins: [
     new ExtractTextPlugin({
    filename: 'static/[name]_[contenthash:7].css',
    disable: false,
    allChunks: true
     })
  ...
  ]

實現圖片/字體的緩存

對於圖片、字體等靜態資源,在使用webpack構建提取時,實際上是使用了file-loader來完成的,生成對應的文件hash值也就是由對應的file-loader來計算的。那麼這些靜態文件的hash值使用的是什麼hash值呢,其實就是hash屬性值。以下面代碼所示:

module.exports = {
 ...
 rules: [
   ...
    {
      test: /\.(gif|png|jpe?g)(\?\S*)?$/,
      loader: require.resolve('url-loader'),
      options: {
        limit: 10000,
        name: path.posix.join('static',  '[name]_[hash:7].[ext]')
      }
    },
    font: {
      test: /\.otf|ttf|woff2?|eot(\?\S*)?$/,
      loader: require.resolve('url-loader'),
      options: {
        limit: 10000,
        name: path.posix.join('static', '[name]_[hash:7].[ext]')
      }
    }
 ]
}

能夠看到上面使用的是hash屬性值,此hash非webpack每次項目構建的hash,它是由file-loader根據文件內容計算出來的,不要誤認爲是webpack構建的hash。

參考

一、webpack之CommonsChunkPlugin正確打開方式
二、webpack 填坑之路--提取獨立文件(模塊)
三、webpack代碼分割技巧
四、據說你用webpack處理文件名的hash?那麼建議你看看你生成的hash對不對
五、chunkhash
六、multiple-commons-chunks
七、用 webpack 實現持久化緩存
八、Webpack中hash與chunkhash的區別,以及js與css的hash指紋解耦方案

相關文章
相關標籤/搜索