1. 程式人生 > >用 webpack 實現持久化緩存

用 webpack 實現持久化緩存

ges 而不是 2.2.0 operator rules 執行 pre 編譯時間 ID

什麽是持久化緩存?

原文鏈接https://sebastianblade.com/using-webpack-to-achieve-long-term-cache/

緩存(cache)一直是前端性能優化的重頭戲,利用好靜態資源的緩存機制,可以使我們的 web 應用更加快速和穩定。僅僅簡單的資源緩存是不夠的,我們還要為不斷更新的資源做持久化緩存(Long term cache)。以前我們能利用服務端模板和構建打包,來給資源增加版本標記,如 app.js?v=1.0.0,但在大流量的網站中,這種更新部署方式會引起下面的問題:技術分享圖片

大公司裏怎樣開發和部署前端代碼? - 回答作者: 張雲龍

上述回答中針對前端代碼部署的最終方案是:

  • 細粒度文件名替換(文件內容摘要)
  • 細粒度資源依賴追蹤和摘要
  • 非覆蓋式更新

但想從頭實現一整套完整的前端部署方案,對於小公司來說還是非常難的。不僅如此,從目前 Web 發展趨勢來看,如今前端早已不是傳統 Web 應用架構能夠 Hold 住的了,前後端分離,前端應用化、工程化的需求在迅速增加:模塊化開發、模塊依賴解析、代碼壓縮、圖片壓縮、請求數最小化、雪碧圖、字體壓縮、CSS 預處理、ES2015/6/7 編譯、模板引擎等,都是在構建過程中要實現的功能。

自從 Node.js 和 npm 問世後,最理解前端優化需求的前端架構師/工程師也可以用自己最熟悉的 JavaScript 來實現自己想要的工程化工具了,社區也先後創造出了 Grunt、gulp、fis、webpack、rollup 等工程化工具,它們作用及架構各不相同,如 gulp 專註流程化任務,rollup 專註模塊打包……

對於今天提出的問題:持久化緩存,它涉及了模塊化,模塊依賴,靜態資源優化,模塊打包,文件摘要處理等問題,如今(2016+)能把這些問題解決並做的最好的社區驅動工具有且只有 webpack。

同類模塊打包工具橫向對比表 -> Comparison - Why webpack?

目前 webpack 2.2.0 已正式發布,是時候用最新的工具來創建更完善的前端構建了。

通過配置,逐步實現持久化緩存

一、文件 Hash 摘要

  1. webpack 配置
  2. 不穩定的 chunkhash
  3. webpack-md5-hash 的問題
  4. 如何生成穩定的模塊 ID?

二、如何避免頻繁的 chunk 內容變動

  1. 合理劃分公共模塊
  2. 代碼分割
  3. import()
  4. 提取公共模塊
    • 提取頻繁共用的模塊
    • manifest(清單)
  5. 提取 CSS
  6. 對 chunks 做最後的優化

一、文件 Hash 摘要

Hash 文件名(vendor.f02bc2.js)是實現持久化緩存的第一步,目前 webpack 有兩種計算 hash 的方式:

  1. 計算所有 chunks 的 hash —— [hash]
  2. 為每個 chunk 計算 hash —— [chunkhash]

第一種是每次編譯生成一個唯一 hash,適合 chunk 拆分不多的小項目,但所有資源全打上同一個 hash,無法完成持久化緩存的需求。

第二種是 webpack 為每個 chunk 資源都生成與其內容相關的 hash 摘要,為不同的資源打上不同的 hash。

相關官方文檔:

  • webpack 1.x - Long term cache
  • webpack 2 - Generating unique hashes for each file - Caching

webpack 配置

JS 資源的 [chunkhash] 由 webpack 計算,Images/Fonts 的 [hash] 由webpack/file-loader 計算,提取的 CSS 的 [contenthash] 由 webpack/extract-text-webpack-plugin 計算。避免冗雜,這裏只寫出了部分 webpack 2 配置:

JavaScript
// production
output: {  
  filename: ‘[name].[chunkhash:8].bundle.js‘,
  chunkFilename: ‘[name].[chunkhash:8].js‘
},
module: {  
  rules: [{
    test: /\.(jpe?g|png|gif|svg)$/i,
    loader: ‘url-loader‘,
    options: {
      limit: 1000,
      name: ‘assets/imgs/[name].[hash:8].[ext]‘
    }
  }, {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    loader: ‘url-loader‘,
    options: {
      limit: 10000,
      name: ‘assets/fonts/[name].[hash:8].[ext]‘
    }
  }]
},
plugins: [  
  new ExtractTextPlugin(‘[name].[contenthash:8].css‘)
]

不要在開發環境使用 [chunkhash]/[hash]/[contenthash],因為不需要在開發環境做持久緩存,而且這樣會增加編譯時間,開發環境用 [name] 就可以了。

不穩定的 chunkhash

不過,只是計算 chunk MD5 摘要並修改 chunk 資源文件名是不夠的。Chunk 的生成還涉及到依賴解析和模塊 ID 分配,這是無法穩定實質上沒有變化的 chunk 文件的 chunkhash 變動問題的本源,附一個未關閉的相關 issue:

Vendor chunkhash changes when app code changes #1315

正如問題 [#1315] 描述的那樣:雖然只修改了 app.js 的代碼,但在最終的構建結果中,vendor.js 的 chunkhash 也被修改了,盡管 vendor.js 的內容沒有實質變化。

其實這個場景比較簡單,只生成了 entry 和 vendor 兩個 chunk,造成上述問題的原因有兩個:

  1. webpack runtime 中包含 chunks ID 及其對應 chunkhash 的對象,但 runtime 被集成到 vendor.js 中;
  2. entry 內容修改後,由於 webpack 的依賴收集規則導致構建產生的 entry chunk 對應的 ID 發生變化,webpack runtime 也因此被改變。

webpack runtime(webpackBootstrap)代碼不多,主要包含幾個功能:

  • 全局 webpackJsonp 方法:模塊讀取函數,用來區分模塊是否加載,並調用 __webpack_require__ 函數;
  • 私有 __webpack_require__ 方法:模塊初始化執行函數,並給執行過的模塊做標記;
  • 異步 chunk 加載函數(用 script 標簽異步加載),加載的 chunk 內容均被 webpackJsonp 包裹的,script 加載成功會直接執行。這個函數還包含了所有生成的 chunks 的路徑。在 webpack 2 中這個函數用到了 Promise,因此可能需要提供 Promise Polyfill;
  • 對 ES6 Modules 的默認導出(export default)做處理。

對於復雜項目的構建,由於模塊間互相依賴,這種問題影響更為巨大:可能只改動了一個小模塊,但在構建後,會發現所有與之直接或間接相關的 chunk 及其 chunkhash 都被更新了……這與我們期望的持久化緩存的需求不符。

解決這個問題的核心在於生成穩定的模塊 ID,避免頻繁的 chunk 內容變動。

如果你看過 #1315 的回復,可能會了解到 webpack-md5-hash 插件可以解決這個問題,甚至 webpack 2 的文檔中也提示用這個插件解決。但我可以負責任的告訴你,這個插件有缺陷……不要使用它,除非你想背黑鍋。

webpack-md5-hash 的問題

erm0l0v/webpack-md5-hash(相關源碼) 通過模塊路徑來排序 chunk 的所有依賴模塊(僅這個 chunk 中的模塊,不含被 CommonsChunkPlugin 剔除的模塊),並將這些排序後的模塊源代碼拼接,最後用 MD5 拼接後內容的 chunkhash。插件這麽做的好處是,使 chunkhash 與該 chunk 內代碼做直接關聯,讓 chunk 與其依賴的模塊 ID 無關化,無論模塊 ID 如何變化,都不會影響父 chunk 的實質內容及 chunkhash。

這個方法比較有效,但在一些情景下,會使 webpack-md5-hash 失效,使構建變得不可信:

Hash does not change when only imported modules IDs change #7。

比如一個簡單場景:有兩個入口 vendor 和 app。
當 app.js 被修改後,其 chunk ID 隨之改變,vendor.js 中 app 對應的 chunk ID 也會改變,即 vendor 內容有變動,其 chunkhash 也理應改變。但 webpack-md5-hash 是根據 chunk 內實際包含模塊而生成的 chunkhash,和僅有 ID 引用的 chunk 內容無關,vendor 只包含 app chunk ID 的引用,並不包含其代碼,所以此次構建中 vendor 的 chunkhash 並不會改變。這樣造成的結果便是:瀏覽器依然會下載舊的 vendor,直接導致發版失誤!

因此 webpack-md5-hash 並沒有解決之前的問題:

  1. 如何生成穩定的模塊ID?
  2. 如何避免頻繁的 chunk 內容變動?

我們先來解決第一個問題,第二個下一節解決。

如何生成穩定的模塊 ID?

默認,模塊的 ID 是 webpack 根據依賴的收集順序遞增的正整數,這種 ID 分配方式不太穩定,因為修改一個被依賴較多的模塊,依賴這個模塊的 chunks 內容均會跟著模塊的新 ID 一起改變,但實際上我們只想讓用戶下載有真正改動的 chunk,而不是所有依賴這個新模塊的 chunk 都重新更新。

因此 webpack (1) 默認的模塊 ID 分配不是很合適,我們需要其他工具來幫我們穩定 ID:

  1. OccurrenceOrderPlugin
    這個插件可以改變默認的 ID 決定方式,讓 webpack 以依賴模塊出現的次數決定 ID 的值,次數越多 ID 越小。在依賴項變動不大情況下,還是一個比較好的方法,但當依賴出現次數有變化時,輸出的模塊 ID 則可能會有大幅變動(級聯)。(目前 webpack 2 已經將此插件默認啟用 ??)

  2. recordsPath 配置
    它會輸出每次構建的「模塊路徑(loaders + module path)」與 ID 鍵值對 JSON,在下次構建時直接使用 JSON 中的 ID。但當修改模塊路徑或 loader 時,ID 會更新。
    同時,需要註意的是 webpack.optimize.DedupePlugin() 插件不可與 recordsPath 共存,它會改變存下來的模塊 ID。

  3. NamedModulesPlugin
    這個模塊可以將依賴模塊的正整數 ID 替換為相對路徑(如:將 4 替換為 ./node_modules/es6-promise/dist/es6-promise.js)。

    • 開發模式,它可以讓 webpack-dev-server 和 HMR 進行熱更新時在控制臺輸出模塊名字而不是純數字;
    • 生產構建環境,它可以避免因修改內容導致的 ID 變化,從而實現持久化緩存。

    但是有兩個缺點:

    • 遞增 ID 替換為模塊相對路徑,構建出來的 chunk 會充滿各種路徑,使文件增大;
    • 模塊(npm 和自己的模塊)路徑會泄露,可能導致安全問題。
  4. HashedModuleIdsPlugin
    這是 NamedModulesPlugin 的進階模塊,它在其基礎上對模塊路徑進行 MD5 摘要,不僅可以實現持久化緩存,同時還避免了它引起的兩個問題(文件增大,路徑泄露)。用 HashedModuleIdsPlugin 可以輕松地實現 chunkhash 的穩定化!

    不過這個插件只被添加到了 webpack 2 中,可能是因為 webpack 2 正式版還沒有發布,HashedModuleIdsPlugin 一直沒有文檔,所以這裏有必要指明如何使用:

    new webpack.HashedModuleIdsPlugin()
    

    如果使用了 HashedModuleIdsPlugin,NamedModulesPlugin 就不要再添加了。

    幸運的是,我們可以通過直接添加 HashedModuleIdsPlugin.js 為模塊到 webpack 1 的配置中,也能達到同樣穩定 chunkhash 的功能。

    const HashedModuleIdsPlugin = require(‘./HashedModuleIdsPlugin‘)
    // ...
    new HashedModuleIdsPlugin()
    

至此 chunkhash 已經穩定,是時候解決另一個問題了……

二、如何避免頻繁的 chunk 內容變動?

一般場景下,我們可能不需要做太多的優化,也不用追求持久化緩存,常規配置即可:

為了節省篇幅,所有配置代碼我會盡量縮減,文章最後會提供 DEMO,包含完整配置。

JavaScript
{
  entry: { entry },
  plugins: [
    new HtmlWebpackPlugin({
      chunks: [‘vendor‘, ‘entry‘]
    }),
    new webpack.optimize.CommonsChunkPlugin({
      names: ‘vendor‘,
      minChunks: Infinity
    })
  ]
}

但隨著業務需求變化,最初的單頁模式可能無法滿足需求,而且把公共模塊全部提取到 vendor 中,也無法做到較好的持久化緩存,我們需要更合理地劃分並提取公共模塊。

合理劃分公共模塊

稍大型的應用通常會包含這幾個部分:

類型公用率使用頻率更新頻率
庫和工具 vue/react/redux/whatwg-fetch 等
定制 UI 庫和工具 UI 組件/私有工具/語法 Polyfill/頁面初始化腳本等
低頻庫/工具/代碼 富文本編輯器/圖表庫/微信 JSSDK/省市 JSON 等
業務模塊 包含業務邏輯的模塊/View

根據公用/使用/更新率來做公共模塊的劃分是比較科學:

  • 庫和工具 - libs
  • 定制 UI 庫和工具 - vendor
  • 業務模塊 - entries
  • 低頻庫/工具/代碼 - 分割為 chunk

我們可通過指定模塊的入口 chunk,來直接分離模塊。以 Vue 搭建的多入口單頁應用為例:

JavaScript
{
  entry: {
    libs: [
      ‘es6-promise/auto‘,
      ‘whatwg-fetch‘,
      ‘vue‘,
      ‘vue-router‘
    ],
    vendor: [
      /*
       * vendor 中均是非 npm 模塊,
       * 用 resolve.alias 修改路徑,
       * 避免冗長的相對路徑。
       */
      ‘assets/libs/fastclick‘,
      ‘components/request‘,
      ‘components/ui‘,
      ‘components/bootstrap‘ // 初始化腳本
    ],
    page1: ‘src/pages/page1‘,
    page2: ‘src/pages/page2‘
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 省略部分配置
      template: ‘src/pages/page1/index.html‘,
      chunks: [‘libs‘, ‘vendor‘, ‘page1‘]
    }),
    new HtmlWebpackPlugin({
      template: ‘src/pages/page2/index.html‘,
      chunks: [‘libs‘, ‘vendor‘, ‘page2‘]
    })
  ]
}

多頁入口最好用腳本來掃描目錄並生成,手動添加維護性較差,可參考 multi-vue。

代碼分割

除了入口代碼的分離,我們還缺少對「低頻庫/工具/代碼」的處理,對於這類代碼最好的辦法是做代碼分割(Code Splitting),做到按需加載,進一步加速應用。

webpack 提供了幾種添加分割點的方法:

  • CommonJs: require.ensure
  • AMD: require
  • ES6 Modules (webpack 1 不支持)

添加分割點可以主動將指定的模塊分離成另一個 chunk,而不是隨當前 chunk 一起打包。對於這幾種情況處理非常好:

  • 比較大,且不常用的庫/工具,如 D3.js、Draft.js、微信 JSSDK、querystring 等;
  • 單頁應用中不常用的 router view,即某些不常訪問的介面。

CommonJs 和 AMD 添加分割點的方法就不再贅述了,詳情請查看文檔:

  • code splitting - webpack 1
  • Code Splitting - Using RequireJS - webpack 2
註意

如果你使用了 babili (babel-minify) 來壓縮你的 ES6+ 代碼,請不要使用 require.ensure/require,因為 babili 會把 require 關鍵字壓縮,導致 webpack 無法識別,造成構建問題。

import()

webpack 2 在 1.x 的基礎上增加了對 ES6 模塊(ES6 Modules)的支持,這意味著在webpack 2 環境下,import 導入模塊語法不再需要編譯為 require 了。還優化了 ES6 模塊依賴(Tree-shaking,後面會談到),並實現了 JS Loader Standard 規範定義中的 import(path) 方法。

註意

在 webpack v2.1.0-beta.28 中,System.import 方法已被廢棄,因為 System.import 不在提案中了,被 import() 代替。

由於 import() 僅僅是個語法,不涉及轉換,因此我們需要使用 babel 插件 syntax-dynamic-import 來讓 babel 可以識別這個語法。另外 import() 也依賴編譯環境,要想讓運行環境通過 import() 進行按需加載,需要額外的插件:

  • webpack 2 已支持,不需要額外插件
  • webpack 1 + babel-plugin-dynamic-import-webpack
  • node + babel-plugin-dynamic-import-node
JavaScript
const { search } = window.location  
import(‘./components/querystring.js‘)  
  .then(querystring => {
    const searchquery = querystring.parse(search)
    // ...
  })
  .catch(err => {
    Toast.error(err)
    console.error(err)
  })

配合 react-router:

React JSX
import { Router, Route, hashHistory } from ‘react-router‘  
import App from ‘./App‘

const lazyLoad = moduleName => _ =>  
  import(`./components/${moduleName}`)
    .then(module => module.default)
    .catch(err => console.error(err))

export default function Root () {  
  return (
    <Router history={hashHistory}>
      <Route path=‘/component={App}>
        <Route path=‘/homegetComponent={lazyLoad(‘Home‘)} />
        <Route path=‘/postsgetComponent={lazyLoad(‘Posts‘)}>
          <Route path=‘:idgetComponent={lazyLoad(‘Article‘)} />
        </Route>
        <Route path=‘/aboutgetComponent={lazyLoad(‘About‘)} />
      </Route>
    </Router>
  )
}

用模板字符串來動態加載模塊時,webpack 在編譯階段會把可能加載的模塊打包,並用正則匹配加載,懶加載示例代碼可見 blade254353074/react-router-lazy-import。

提取公共模塊

在上述例子中,我們劃分了公共模塊,並進行了代碼分割,下面我們要做的是:提取頻繁共用的模塊,將 webpack runtime 構建為內聯 script。

提取頻繁共用的模塊

提取公共模塊要使用 Commons-chunk-plugin,對於持久化緩存來說,我們只需要將共用的模塊打包到 libs/vendor 中即可。

模塊有兩種共用情況:

  • libs/vendor 與其他 chunk 共用的模塊,如:vue/react/moment/whatwg-fetch
  • 多個 chunks 間共用的模塊,如 page1 和 page2 共用 Header 組件

對於想把所有共用的模塊全部提取的需求,我們可以做如下配置:

JavaScript
new webpack.optimize.CommonsChunkPlugin({  
  names: [‘libs‘, ‘vendor‘].reverse()
})

用上述配置構建時,webpack 會將 webpack runtime 打包到 libs 中(names 數組末尾的 chunk),而 chunks 間共用的模塊會打包到 vendor中。

如果你不想讓僅有兩個 chunks 共用的模塊被提取到 vendor 中,而想讓 n 個 chunks 共用的模塊被提取出來時,可以借助 minChunks 實現。
minChunks 是指限定模塊被 chunks 依賴的最少次數,低於設定值(2 ≤ n ≤ chunks 總數)將不會被提取到公共 chunk 中。如果 chunks 太多,又不想讓所有公共模塊被分離到 vendor 中,可以將 minChunks 設為 Infinity,則公共 chunk 僅僅包含在 entry 中指定的模塊,而不會把其他共用的模塊提取進去。

JavaScript
new webpack.optimize.CommonsChunkPlugin({  
  names: [‘libs‘, ‘vendor‘].reverse(),
  // minChunks: 3
  minChunks: Infinity
})

CommonsChunkPlugin 似乎還是有些 Bug,當我用 vue-style-loader 時,其中的 addStyle.js 會被添加到依賴中,但在以下配置中,addStyle.js 在打包後會被 CommonsChunkPlugin 漏掉,導致無法正常運行:

new webpack.optimize.CommonsChunkPlugin({
  names: [‘libs‘, ‘vendor‘].reverse()
})
manifest (清單)

盡管我們已經劃分好了 chunks,也提取了公共的模塊,但僅改動一個模塊的代碼還是會造成 Initial chunk (libs) 的變化。原因是這個初始塊包含著 webpack runtime,而 runtime 還包含 chunks ID 及其對應 chunkhash 的對象。因此當任何 chunks 內容發生變化,webpack runtime 均會隨之改變。

技術分享圖片webpack runtime 中的 chunks 清單

正如文檔 # Manifest File - Code Splitting - Libraries中描述的那樣,我們可以通過增加一個指定的公共 chunk 來提取 runtime,從而進一步實現持久化緩存:

JavaScript
new webpack.optimize.CommonsChunkPlugin({  
  // 將 `manifest` 優先於 libs 進行提取,
  // 則可以將 webpack runtime 分離到這個塊中。
  names: [‘manifest‘, ‘libs‘, ‘vendor‘].reverse()
  // manifest 只是個有意義的名字,也可以改成其他名字。
})

manifest 只是個特定的名字(可能是包含了 chunks 清單,所以起名 manifest),如果僅僅是為了分離 webpack runtime,可以將 manifest 替換成任意你想要的名字。

這樣在我們構建之後,就會多打包一個特別小(不足 2kb)的 manifest.js,解決了 libs 經常「被」更新的問題。不過,你可能發現了一個問題 —— manifest.js 實在是太小了,以至於不值得再為一個小 js 增加資源請求數量。

這時候我們可以引入另一個插件:inline-manifest-webpack-plugin。
它可以將 manifest 轉為內聯在 html 內的 inline script,因為 manifest 經常隨著構建而變化,寫入到 html 中便不需要每次構建再下載新的 manifest 了,從而減少了一個小文件請求。此插件依賴 html-webpack-plugin 和 manifest 公共塊,因此我們要配置 HtmlWebpackPlugin 且保持 manifest 的命名:

JavaScript
{
  module: {
    rules: [{
      test: /\.ejs$/,
      loader: ‘ejs-loader‘
    }]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      names: [‘manifest‘, ‘libs‘, ‘vendor‘].reverse()
    }),
    new HtmlWebpackPlugin({
      template: ‘src/pages/page1/index.ejs‘,
      chunks: [‘manifest‘, ‘libs‘, ‘vendor‘, ‘page1‘]
    }),
    new InlineManifestWebpackPlugin()
  ]
}

EJS Template:

HTML
<!-- ejs template -->  
<!DOCTYPE html>  
<html lang="zh-CN">

<head>  
  <meta charset="UTF-8">
  <title>Template</title>
  <%= htmlWebpackPlugin.files.webpackManifest %>
</head>

<body>  
  <div id="app"></div>
</body>

</html>  

在 inline-manifest-webpack-plugin 的幫助下進行構建,最終我們的 html 便內聯了 webpack runtime 腳本,提高了頁面的加載速度:技術分享圖片內聯 manifest 的 html

提取 CSS

這篇文章主要針對 JS 資源的持久化緩存優化,關於 CSS 提取請看 webpack/extract-text-webpack-plugin。

對 chunks 做最後的優化

webpack 中對 chunks 做優化的還有這幾個插件:

  • DedupePlugin (webpack 2 已廢棄)
  • OccurrenceOrderPlugin (webpack 2 默認啟用)
  • LimitChunkCountPlugin
  • MinChunkSizePlugin
  • UglifyJsPlugin
  • AggressiveMergingPlugin
  • DllPlugin
  • DllReferencePlugin

關於 Tree Shaking

盡管 webpack 2 還未大量使用,但現在我們有一個不得不用 webpack 2 的理由 —— Tree Shaking

註意

為了避免 import x from ‘foo‘ 被 babel 轉換為 require,我們需要在 .babelrc 的 presets 配置中標明 "modules": false

JSON
{
  "presets": [
    ["latest", {
      "es2015": { "modules": false }
    }]
  ],
  "plugins": ["transform-runtime", "syntax-dynamic-import"],
  "comments": false
}

webpack 在構建過程中只會標記出未使用的 exports,並不會直接將 dead code 去掉,因為為了使工具盡量通用,webpack 被設計為:只標註未使用的 imports/exports。真正的清除死代碼工作,交給了 UglifyJS/babili 等工具。

技術分享圖片Does webpack include unused imports in the bundle or not?

UglifyJsPlugin 不僅可以將未使用的 exports 清除,還能去掉很多不必要的代碼,如無用的條件代碼、未使用的變量、不可達代碼等。

JavaScript
new webpack.optimize.UglifyJsPlugin({  
  compress: { warnings: true }
})

技術分享圖片如果打開了 UglifyJsPlugin 的 warning 功能,就可以在構建結果中看到清除的代碼警告。

因此必須在生產環境中配置 UglifyJsPlugin,並啟用 -p (production) 環境,才能真正發揮 Tree Shaking 的作用。

其他資源

  • multi-vue
  • react-router-lazy-import
  • webpack1-long-term-cache
  • webpack2-long-term-cache

引用

  • 大公司裏怎樣開發和部署前端代碼? - 張雲龍的回答 - 知乎
  • LONG TERM CACHING - webpack.github.io
  • Generating unique hashes for each file - webpack.js.org
  • Configuration - webpack.js.org
  • Migrating from v1 to v2 - webpack.js.org
  • Vendor chunkhash changes when app code changes #1315 - webpack/webpack
  • 你用 webpack 1.x 輸出的 hash 靠譜不 ? #1 - zhenyong/Blog
  • HashedModuleIdsPlugin
  • Syntax Dynamic Import - Babel
  • Manifest File - Code Splitting - Libraries - webpack.js.org
  • Long-term caching of static assets with Webpack - Medium
  • Caching Assets Long Term with Webpack - Medium
個人分類: Webpack打包工具

用 webpack 實現持久化緩存