// module包含了此次compilation中所有的module文件 // 在本例中,是index.js, another-module.js以及lodash.js module for (const module of compilation.modules) { // 通过module拿到所有匹配的cacheGroups // 对于index和another-module,是一个包含唯一的default cache group的单元素数组 // 对于lodash,则为包含两个元素的数组,数组元素分别对应default cache group以及defaultVendors cache group let cacheGroups = this.options.getCacheGroups(module, context); if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) { continue; } let cacheGroupIndex = 0; // 对于lodash的两次遍历,分别是default和defaultVendors cache group // 两次不同的是defaultVendors默认的minChunks为1 // 但是并不影响什么 // 因为selectedChunks和selectedChunksKey都分别是空数组和0n for (const cacheGroupSource of cacheGroups) { // 对于index,another-module经历一次遍历 // 对应cacheGroup的经过处理的default cache group const cacheGroup = this._getCacheGroup(cacheGroupSource); // 对于index和another-module,这里是一个chunk数组,且只包含一个元素 // 对于lodash,是一个chunk和set数组,大致是[set, chunk, chunk] // 其中set包含的元素也是这两个chunk // 至于原因则是createGetCombinations中的逻辑 const combs = cacheGroup.usedExports ? getCombsByUsedExports() : getCombs(); // For all combination of chunk selection for (const chunkCombination of combs) { // Break if minimum number of chunks is not reached // 对于lodash,第一次遍历的chunkCombination是包含两个chunk的set // 第二和第三次分别是index chunk和another chunk // 同理,count为1直接跳过 const count = chunkCombination instanceof Chunk ? 1 : chunkCombination.size; // 默认的minChunks为2,对于index和another-module,这里不做处理,直接进入下一个module的处理 if (count < cacheGroup.minChunks) continue; // Select chunks by configuration // 对于lodash进入这里的逻辑,因为lodash包含在两个chunk中 const { chunks: selectedChunks, key: selectedChunksKey } = getSelectedChunks(chunkCombination, cacheGroup.chunksFilter); // 第一次为空数组,直接返回 addModuleToChunksInfoMap( cacheGroup, cacheGroupIndex, selectedChunks, selectedChunksKey, module ); } cacheGroupIndex++; } }
getCombs
我们示例的usedExports没有设置,默认为false
// Prepare some values (usedExports = false) const getCombs = memoize(() => { // 获取到包含这个module的所有chunk, 是一个集合 const chunks = chunkGraph.getModuleChunksIterable(module); const chunksKey = getKey(chunks); return getCombinations(chunksKey); });
getModuleChunksIterable
chunkGraph包含_modules map,其中key为module,值为chunkGraphModule
class ChunkGraphModule { constructor() { / @type {SortableSet<Chunk>} */ this.chunks = new SortableSet(); / @type {Set<Chunk> | undefined} */ this.entryInChunks = undefined; / @type {Set<Chunk> | undefined} */ this.runtimeInChunks = undefined; / @type {RuntimeSpecMap<ModuleHashInfo>} */ this.hashes = undefined; / @type {string | number} */ this.id = null; / @type {RuntimeSpecMap<Set<string>> | undefined} */ this.runtimeRequirements = undefined; / @type {RuntimeSpecMap<string>} */ this.graphHashes = undefined; / @type {RuntimeSpecMap<string>} */ this.graphHashesWithConnections = undefined; } }
_getChunkGraphModule(module) { let cgm = this._modules.get(module); if (cgm === undefined) { cgm = new ChunkGraphModule(); this._modules.set(module, cgm); } return cgm; }
getModuleChunksIterable(module) { const cgm = this._getChunkGraphModule(module); return cgm.chunks; }
getKey
// 如果只有单个chunk,返回唯一的chunk // 否则返回一个bigInt const getKey = chunks => { const iterator = chunks[Symbol.iterator](); let result = iterator.next(); if (result.done) return ZERO; const first = result.value; result = iterator.next(); if (result.done) return first; let key = chunkIndexMap.get(first) | chunkIndexMap.get(result.value); while (!(result = iterator.next()).done) { const raw = chunkIndexMap.get(result.value); key = key ^ raw; } return key; };
getCombinations
const groupChunkSetsByCount = chunkSets => { / @type {Map<number, Array<Set<Chunk>>>} */ const chunkSetsByCount = new Map(); // for of mapIterator只遍历map的values // 也就是chunk set for (const chunksSet of chunkSets) { // 对于index和another-module,这个count都为1 // 因为分别只有一个chunk包含module const count = chunksSet.size; let array = chunkSetsByCount.get(count); if (array === undefined) { array = []; chunkSetsByCount.set(count, array); } array.push(chunksSet); } // 返回的map键为被chunk引用的次数,也就是这个module包含在多少chunk中 // 值则为chunk set array,array中的元素set中的元素个数应等同于map的键 return chunkSetsByCount; }; const getChunkSetsByCount = memoize(() => groupChunkSetsByCount( // 返回一个mapItearator对象 getChunkSetsInGraph().chunkSetsInGraph.values() ) ); const getChunkSetsInGraph = memoize(() => { / @type {Map<bigint, Set<Chunk>>} */ const chunkSetsInGraph = new Map(); / @type {Set<Chunk>} */ const singleChunkSets = new Set(); for (const module of compilation.modules) { // 是一个chunk集合 const chunks = chunkGraph.getModuleChunksIterable(module); const chunksKey = getKey(chunks); if (typeof chunksKey === "bigint") { // 对于lodash,这里在chunkSetsInGraph中设置值,值为chunks集合 if (!chunkSetsInGraph.has(chunksKey)) { chunkSetsInGraph.set(chunksKey, new Set(chunks)); } } else { // 只包含单个chunk // 对应于index和another-module singleChunkSets.add(chunksKey); } } return { chunkSetsInGraph, singleChunkSets }; }); const createGetCombinations = ( chunkSets, singleChunkSets, chunkSetsByCount ) => { / @type {Map<bigint | Chunk, (Set<Chunk> | Chunk)[]>} */ const combinationsCache = new Map(); return key => { const cacheEntry = combinationsCache.get(key); if (cacheEntry !== undefined) return cacheEntry; // 对于index和another-module,直接从这里返回,结果是一个包含chunk的数组 if (key instanceof Chunk) { const result = [key]; combinationsCache.set(key, result); return result; } // 得到index和another chunk set const chunksSet = chunkSets.get(key); / @type {(Set<Chunk> | Chunk)[]} */ // 对于lodash进入这里的逻辑 // 初始情况下数组就包含一个set const array = [chunksSet]; 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); } } } } // 后续加入index和another chunk for (const chunk of singleChunkSets) { if (chunksSet.has(chunk)) { array.push(chunk); } } combinationsCache.set(key, array); return array; }; }; const getCombinationsFactory = memoize(() => { const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph(); return createGetCombinations( chunkSetsInGraph, singleChunkSets, getChunkSetsByCount() ); }); const getCombinations = key => getCombinationsFactory()(key);
getSelectedChunks
const getSelectedChunks = (chunks, chunkFilter) => { let entry = selectedChunksCacheByChunksSet.get(chunks); if (entry === undefined) { entry = new WeakMap(); selectedChunksCacheByChunksSet.set(chunks, entry); } / @type {SelectedChunksResult} */ let entry2 = entry.get(chunkFilter); if (entry2 === undefined) { / @type {Chunk[]} */ const selectedChunks = []; if (chunks instanceof Chunk) { if (chunkFilter(chunks)) selectedChunks.push(chunks); } else { // lodash第一次遍历时chunks为set,进入这里的逻辑 // chunk分别是index chunk和another chunk // chunkFilter是chunk => !chunk.canBeInitial() // 而index chunk是initial chunk,所以不进入selectedChunks // 对于another chunk同理,因为它也是initial chunk for (const chunk of chunks) { if (chunkFilter(chunk)) selectedChunks.push(chunk); } } // selectedChunks为空数组 // key为0n entry2 = { chunks: selectedChunks, key: getKey(selectedChunks) }; entry.set(chunkFilter, entry2); } return entry2; };
addModuleToChunksInfoMap
const addModuleToChunksInfoMap = ( cacheGroup, cacheGroupIndex, selectedChunks, selectedChunksKey, module ) => { // Break if minimum number of chunks is not reached // 第一次为空数组,直接返回 if (selectedChunks.length < cacheGroup.minChunks) return; // Determine name for split chunk const name = cacheGroup.getName( module, selectedChunks, cacheGroup.key ); // Check if the name is ok const existingChunk = compilation.namedChunks.get(name); if (existingChunk) { const parentValidationKey = `${name}|${ typeof selectedChunksKey === "bigint" ? selectedChunksKey : selectedChunksKey.debugId}`; const valid = alreadyValidatedParents.get(parentValidationKey); if (valid === false) return; if (valid === undefined) { // Module can only be moved into the existing chunk if the existing chunk // is a parent of all selected chunks let isInAllParents = true; / @type {Set<ChunkGroup>} */ const queue = new Set(); for (const chunk of selectedChunks) { for (const group of chunk.groupsIterable) { queue.add(group); } } for (const group of queue) { if (existingChunk.isInGroup(group)) continue; let hasParent = false; for (const parent of group.parentsIterable) { hasParent = true; queue.add(parent); } if (!hasParent) { isInAllParents = false; } } const valid = isInAllParents; alreadyValidatedParents.set(parentValidationKey, valid); if (!valid) { if (!alreadyReportedErrors.has(name)) { alreadyReportedErrors.add(name); compilation.errors.push( new WebpackError( "SplitChunksPlugin\n" + `Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` + `Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` + "Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependOn).\n" + 'HINT: You can omit "name" to automatically create a name.\n' + "BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " + "This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" + "Remove this entrypoint and add modules to cache group's 'test' instead. " + "If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " + "See migration guide of more info." ) ); } return; } } } // Create key for maps // When it has a name we use the name as key // Otherwise we create the key from chunks and cache group key // This automatically merges equal names const key = cacheGroup.key + (name ? ` name:${name}` : ` chunks:${keyToString(selectedChunksKey)}`); // Add module to maps let info = chunksInfoMap.get(key); if (info === undefined) { chunksInfoMap.set( key, (info = { modules: new SortableSet( undefined, compareModulesByIdentifier ), cacheGroup, cacheGroupIndex, name, sizes: {}, chunks: new Set(), reuseableChunks: new Set(), chunksKeys: new Set() }) ); } const oldSize = info.modules.size; info.modules.add(module); if (info.modules.size !== oldSize) { for (const type of module.getSourceTypes()) { info.sizes[type] = (info.sizes[type] || 0) + module.size(type); } } const oldChunksKeysSize = info.chunksKeys.size; info.chunksKeys.add(selectedChunksKey); if (oldChunksKeysSize !== info.chunksKeys.size) { for (const chunk of selectedChunks) { info.chunks.add(chunk); } } };
当添加
情况有所不同,因为默认情况下,webpack只会对async chunk进行split处理,一个chunk实例有一个canBeInitial方法
canBeInitial() { for (const chunkGroup of this._groups) { if (chunkGroup.isInitial()) return true; } return false; }
class Entrypoint extends ChunkGroup constructor(entryOptions, initial = true) isInitial() { return this._initial; }
而非entrypoint实例的chunkgroup实例则直接为false,entrypoint chunkgroup根据initial属性判断是不是initial chunk。
我们在splitChunks中设置chunks: all会影响chunkFilter,从而影响selectedChunks的值,进而会把lodash分离
Webpack will automatically split chunks based on these conditions:
- New chunk can be shared OR modules are from the
node_modulesfolder- New chunk would be bigger than 20kb (before min+gz)
- Maximum number of parallel requests when loading chunks on demand would be lower or equal to 30
- Maximum number of parallel requests at initial page load would be lower or equal to 30
因为lodash位于node_modules且被两个module共享,minify和gzip之前size大于20k,且满足初始加载请求数量小于30(此时为3),需要注意的是,对于node_Modules中的module,默认情况下minChunks为1,只要被某个module引用就会被分离为一个单独的chunk。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/16095.html