程式碼均放在git倉庫
Webpack 4給我們帶來了一些改變。包括更快的打包速度,引入了SplitChunksPlugin外掛來取代(之前版本里的)CommonsChunksPlugin外掛。在這篇文章中,你將學習如何分割你的輸出程式碼,從而提升我們應用的效能。
SplitChunks外掛(webpack 4.x以前使用CommonsChunkPlugin
)允許我們將公共依賴項提取到現有的entry chunk
或全新的程式碼塊中。
程式碼分割的理念
首先搞明白: webpack裡的程式碼分割是個什麼鬼? 它允許你將一個檔案分割成多個檔案。如果使用的好,它能大幅提升你的應用的效能。其原因是基於瀏覽器會快取你的程式碼這一事實。每當你對某一檔案做點改變,訪問你站點的人們就要重新下載它。然而依賴卻很少變動。如果你將(這些依賴)分離成單獨的檔案,訪問者就無需多次重複下載它們了。
使用webpack生成一個或多個包含你原始碼最終版本的“打包好的檔案”(bundles),(概念上我們當作)它們由(一個一個的)chunks組成。
首先 webpack 總共提供了三種辦法來實現 Code Splitting,如下:
- 入口配置:entry 入口使用多個入口檔案;
- 抽取公有程式碼:使用 SplitChunks 抽取公有程式碼;
- 動態載入 :動態載入一些程式碼。
這裡我們姑且只討論使用 SplitChunks 抽取公有程式碼。
splitChunks配置
在src目錄下建立三個檔案pageA.js、pageB.js和pageC.js。程式碼詳情見文章開頭git倉庫。
// src/pageA.js
var react = require('react');
var reactDom = require('react-dom');
var utility1 = require('../utils/utility1');
var utility2 = require('../utils/utility2');
new Vue();
module.exports = "pageA";
複製程式碼
// src/pageB.js
var react = require('react');
var reactDom = require('react-dom');
var utility2 = require('../utils/utility2');
var utility3 = require('../utils/utility3');
module.exports = "pageB";
複製程式碼
// src/pageC.js
var react = require('react');
var reactDom = require('react-dom');
var utility2 = require('../utils/utility2');
var utility3 = require('../utils/utility3');
module.exports = "pageC";
複製程式碼
入口檔案 && 出口檔案
entry: {
pageA: "./src/pageA", // 引用utility1.js utility2.js
pageB: "./src/pageB", // 引用utility2.js utility3.js
pageC: "./src/pageC", // 引用utility2.js utility3.js
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name].[hash:8].bundle.js"
},
複製程式碼
配置optimization
首先我們配置optimization如下:
optimization: {
splitChunks: {
chunks: "all",
},
複製程式碼
執行npm run build打包命令之後,檢視dist目錄
可以發現,打包出來的除了三個page檔案,還存在一個vendors~pageA~pageB~pageC.[hash].bundle.js檔案(此檔案中儲存了pageA、pageB、pageC和node_modules中共有的size大於30KB的檔案)。事實上這全靠了配置中本身預設固有一個cacheGroups的配置項:
splitChunks: {
chunks: "all",
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配node_modules目錄下的檔案
priority: -10 // 優先順序配置項
},
default: {
minChunks: 2,
priority: -20, // 優先順序配置項
reuseExistingChunk: true
}
}
}
複製程式碼
在預設設定中,
- 會將 node_mudules 資料夾中的模組打包進一個叫 vendors的bundle中,
- 所有引用超過兩次的模組分配到 default bundle 中 更可以通過 priority 來設定優先順序。
引數說明如下:
- chunks:表示從哪些chunks裡面抽取程式碼,除了三個可選字串值 initial、async、all 之外,還可以通過函式來過濾所需的 chunks;
- minSize:表示抽取出來的檔案在壓縮前的最小大小,預設為 30000;
- maxSize:表示抽取出來的檔案在壓縮前的最大大小,預設為 0,表示不限制最大大小;
- minChunks:表示被引用次數,預設為1;上述配置commons中minChunks為2,表示將被多次引用的程式碼抽離成commons。
值得注意的是,如果沒有修改minSize屬性的話,而且被公用的程式碼(假設是utilities.js)size小於30KB的話,它就不會分割成一個單獨的檔案。在真實情形下,這是合理的,因為(如分割)並不能帶來效能確實的提升,反而使得瀏覽器多了一次對utilities.js的請求,而這個utilities.js又是如此之小(不划算)。
- maxAsyncRequests:最大的按需(非同步)載入次數,預設為 5;
- maxInitialRequests:最大的初始化載入次數,預設為 3;
- automaticNameDelimiter:抽取出來的檔案的自動生成名字的分割符,預設為 ~;
- name:抽取出來檔案的名字,預設為 true,表示自動生成檔名;
- cacheGroups: 快取組。(這才是配置的關鍵)
快取組會繼承splitChunks的配置,但是test、priorty和reuseExistingChunk只能用於配置快取組。cacheGroups是一個物件,按上述介紹的鍵值對方式來配置即可,值代表對應的選項。除此之外,所有上面列出的選擇都是可以用在快取組裡的:chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, name。可以通過optimization.splitChunks.cacheGroups.default: false禁用default快取組。預設快取組的優先順序(priotity)是負數,因此所有自定義快取組都可以有比它更高優先順序(譯註:更高優先順序的快取組可以優先打包所選擇的模組)(預設自定義快取組優先順序為0)
現在我們再重新來看一下pageA、pageB、pageC三個js檔案,這三個檔案中都引入了utility2.js檔案,但是此檔案size很明顯小於30KB,所以這部分公用程式碼並沒有分割出來。如果想要分割出來很簡單,只需要:
optimization: {
splitChunks: {
chunks: "all",
minSize: 0
}
},
複製程式碼
執行npm run build打包命令之後,檢視dist目錄
顯然多了一個pageA~pageB~pageC.[hash].bundle.js檔案。檢視檔案可得知此檔案中儲存了utility2.js中的程式碼。如下圖所示(藉助於webpack-bundle-analyzer外掛,詳情文章末尾附錄)。
上圖可以看出,React相關程式碼均放在了vendors~pageA~pageB~pageC.[hash].bundle.js檔案中,如果我們想要抽離出React程式碼,應該怎麼做吶?
splitChunks: {
chunks: "all",
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2,
name: "commons",
maxInitialRequests: 5,
minSize: 0, // 預設是30kb,minSize設定為0之後
// 多次引用的utility1.js和utility2.js會被壓縮到commons中
},
reactBase: {
test: (module) => {
return /react|redux|prop-types/.test(module.context);
}, // 直接使用 test 來做路徑匹配,抽離react相關程式碼
chunks: "initial",
name: "reactBase",
priority: 10,
}
}
},
複製程式碼
run build之後如下圖所示。
附錄
我們再安裝一個 webpack-bundle-analyzer,這個外掛會清晰的展示出打包後的各個bundle所依賴的模組:
npm i webpack-bundle-analyzer -D
複製程式碼
引入:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
複製程式碼
使用,在plugins陣列中新增即可:
new BundleAnalyzerPlugin()
複製程式碼