前身 - CommonsChunkPlugin
webpack4之前,大家一直都是用CommonsChunkPlugin來做程式碼切割。那用他具體有什麼坑呢?
-
我用起來感覺最詭異的點就是每次切割之後,新生成的chunk會和被提取的chunk生成一種父子關係(生成的公共chunk為entry chunk,之前有寫過一篇文章介紹),即被提取的chunk會依賴於生成的chunk。下一次再做提取的時候,除非手動指定,只會掃描所有的entry chunk.
-
感覺CommonsChunkPlugin的表現也不是那麼穩定,之前寫了一個把node_modules裡面的module全打包到vendor chunk裡,明明minChunks屬性設定成了Infinity,線上還是出了鍋,同事在node_modules裡面裝了一個包莫名其妙地被打到了業務程式碼裡,害我出了一次事故。
SplitChunksPlugin
升級了webpack4之後,production模式下,SplitChunksPlugin外掛是預設被啟用的,預設配置如下:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
複製程式碼
這個配置是啥意思呢?老規矩,一個個引數的來看:
- chunks: 可填
async
,initial
,all
. 顧名思義,async針對非同步載入的chunk做切割,initial針對初始chunk,all針對所有chunk。 - minSize: 我們切割完要生成的新chunk要>30kb,否則不生成新chunk。
- minChunks: 共享該module的最小chunk數
- maxAsyncRequests:最多有5個非同步載入請求該module
- maxInitialRequests: 初始化的時候最多有3個請求該module
- automaticNameDelimiter:名字中間的間隔符
- name:chunk的名字,如果設成true,會根據被提取的chunk自動生成。
- cacheGroups: 這個就是重點了,我們要切割成的每一個新chunk就是一個cache group。
- test:和CommonsChunkPlugin裡的minChunks非常像,用來決定提取哪些module,可以接受字串,正規表示式,或者函式,函式的一個引數為module,第二個引數為引用這個module的chunk(陣列).
- priority:優先順序高的chunk為被優先選擇(說出來感覺好蠢),優先順序一樣的話,size大的優先被選擇
- reuseExistingChunk: 當module未變時,是否可以使用之前的chunk.
看到這裡八成是一臉懵逼,這特麼是在說啥?別急,具體來看幾個例子就清楚了:
const entry = {
index: "./src/index.jsx",
index2: './src/index2.jsx',
}
複製程式碼
有index和index2兩個entry(都引用了react和react-dom):
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
複製程式碼
import React from 'react';
import ReactDOM from 'react-dom';
console.log(React, ReactDOM);
複製程式碼
然後App.jsx長這樣:
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'
class App extends PureComponent {
render() {
return (
<div>App...</div>
)
}
}
export default hot(module)(App);
複製程式碼
根據預設配置,是不會有任何切割的,因為我們只對async程式碼做切割。先把chunks改成initial,build得到如下結果:
如圖,所有node_modules裡面的引用都被打到了vendors~index~index2~.....裡面. 現在我們再加一個group:
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
'react-vendor': {
test: (module, chunks) => /react/.test(module.context),
priority: 1,
},
複製程式碼
build得到如下結果:
因為react-vendor的優先順序高,所以react相關module都被打到了react-vendor~index~index2-.....裡面。可以看到index和index2 chunk裡都包含了fbjs 和lib包,這是因為這兩個包太小了,放到一起生成的chunk size<我們設定的30kb。我們把minSize設定成3kb,再次build:
可以看到fbjs和lib等小module被打到了vendors裡面(react相關的module之所以被打到react-vendor 而不是vendor就是因為react-vendor的優先順序高於vendor。 如果把react-vendor的優先順序改成-11,則所有所有的module都會被打到vendor,不會生成react-vendor)。
接著我們再來看一下minchunks,在index2裡我們引用moment
import React from 'react';
import ReactDOM from 'react-dom';
import moment from 'moment';
console.log(React, ReactDOM, moment);
複製程式碼
把vendor group的minchunks 設成2,覆蓋掉splitChunks裡面上面設定的1,build:
如圖,moment因為只被index2引用了一次,所以不會被提取出來.
除了這種簡單的提取,通過test我們可以拿到module的資訊(目前我只用過他的路徑)和提取chunk的資訊,然後自己進行組合來自定義生成新chunk。
vendors: {
test: (module, chunks) => {
let chunkName = '';
chunks.forEach(chunk => {
chunkName += chunk.name + ',';
})
console.log(module.context, chunkName, chunks.length);
return /node_modules/.test(module.context)
},
},
複製程式碼
另外,如果需要做持久快取的話,雖然runtime可以通過設定runtimeChunk: true
來解決,但是moduleID,ChunkID的問題還是需要HashedModuleIdsPlugin等外掛來解決(webpack實現持久快取)。
參考文獻
webpack 4: Code Splitting, chunk graph and the splitChunks optimization