手摸手,帶你用合理的姿勢使用webpack4(下)

花褲衩發表於2018-08-07

本文作者來自 華爾街見聞技術團隊 - 花褲衩

推薦先閱讀 webpack 入門教程之後再來閱讀本文。

本文為手摸手使用 webpack4(下),主要分為兩部分:

  • 怎麼合理的運用瀏覽器快取
  • 怎麼構建可靠的持久化快取

預設分包策略

webpack 4 最大的改動就是廢除了 CommonsChunkPlugin 引入了 optimization.splitChunks

webpack 4 的Code Splitting 它最大的特點就是配置簡單,如果你的 modeproduction,那麼 webpack 4 就會自動開啟 Code Splitting

手摸手,帶你用合理的姿勢使用webpack4(下)

以下內容都會以 vue-element-admin 為例子。 線上 bundle-report

如上圖所示,在沒配置任何東西的情況下,webpack 4 就智慧的幫你做了程式碼分包。入口檔案依賴的檔案都被打包進了app.js,那些大於 30kb 的第三方包,如:echartsxlsxdropzone等都被單獨打包成了一個個獨立 bundle。

它內建的程式碼分割策略是這樣的:

  • 新的 chunk 是否被共享或者是來自 node_modules 的模組
  • 新的 chunk 體積在壓縮之前是否大於 30kb
  • 按需載入 chunk 的併發請求數量小於等於 5 個
  • 頁面初始載入時的併發請求數量小於等於 3 個

手摸手,帶你用合理的姿勢使用webpack4(下)

但有一些小的元件,如上圖:vue-count-to 在未壓縮的情況下只有 5kb,雖然它被兩個頁面共用了,但 webpack 4 預設的情況下還是會將它和那些懶載入的頁面程式碼打包到一起,並不會單獨將它拆成一個獨立的 bundle。(雖然被共用了,但因為體積沒有大於 30kb)

你可能會覺得 webpack 預設策略是不是有問題,我一個元件被多個頁面,你每個頁面都將這個元件打包進去了,豈不是會重複打包很多次這個元件?就拿vue-count-to來舉例,你可以把共用兩次以上的元件或者程式碼單獨抽出來打包成一個 bundle,但你不要忘了vue-count-to未壓縮的情況下就只有 5kb,gizp 壓縮完可能只有 1.5kb 左右,你為了共用這 1.5kb 的程式碼,卻要額外花費一次 http 請求的時間損耗,得不償失。我個人認為 webpack 目前預設的打包規則是一個比較合理的策略了。

但有些場景下這些規則可能就顯得不怎麼合理了。比如我有一個管理後臺,它大部分的頁面都是表單和 Table,我使用了一個第三方 table 元件,幾乎後臺每個頁面都需要它,但它的體積也就 15kb,不具備單獨拆包的標準,它就這樣被打包到每個頁面的 bundle 中了,這就很浪費資源了。這種情況下建議把大部分頁面能共用的元件單獨抽出來,合併成一個component-vendor.js的包(後面會介紹)。

優化沒有銀彈,不同的業務,優化的側重點是不同的。個人認為 webpack 4 預設拆包已經做得不錯了,對於大部分簡單的應用來說已經夠用了。但作為一個通用打包工具,它是不可能滿足所有的業務形態和場景的,所以接下來就需要我們自己稍微做一些優化了。

優化分包策略

就拿 vue-element-admin 來說,它是一個基於 Element-UI 的管理後臺,所以它會用到如 echartsxlsxdropzone等各種第三方外掛,同時又由於是管理後臺,所以本身自己也會寫很多共用元件,比如各種封裝好的搜尋查詢元件,共用的業務模組等等,如果按照預設的拆包規則,結果就不怎麼完美了。

如第一張圖所示,由於element-uientry入口檔案中被引入並且被大量頁面共用,所以它預設會被打包到 app.js 之中。這樣做是不合理的,因為app.js裡還含有你的router 路由宣告store 全域性狀態utils 公共函式icons 圖示等等這些全域性共用的東西。

但除了element-ui,其它這些又是平時開發中經常會修改的東西,比如我新增了一個全域性功能函式,utils檔案就會發生改變,或者我修改一個路由的 path,router檔案就變了,這些都會導致app.js的 hash 發生改變:app.1.js => app.2.js。但由於 element-uivue/react等也被打包在其中,雖然你沒改變它們,但它們的快取也會隨著app.xxx.js變化而失效了,這就非常不合理的。所以我們需要自己來優化一下快取策略。

我們現在的策略是按照體積大小、共用率、更新頻率重新劃分我們的包,使其儘可能的利用瀏覽器快取。

手摸手,帶你用合理的姿勢使用webpack4(下)

我們根據上表來重新劃分我們的程式碼就變成了這樣。

  • 基礎類庫 chunk-libs

它是構成我們專案必不可少的一些基礎類庫,比如 vue+vue-router+vuex+axios 這種標準的全家桶,它們的升級頻率都不高,但每個頁面都需要它們。(一些全域性被共用的,體積不大的第三方庫也可以放在其中:比如 nprogress、js-cookie、clipboard 等)

  • UI 元件庫

理論上 UI 元件庫也可以放入 libs 中,但這裡單獨拿出來的原因是: 它實在是比較大,不管是 Element-UI還是Ant Design gizp 壓縮完都可能要 200kb 左右,它可能比 libs 裡面所有的庫加起來還要大不少,而且 UI 元件庫的更新頻率也相對的比 libs 要更高一點。我們不時的會升級 UI 元件庫來解決一些現有的 bugs 或使用它的一些新功能。所以建議將 UI 元件庫也單獨拆成一個包。

  • 自定義元件/函式 chunk-commons

這裡的 commons 主要分為 必要非必要

必要元件是指那些專案裡必須載入它們才能正常執行的元件或者函式。比如你的路由表、全域性 state、全域性側邊欄/Header/Footer 等元件、自定義 Svg 圖示等等。這些其實就是你在入口檔案中依賴的東西,它們都會預設打包到app.js中。

非必要元件是指被大部分頁面使用,但在入口檔案 entry 中未被引入的模組。比如:一個管理後臺,你封裝了很多 select 或者 table 元件,由於它們的體積不會很大,它們都會被預設打包到到每一個懶載入頁面的 chunk 中,這樣會造成不少的浪費。你有十個頁面引用了它,就會包重複打包十次。所以應該將那些被大量共用的元件單獨打包成chunk-commons

不過還是要結合具體情況來看。一般情況下,你也可以將那些非必要元件\函式也在入口檔案 entry 中引入,和必要元件\函式一同打包到app.js之中也是沒什麼問題的。

  • 低頻元件

低頻元件和上面的共用元件 chunk-commons 最大的區別是,它們只會在一些特定業務場景下使用,比如富文字編輯器、js-xlsx前端 excel 處理庫等。一般這些庫都是第三方的且大於 30kb,所以 webpack 4 會預設打包成一個獨立的 bundle。也無需特別處理。小於 30kb 的情況下會被打包到具體使用它的頁面 bundle 中。

  • 業務程式碼

這部分就是我們平時經常寫的業務程式碼。一般都是按照頁面的劃分來打包,比如在 vue 中,使用路由懶載入的方式載入頁面 component: () => import('./Foo.vue') webpack 預設會將它打包成一個獨立的 bundle。

完整配置程式碼:

splitChunks: {
  chunks: "all",
  cacheGroups: {
    libs: {
      name: "chunk-libs",
      test: /[\\/]node_modules[\\/]/,
      priority: 10,
      chunks: "initial" // 只打包初始時依賴的第三方
    },
    elementUI: {
      name: "chunk-elementUI", // 單獨將 elementUI 拆包
      priority: 20, // 權重要大於 libs 和 app 不然會被打包進 libs 或者 app
      test: /[\\/]node_modules[\\/]element-ui[\\/]/
    },
    commons: {
      name: "chunk-commons",
      test: resolve("src/components"), // 可自定義擴充你的規則
      minChunks: 2, // 最小共用次數
      priority: 5,
      reuseExistingChunk: true
    }
  }
};
複製程式碼

手摸手,帶你用合理的姿勢使用webpack4(下)

上圖就是最終拆包結果概要,你可以 點我點我點我,線上檢視拆包結果。

這樣就能儘可能的利用了瀏覽器快取。當然這種優化還是需要因專案而異的。比如上圖中的共用元件 chunk-commons,可能打包出來發現特別大,包含了很多元件,但又不是每一個頁面或者大部分頁面需要它。很可能出現這種狀況:A 頁面只需要 chunk-commons裡面的 A 元件, 但卻要下載整個chunk-commons.js,這時候就需要考慮一下,目前的拆包策略是否合理,是否還需要chunk-commons?還是將這些元件打包到各自的 bundle 中?還是調整一下 minChunks: 2( 最小共用次數)?或者修改一下它的拆包規則?

// 或者你可以把策略改為只提取那些你註冊在全域性的元件。

- test: resolve("src/components")
+ test: resolve("src/components/global_components") //你註冊全域性元件的目錄
複製程式碼

博弈

其實優化就是一個博弈的過程,是讓 a bundle 大一點還是 b? 是讓首次載入快一點還是讓 cache 的利用率高一點? 但有一點要切記,拆包的時候不要過分的追求顆粒化,什麼都單獨的打成一個 bundle,不然你一個頁面可能需要載入十幾個.js檔案,如果你還不是HTTP/2的情況下,請求的阻塞還是很明顯的(受限於瀏覽器併發請求數)。所以還是那句話資源的載入策略並沒什麼完全的方案,都需要結合自己的專案找到最合適的拆包策略。

比如支援HTTP/2的情況下,你可以使用 webpack4.15.0 新增的 maxSize,它能將你的chunkminSize的範圍內更加合理的拆分,這樣可以更好地利用HTTP/2來進行長快取(在HTTP/2的情況下,快取策略就和之前又不太一樣了)。


Long term caching

持久化快取其實是一個老生常談的問題,前端發展到現在,快取方案已經很成熟了。簡單原理:

  • 針對 html 檔案:不開啟快取,把 html 放到自己的伺服器上,關閉伺服器的快取
  • 針對靜態的 js,css,圖片等檔案:開啟 cdn 和快取,將靜態資源上傳到 cdn 服務商,我們可以對資源開啟長期快取,因為每個資源的路徑都是獨一無二的,所以不會導致資源被覆蓋,保證線上使用者訪問的穩定性。
  • 每次釋出更新的時候,先將靜態資源(js, css, img) 傳到 cdn 服務上,然後再上傳 html 檔案,這樣既保證了老使用者能否正常訪問,又能讓新使用者看到新的頁面。

相關文章 大公司裡怎樣開發和部署前端程式碼?

所以我們現在要做的就是要讓 webpack 給靜態資源生產一個可靠的 hash,讓它能自動在合適的時候更新資源的 hash, 並且保證 hash 值的唯一性,即為每個打包後的資源生成一個獨一無二的 hash 值,只要打包內容不一樣,那麼 hash 值就不一樣。

其實 webpack 4 在持久化快取這一塊已經做得非常的不錯了,但還是有一些欠缺,下面我們將要從這幾個方面討論這個問題。

  • RuntimeChunk(manifest)
  • Module vs Chunk
  • HashedModuleIdsPlugin
  • NamedChunksPlugin

RuntimeChunk(manifest)

webpack 4 提供了 runtimeChunk 能讓我們方便的提取 manifest,以前我們需要這樣配置

new webpack.optimize.CommonsChunkPlugin({
  name: "manifest",
  minChunks: Infinity
});
複製程式碼

現在只要一行配置就可以了

{
  runtimeChunk: true;
}
複製程式碼

它的作用是將包含chunks 對映關係的 list單獨從 app.js裡提取出來,因為每一個 chunk 的 id 基本都是基於內容 hash 出來的,所以你每次改動都會影響它,如果不將它提取出來的話,等於app.js每次都會改變。快取就失效了。

單獨抽離 runtimeChunk 之後,每次打包都會生成一個runtimeChunk.xxx.js。(預設叫這名字,可自行修改)

手摸手,帶你用合理的姿勢使用webpack4(下)

優化

其實我們發現打包生成的 runtime.js非常的小,gzip 之後一般只有幾 kb,但這個檔案又經常會改變,我們每次都需要重新請求它,它的 http 耗時遠大於它的執行時間了,所以建議不要將它單獨拆包,而是將它內聯到我們的 index.html 之中(index.html 本來每次打包都會變)。

這裡我選用了 script-ext-html-webpack-plugin,主要是因為它還支援preloadprefetch,正好需要就不想再多引用一個外掛了,你完全可以使用 inline-manifest-webpack-plugin或者 assets-webpack-plugin等來實現相同的效果。

const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

// 注意一定要在HtmlWebpackPlugin之後引用
// inline 的name 和你 runtimeChunk 的 name保持一致
new ScriptExtHtmlWebpackPlugin({
  //`runtime` must same as runtimeChunk name. default is `runtime`
  inline: /runtime\..*\.js$/
});
複製程式碼

Module vs Chunk

我們經常看到xxxModuleIdsPluginxxxChunksPlugin,所以在 webpack 中 modulechunk到底是一個怎麼樣的關係呢?

  • chunk: 是指程式碼中引用的檔案(如:js、css、圖片等)會根據配置合併為一個或多個包,我們稱一個包為 chunk。
  • module: 是指將程式碼按照功能拆分,分解成離散功能塊。拆分後的程式碼塊就叫做 module。可以簡單的理解為一個 export/import 就是一個 module。

每個 chunk 包可含多個 module。 比如:

//9.xxxxxxxxx.js

//chunk id為 9 ,包含了Vc2m和JFUb兩個module
(window.webpackJsonp = window.webpackJsonp || []).push([
  [9],
  {
    Vc2m: function(e, t, l) {},
    JFUb: function(e, t, l) {}
  }
]);
複製程式碼

一個module還能跨chunk引用另一個module,比如我想在app.js裡面需要引用 chunkId13的模組2700可以這樣引用:

return n.e(13).then(n.bind(null, "27OO"));
複製程式碼

HashedModuleIdsPlugin

瞭解了 modulechunk之後,我們來研究一下 moduleId

首先要確定你的 filename 配置的是chunkhash(它與 hash 的區別可以看上篇文章)。

output: {
  path: path.join(__dirname, 'dist'),
  filename: '[name].[chunkhash].js',
}
複製程式碼

我們在入口檔案中隨便引入一個新檔案test.js

//main.js
import "./test";

//test.js
console.log("apple");
複製程式碼

我們執行npm run build,發現了一件奇怪的事情,我只是多引入了一個檔案,但發現有十幾個檔案發生了變化。這是為什麼呢?

我們隨便挑一個檔案 diff 一下,發現兩個檔案只有 module id 的不同。

手摸手,帶你用合理的姿勢使用webpack4(下)

這是因為: webpack 內部維護了一個自增的 id,每個 module 都有一個 id。所以當增加或者刪除 module 的時候,id 就會變化,導致其它檔案雖然沒有變化,但由於 id 被強佔,只能自增或者自減,導致整個 id 的順序都錯亂了。

雖然我們使用 [chunkhash] 作為輸出名,但仍然是不夠的。 因為 chunk 內部的每個 module 都有一個 id,webpack 預設使用遞增的數字作為 moduleId。 如果引入了一個新檔案或刪掉一個檔案,都可能會導致其它檔案的 moduleId 發生改變, 那這樣快取失效了。如:

手摸手,帶你用合理的姿勢使用webpack4(下)

本來是一個按序的 moduleId list,這時候我插入一個orange模組,插在第三個位置,這樣就會導致它之後的所以 module id 都依次加了 1。

這到了原因,解決方案就很簡單了。我們就不要使用一個自增的 id 就好了,這裡我們使用HashedModuleIdsPlugin

或者使用optimization.moduleIds v4.16.0 新發布,文件還沒有。檢視 原始碼發現它有naturalnamedhashedsizetotal-size。這裡我們設定為optimization.moduleIds='hash'等於HashedModuleIdsPlugin。原始碼了也寫了webpack5會優化這部分程式碼。

它的原理是使用檔案路徑的作為 id,並將它 hash 之後作為 moduleId。

手摸手,帶你用合理的姿勢使用webpack4(下)

使用了 HashedModuleIdsPlugin`,我們再對比一下發現 module id 不再是簡單的 id 了,而是一個四位 hash 過得字串(不一定都是四位的,如果重複的情況下會增加位數,保證唯一性 原始碼)。 這樣就固定住了 module id 了。

NamedModulesPlugin 和 HashedModuleIdsPlugin 原理是相同的,將檔案路徑作為 id,只不過沒有把路徑 hash 而已,適用於開發環境方便除錯。不建議在生產環境配置,因為這樣不僅會增加檔案的大小(路徑一般偶讀比較長),更重要的是為暴露你的檔案路徑。

NamedChunkPlugin

我們在固定了 module id 之後同理也需要固定一下 chunk id,不然我們增加 chunk 或者減少 chunk 的時候會和 module id 一樣,都可能會導致 chunk 的順序發生錯亂,從而讓 chunk 的快取都失效。

作者也意識到了這個問題,提供了一個叫NamedChunkPlugin的外掛,但在使用路由懶載入的情況下,你會發現NamedChunkPlugin並沒什麼用。 供了一個線上demo,可以自行測一下。這裡提就直接貼一下結果:

手摸手,帶你用合理的姿勢使用webpack4(下)

產生的原因前面也講了,使用自增 id 的情況下是不能保證你新新增或刪除 chunk 的位置的,一旦它改變了,這個順序就錯亂了,就需要重排,就會導致它之後的所有 id 都發生改變了。

接著我們 檢視原始碼 還發現它只對有 name 的 chunk 才奏效!所以我們那些非同步懶載入的頁面都是無效的。這啟不是坑爹!我們迭代業務肯定會不斷的新增刪除頁面,這豈不是每新增一個頁面都會讓之前的快取都失效?那我們之前還費這麼大力優化什麼拆包呢?

其實這是一個古老的問題了 相關 issue: Vendor chunkhash changes when app code changes 早在 2015 年就有人提了這個問題,這個問題也一直討論至今,'網友們'也提供了各種奇淫巧技,不過大部分隨著 webpack 的迭代已經不適用或者是修復了。

這裡我就結合一下 timse(webpack 第二多貢獻)寫的持久快取的文章(在 medium 上需要翻牆) 總結一下目前能解決這個問題的三種方案。

目前解決方案有三種

  • records
  • webpackChunkName
  • 自定義 nameResolver

webpack records

很多人可能連這個配置項都沒有注意過,不過早在 2015 年就已經被設計出來讓你更好的利用 cache。官方文件

要使用它配置也很簡單:

recordsPath: path.join(__dirname, "records.json");
複製程式碼

對,只要這一行程式碼就能開啟這個選項,並打包的時候會自動生成一個 JSON 檔案。它含有 webpack 的 records 記錄 - 即「用於儲存跨多次構建(across multiple builds)的模組識別符號」的資料片段。可以使用此檔案來跟蹤在每次構建之間的模組變化。

大白話就是:等於每次構建都是基於上次構建的基礎上進行的。它會先讀取你上次的 chunk 和 module id 的資訊之後再進行打包。所以這時候你再新增或者刪除 chunk,並不會導致之前所說的亂序了。

簡單看一下構建出來的 JSON 長啥樣。

{
  "modules": {
    "byIdentifier": {
      "demo/vendor.js": 0,
      "demo/vendor-two.js": 1,
      "demo/index.js": 2,
      ....
    },
    "usedIds": {
      "0": 0,
      "1": 1,
      "2": 2,
      ...
    }
  },
  "chunks": {
    "byName": {
      "vendor-two": 0,
      "vendor": 1,
      "entry": 2,
      "runtime": 3
    },
    "byBlocks": {},
    "usedIds": [
      0,
      1,
      2
  }
}
複製程式碼

我們和之前一樣,在路由裡面新增一個懶載入的頁面,打包對比後發現 id 並不會像之前那樣按照遍歷到的順序插入了,而是基於之前的 id 依次累加了。一般新增頁面都會在末尾填寫一個新 id,刪除 chunk 的話,會將原來代表 chunk 的 id,保留,但不會再使用。

手摸手,帶你用合理的姿勢使用webpack4(下)

但這個方案不被大家知曉主要原因就是維護這個records.json比較麻煩。如果你是在本地打包執行webpack的話,你只要將records.json當做普通檔案上傳到githubgitlab或其它版本控制倉庫。

但現在一般公司都會將打包放在 CI裡面,用docker打包,這時候這份records.json存在哪裡就是一個問題了。它不僅需要每次打包之前先讀取你這份 json,打包完之後它還需要再更新這份 json,並且還要找地方存貯,為了下次構建再使用。你可以存在 git 中或者找一個伺服器存,但存在什麼地其它方都感覺怪怪的。

如果你使用 Circle CI可以使用它的store_artifacts,相關教程

本人在使用了之後還是放棄了這個方案,使用成本略高。前端打包應該更加的純粹,不需要依賴太多其它的東西。

webpackChunkName

在 webpack2.4.0 版本之後可以自定義非同步 chunk 的名字了,例如:

import(/* webpackChunkName: "my-chunk-name" */ "module");
複製程式碼

我們在結合 vue 的懶載入可以這樣寫。

{
    path: '/test',
    component: () => import(/* webpackChunkName: "test" */ '@/views/test')
  },
複製程式碼

打包之後就生成了名為 test的 chunk 檔案。

手摸手,帶你用合理的姿勢使用webpack4(下)

chunk 有了 name 之後就可以解決NamedChunksPlugin沒有 name 的情況下的 bug 了。檢視打包後的程式碼我們發現 chunkId 就不再是一個簡單的自增 id 了。

不過這種寫法還是有弊端的,首先你需要手動編寫每一個 chunk 的 name,同時還需要保證它的唯一性,當頁面一多,維護起來還是很麻煩的。這就違背了程式設計師的原則:能偷懶就偷懶。

所以有什麼辦法可以自動生成一個 name 給 chunk 麼 ?檢視 webpack 原始碼我們發現了NamedChunksPlugin其實可以自定義 nameResolver 的。

自定義 nameResolver

NamedChunksPlugin支援自己寫 nameResolver 的規則的。但目前大部分相關的文章裡的自定義函式是不適合 webpack4 ,而且在結合 vue 的情況下還會報錯。

社群舊方案:

new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  return chunk.modules.map(m => path.relative(m.context, m.request)).join("_");
});
複製程式碼

適配 webpack4 和 vue 的新實現方案:

new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  return Array.from(chunk.modulesIterable, m => m.id).join("_");
});
複製程式碼

當然這個方案還是有一些弊端的因為 id 會可能很長,如果一個 chunk 依賴了很多個 module 的話,id 可能有幾十位,所以我們還需要縮短一下它的長度。我們首先將拼接起來的 id hash 以下,而且要保證 hash 的結果位數也能太長,浪費位元組,但太短又容易發生碰撞,所以最後我們我們選擇 4 位長度,並且手動用 Set 做一下碰撞校驗,發生碰撞的情況下位數加 1,直到碰撞為止。詳細程式碼如下:

const seen = new Set();
const nameLength = 4;

new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  const modules = Array.from(chunk.modulesIterable);
  if (modules.length > 1) {
    const hash = require("hash-sum");
    const joinedHash = hash(modules.map(m => m.id).join("_"));
    let len = nameLength;
    while (seen.has(joinedHash.substr(0, len))) len++;
    seen.add(joinedHash.substr(0, len));
    return `chunk-${joinedHash.substr(0, len)}`;
  } else {
    return modules[0].id;
  }
});
複製程式碼

我給 vue-cli 官方也提了一個相關 issue尤雨溪最後也採納了這個方案。 所以如果你現在下載最新 vue-cli@3上面囉嗦了半天的東西,其實都已經預設配置好了(但作者本人為了找到這個 hack 方法整整花了兩天時間 o(╥﹏╥)o)。

目前測試了一段時間沒發現有什麼問題。不過有一點不是很理解,不知道 webpack 出於什麼樣的原因,官方一直沒有修復這個問題?可能是在等 webpack5 的時候放大招吧。

總結

拆包策略:

  • 基礎類庫 chunk-libs
  • UI 元件庫 chunk-elementUI
  • 自定義共用元件/函式 chunk-commons
  • 低頻元件 chunk-eachrts/chunk-xlsx
  • 業務程式碼 lazy-loading xxxx.js

持久化快取:

  • 使用 runtimeChunk 提取 manifest,使用 script-ext-html-webpack-plugin等外掛內聯到index.html減少請求
  • 使用 HashedModuleIdsPlugin 固定 moduleId
  • 使用 NamedChunkPlugin結合自定義 nameResolver 來固定 chunkId

上述說的問題大部分在 webpack 官方文件都沒明確指出,唯一可以參考的就是這份 cache 文件,在剛更新 webpack4 的時候,我以為官方已經將 id 不能固定的問題解決了,但現實是殘酷的,結果並不理想。不過作者也在很多的 issue 中說他正在著手優化 long term caching

We plan to add another way to assign module/chunk ids for long term caching, but this is not ready to be told yet.

在 webpack 的 issue 和原始碼中也經常見到 Long term caching will be improved in webpack@5TODO webpack 5 xxxx這樣的程式碼註釋。這讓我對webpack 5很期待。真心希望webpack 5能真正的解決前面幾個問題,並且讓它更加的out-of-the-box,更加的簡單和智慧,就像webpack 4optimization.splitChunks,你基本不用做什麼,它就能很好的幫你拆分好bundle包,同時又給你非常的自由發揮空間。

展望

Whats next? 官方在這篇文章中展望了一下 webpack5 和講述了一下未來的計劃--持續改進使用者體驗、提升構建速度和效能,降低使用門檻,完善Persistent Caching等等。同時 webpack 也已經支援 Prefetching/Preloading modules,我相信之後也會有更多的網站會使用這一屬性。

同時 webpack 的團隊已經承諾會通過投票的方式來決定一些功能。比如不久前發起的投票。

手摸手,帶你用合理的姿勢使用webpack4(下)

大家可以關注 Tobias Koppers 的 twitter 進行投票。

最後還是期待一下 webpack5 和它之後的發展吧。如果沒有 webpack,也就不會有今天的前端。

其實如一開始就講的,vue 有vue-cli、react 有creat-react-app,現在新建專案基本都是基於腳手架的,很少有人從零開始寫 webpack 配置檔案的,而且一般開發中,一般程式設計師也不需要經常去修改 webpack 的配置。webpack 官方本身也在不斷完善預設配置項,相信 webpack 的配置門檻也會越來低多。

願世間再無 webpack 配置工程師。

手摸手,帶你用合理的姿勢使用webpack4(下)

擴充閱讀

相關文章