最近在学习webpack代码分割时,发现网上很多资料讲的都是splitChunk是如何使用的。本文章主要从splitChunk源码分析,学习splitChunk设计思想。
首先,让我们看一个实例:
1、实例
打包后的结果
那么这里就有一个问题:为什么不是vendors~demo~index~search、vendors~demo~index、vendors~index~search?
答案:lib-flexible<30k
那么我们把minSize = 0
optimization: {
splitChunks: {
chunks: "all",
minSize: 0, // 默认是30kb,minSize设置为0之后
}
},
此时打包后的结果
1、哎,此时的结果为什么是vendors-search.js,而不是vendors~index~search.js?2、vendors? 这个是从那里来的?
答案:
1、默认情况下maxInitialRequests=3,index页面已经有(vendors~demo~index~search.js、vendors~demo~index.js、index.js)三个请求,所以针对index页面来说不能再分割了。
2、webpack会设置默认cacheGroup,这个vendors来自于这个默认cacheGroup中【下面阅读源码时,会详细讲这部分】
现在进入今天的主题:源码的分析
2、源码
先看下整体的流程图:
第一步
给每个chunk添加一个id
for (const chunk of chunks) {
indexMap.set(chunk, index++);
}
第二步
循环module,建立chunk集和:chunkSetsInGraph
for (const module of compilation.modules) {
const chunksKey = getKey(module.chunksIterable);
/* *********************建立chunkSetsInGraph,比如****************
demo:vue、axios
index:vue、axios、lib-flexible
search:vue、lib-flexible
打包顺序demo->index->search,对应的indexMap的id是1、2、3
chunkSetsInGraph:
1->demo
1、2->axios
1、2、3->vue
2->index
2、3->lib-flexible
3->search
*/
if (!chunkSetsInGraph.has(chunksKey)) {
chunkSetsInGraph.set(chunksKey, new Set(module.chunksIterable));
}
}
第三步
循环module,创建可能被分割的chunk集和chunksInfoMap。
这个的chunk集和非常重要,是代码分割的开端。
1、先看下上述实例中,生成的chunk集合和chunksInfoMap是什么样子的?
问题:1、模块集合中default和vendors是怎么来的?
2、chunk集合怎么来的?
3、模块集合chunksInfoMap是怎么生成的?
答案:
1、如果我们在webpack.config.js中不设置cacheGroup,webpack会默认为我们设置cacheGroup。其中像index、demo、search默认设置只有一个default这个cacheGroup。而axios、vue、lib-flexible这种被多个页面引用的模块,会多一个vendors这样的cacheGroup。
2、chunk集合生成
循环模块,根据每个模块所依赖的chunk,得到chunksKey,然后根据getCombinations()这个方法获取combs。
for (const module of compilation.modules) {
const chunksKey = getKey(module.chunksIterable);
let combs = combinationsCache.get(chunksKey);
if (combs === undefined) {
// *****************创建chunk子集合****************
combs = getCombinations(chunksKey);
combinationsCache.set(chunksKey, combs);
}
}
分析:getCombinations
const getCombinations = key => {
const chunksSet = chunkSetsInGraph.get(key);
var array = [chunksSet];
if (chunksSet.size > 1) {
for (const [count, setArray] of chunkSetsByCount) {
// "equal" is not needed because they would have been merge in the first step
if (count < chunksSet.size) {
for (const set of setArray) {
if (isSubset(chunksSet, set)) {
array.push(set);
}
}
}
}
}
return array;
};
这段代码做了什么?让我们用例子分析:
我们的chunkSetsInGraph是下面子集的map:
那么针对vue【1,2,3】来说,在chunkSetsInGraph中包含的子集就是
那么针对axios【1,2】来说,在chunkSetsInGraph中包含的子集就是
那么针对lib-flexible【2,3】来说,在chunkSetsInGraph中包含的子集就是
同样的道理: 我们的模块对应的chunk子集是:
3、chunksInfoMap生成?
for (const module of compilation.modules) {
// Prepare some values
const chunksKey = getKey(module.chunksIterable);
let combs = combinationsCache.get(chunksKey);
// chunksKey:1、2
// console.log('----chunksKey------',chunksKey);
if (combs === undefined) {
// *****************创建chunk子集合****************
combs = getCombinations(chunksKey);
// console.log('----combs------',combs);
combinationsCache.set(chunksKey, combs);
}
for (const cacheGroupSource of cacheGroups) {
// ....
// For all combination of chunk selection
for (const chunkCombination of combs) {
// *******************忽略小于minChunks的chunkCombination
if (chunkCombination.size < cacheGroup.minChunks) continue;
// Select chunks by configuration
const {
chunks: selectedChunks,
key: selectedChunksKey
} = getSelectedChunks(chunkCombination, cacheGroup.chunksFilter);
// *******************模块与chunk之间的映射
addModuleToChunksInfoMap(cacheGroup, selectedChunks, selectedChunksKey, module);
}
}
}
循环每个module的cacheGroups,并在循环中循环上一步骤得到的chunk子集combs。通过addModuleToChunksInfoMap,添加 module。
addModuleToChunksInfoMap代码:
const addModuleToChunksInfoMap = (
cacheGroup,
selectedChunks,
selectedChunksKey,
module
) => {
if (selectedChunks.length < cacheGroup.minChunks) return;
// Determine name for split chunk
const name = cacheGroup.getName(
module,
selectedChunks,
cacheGroup.key
);
const key =
cacheGroup.key +
(name ? ` name:${name}` : ` chunks:${selectedChunksKey}`);
// Add module to maps
let info = chunksInfoMap.get(key);
// console.log('---------preview-info-------------',info);
if (info === undefined) {
chunksInfoMap.set(
key,
(info = {
modules: new SortableSet(undefined, sortByIdentifier),
cacheGroup,
name,
validateSize: cacheGroup.minSize > 0,
size: 0,
chunks: new Set(),
reuseableChunks: new Set(),
chunksKeys: new Set()
})
);
}
// console.log('----------chunksInfoMap------------',chunksInfoMap.get(key));
info.modules.add(module);
if (info.validateSize) {
info.size += module.size();
}
if (!info.chunksKeys.has(selectedChunksKey)) {
info.chunksKeys.add(selectedChunksKey);
for (const chunk of selectedChunks) {
info.chunks.add(chunk);
}
}
// console.log('---------last-info------------',info);
};
看看我们的chunksInfoMap长什么样?
2、回到第一张图,合并相同的key
后一个集合与前一个集合的区别?比如:后一个vendors~search包含lib-flexible+vue 前一个:仅仅只有vue
最终集合:chunksInfoMap
第四步
我们不可能把上面的chunksInfo全都转换成chunk,优化chunksInfoMap,将满足条件的抽离出来,转化成最终生成的chunk文件。
1、怎么优化呢?
循环chunksInfoMap,找出最优bestEntryKey[就是chunksInfoMap的key],直至chunksInfoMap为空。
1、如何找出bestEntryKey?
根据上述条件,找到的第一个bestEntryKey是: vendors name:vendors~demo~index~search
分析: vendors的priority>default的priority,排除default开头的,然后vendors~demo~index~search的chunk个数是3,最大。所以最优。2、校验bestEntryKey,通过每个chunk的maxRequest:
也就是我们配置的maxInitialRequests
此时我们设定的三个chunk对应的请求文件是:
vendors name:vendors~demo~index~search
那么,请求数是:
demo: 1 index:1 search:1
3、当前的chunksInfoMap是多少?
去掉最优集合bestEntryKey所对应的module之后 循环chunksInfoMap,将其每一项剩下的module的size与minSize对比 把<minSize的项去掉
endors name:vendors~demo~index~search 对应的module是vue
(1)去掉vue
(2)剩下的module.size与minSize比较此刻 chunksInfomap只剩下四个
接下来进行第二次循环:
此刻的chunksInfoMap: 至此:chunsInfoMap已经为空了,最后结果
补充:
当bestEntryKey校验失败时