自Webpack4之後,代碼拆分的插件由CommonsChunkPlugin變成了SplitChunksPlugin,而且沒必要單獨引用,集成到了Webpack之中,經過配置下的optimization.splitChunks和optimization.runtimeChunk就能夠控制了。vue
前兩天研究了一下CommonsChunkPlugin插件,總結出來一條經驗,就是要理解這個插件,單純看如何配置它,是不會懂它的。node
先知道它的設計思路,再學習如何配置它。react
Webpack4的代碼拆分方案,徹底換了一個插件,想必設計思路和使用上差異會比較大,實際上也的確如此。webpack
若是是一些簡單的重複代碼的拆分,CommonsChunkPlugin是能夠勝任的。但一些複雜的場景,CommonsChunkPlugin就不行了。web
咱們的項目結構是這樣的:瀏覽器
這時候用Webpack打包此項目,使用CommonsChunkPlugin的話,會將React.js Vue.js這些庫打包到vendor.js中。緩存
這樣作的問題:bash
不是將React.js、Vue.js打包到同一個vendor Chunk中,而是Webpack經過分析,將React.js打包到一個vendor~Greeter1~Greeter2.js中,將Vue.js打包到一個vendor~Greeter3.js中,這樣分別打包公共代碼。併發
而後首屏加載的時候,只加載入口Chunk index.js。等用戶查看Greeter1.js的時候,再並行加載Chunk Greeter1.js和Chunk vendor~Greeter1~Greeter2.js。查看Greeter3.js的時候,再並行加載Chunk Greeter3.js和Chunk vendor~Greeter3.js。app
這樣,解決了上面提到的兩個問題,首屏速度和流量浪費。
SplitChunksPlugin就是能夠應付上面描述的複雜的拆分狀況,比較理想的拆分代碼。
按照上面描述的,咱們新建文件,目錄結構以下:
文件內容以下:
// index.js
// 這樣就是異步加載Greeter一、二、3 三個Chunk
import(/* webpackChunkName: "Greeter1" */'./Greeter1').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
import(/* webpackChunkName: "Greeter2" */'./Greeter2').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
import(/* webpackChunkName: "Greeter3" */'./Greeter3').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
複製代碼
// Greeter1.js / Greeter2.js
// 咱們就這樣模擬引用了React。Greeter2.js和Greeter1.js長得同樣。
import React from 'react';
console.log(React);
export const greeter = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
複製代碼
// Greeter3.js
// Greeter3中引用的是Vue
import Vue from 'vue';
console.log(Vue);
export const greeter = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
複製代碼
再來看看關鍵的Webpack配置
module.exports = {
entry: {
index: __dirname + "/app/index.js",
},
output: {
path: __dirname + "/public",//打包後的文件存放的地方
filename: "[name].js", //打包後輸出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
},
}
}
},
}
複製代碼
關鍵的就是optimization下的配置,咱們先只關注cacheGroups下的vendors配置。其中的test是分割代碼的規則,表明node_modules文件夾下的代碼都要被抽離出來。咱們運行一下Webpack,看看輸出結果:
我查看了一下vendor~Greeter1~Greeter2.js文件,裏面是React打包後的代碼。 我查看了一下vendor~Greeter3.js文件,裏面是Vue庫打包後的代碼。
再利用打包分析,看到:
這不就是咱們設想的那種理想狀況。若是是CommonsChunkPlugin,配置後,只會幫咱們打包出一個vendor.js的公共Chunk,而SplitChunksPlugin,咱們只是告訴它node_modules下的文件要抽離出來,Webpack就根據項目的引用狀況,自動分理處兩個公共Chunk vendor~Greeter1~Greeter2.js 和 vendor~Greeter3.js
解決複雜場景下的代碼拆分問題。針對異步加載中公共模塊的拆分,咱們只需設置須要被公共打包的代碼,SplitChunksPlugin就會自動幫咱們按照各異步模塊的需求,將公共的Chunk拆分紅一些小的公共Chunks。供各異步模塊使用。而且這些公共Chunks不會首屏加載,會隨着使用使用它們的異步模塊,使用時再一同並行加載。
核心思路:根據咱們給的規則拆分代碼,而後針對拆分的公共Chunk,再次拆分。
到這裏,還需一種極端狀況,就是被拆分出來的公共Chunk,太多了。Webpack的初衷是合併代碼啊,這又給拆碎了。
過多Chunk致使的問題就是瀏覽器同時須要併發請求太多的js。
一樣的SplitChunksPlugin也替咱們想到了。
咱們再來作一個實驗。
我畫一個圖說明狀況:
是否跟你預想的同樣,出現了三個公共Chunk,也就是咱們上圖畫的公共部分,分別包含了header1~3的代碼。再看更直觀的效果圖:
瀏覽器加載Greeter4.js的時候,須要同時加載default~Greeter1~Greeter4.js、default~Greeter2~Greeter4.js、default~Greeter3~Greeter4.js三個Chunk。也就是用戶看Greeter4.js時,需並行請求4個js文件。
問題就在於咱們把公共包拆的過於細,有可能會出現,加載一個異步Chunk的時候,須要同時而且請求不少的公共Chunk,這不是咱們想看到的,爲此,SplitChunksPlugin提供給咱們一個屬性maxAsyncRequests,限制最大並行請求數。
目前的最大的並行請求數是加載Greeter4.js時的4,咱們設置成3,看看什麼效果:
optimization: {
splitChunks: {
maxAsyncRequests: 3, // 在此設置
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
複製代碼
運行webpack,效果以下:
Greeter4將helper3.js打到一個Chunk裏,而後helper一、helper2單獨打包,這樣Greeter4的並行請求數等於3,符合預期。
一樣的將helper3.js一樣被Greeter3.js引用,因此也打包到了Greeter3中,形成了重複打包helper3。爲了減小而且請求數,就會致使必定程度的重複打包,咱們要作的,就是經過配置在平衡而且請求數和重複打包率上作一個平衡。
總的來講SplitChunksPlugin仍是很智能啊,咱們只是提出要求(並行請求數要小於等於3),它就會基於此條件爲咱們的進行拆包和組合包。
即便咱們不寫optimization,Webpack也會幫助咱們進行代碼拆分,至關於咱們寫了以下的配置:
splitChunks: {
chunks: "async", // 默認只處理異步chunk的配置
minSize: 30000, // 若是模塊的最小體積小於30,就不拆分它
minChunks: 1, // 模塊的最小被引用次數
maxAsyncRequests: 5, // 異步加載Chunk時的最大並行請求數
maxInitialRequests: 3, // 入口Chunk的最大並行請求數
automaticNameDelimiter: '~', // 文件名的鏈接符
name: true, // 此處寫成false,公共塊就不會是default~Greeter1~Greeter4.js了,而是0.js這樣命名Chunk。
cacheGroups: { // 緩存組,拆分Chunk的規則
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 此數越大,越優先匹配
},
default: {
minChunks: 2, // CommonsChunkPlugin的minChunks既能夠傳方法,也能夠傳數字,如今只能夠傳數字了,若是你想傳方法,用test屬性
priority: -20,
reuseExistingChunk: true // 配置項容許重用已經存在的代碼塊而不是建立一個新的代碼塊。這句我不懂,有知道的小夥伴麻煩告訴我一下
}
}
}
複製代碼
能夠看到默認配置只對異步加載的Chunk有效,緣由是配置了 chunks: "async"。
如下是默認配置的描述:
這些描述分別對應了上面哪條配置,相信你們都清楚了。若是沒有通過分析,這些描述真是讓人摸不着頭腦。
maxInitialRequests字段咱們尚未解釋,看字段名字應該是初始化時,也就是針對入口Chunk的分割吧,因而我作了以下配置:
module.exports = {
entry: {
Greeter1: __dirname + "/app/Greeter1.js",
Greeter2: __dirname + "/app/Greeter2.js",
Greeter3: __dirname + "/app/Greeter3.js",
Greeter4: __dirname + "/app/Greeter4.js",
},
output: {
path: __dirname + "/public",//打包後的文件存放的地方
filename: "[name].js", //打包後輸出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
splitChunks: {
chunks: "initial", // 默認只處理異步chunk的配置
maxInitialRequests: 3, // 一個入口最大並行請求數
cacheGroups: { // 緩存組
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
}
複製代碼
咱們來看打包效果:
我將maxInitialRequests調成5,再來看打包效果:
打包的結果,和咱們分析異步Chunk的提取策略一致,限制爲5的時候,即便是Greeter4.js的最大而且請求數纔是4,因此能夠盡情的拆包。但限制爲3的時候,Webpack就不把helper3.js單獨拆成一個公共Chunk了,而是分別打包到引用了它的Greeter4.js和Greeter3.js裏,以此來限制Greeter4這個入口Chunk被加載時,並行請求爲3。能夠說maxInitialRequests就是 針對多入口限制拆包數量的maxAsyncRequests。
說了這麼多,尚未提到拆分runtime呢。SplitChunksPlugin拆分runtime只需配置一個屬性,以下:
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: "initial", // 默認只處理異步chunk的配置
minSize: 30000, // 若是模塊的最小體積小於30,就不拆分它
minChunks: 1, // 模塊的最小被引用次數
maxAsyncRequests: 5, // 按需加載的最大並行請求數
maxInitialRequests: 5, // 一個入口最大並行請求數
automaticNameDelimiter: '~', // 文件名的鏈接符
name: true,
cacheGroups: { // 緩存組
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
複製代碼
我這裏仍是沿用上一個例子,打包結果以下:
頗有意思,針對咱們四個入口文件,分別生成了四個文件,runtime~Greeter1到4。這也符合預期,使用哪一個入口的代碼,就也加載它對應的runtime文件。
如今,咱們仍是回到最初的一個簡單的例子,結束咱們今天的研究。
不考慮異步加載模塊,只是分離業務代碼,第三方庫代碼和runtime代碼。
入口文件index.js,裏面只引用了react。 配置以下:
module.exports = {
entry: {
index: __dirname + "/app/index.js",
},
output: {
path: __dirname + "/public",//打包後的文件存放的地方
filename: "[name].js", //打包後輸出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: "initial",
automaticNameDelimiter: '~',
name: true,
cacheGroups: { // 緩存組
vendors: {
test: /[\\/]node_modules[\\/]/,
},
}
}
},
}
複製代碼
咱們看到react是第三方庫,提取到了vendors~index.js中,runtime代碼,提取到了runtime-index.js,業務代碼,就是index.js。
Webpack的官方文檔,沒有解釋的那麼清楚,對於Webpack的學習,須要多多動手,在實踐中,幫助咱們學習體會Webpack。
SplitChunksPlugin要理解起來仍是稍微複雜一點的,它的設計就是爲了搞定複雜的拆分狀況。但摸清它的原理後,發現它仍是很強大的,經過幾項配置,就能夠完成複雜狀況下的代碼拆分。