You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constvisitModules=(logger,compilation,inputEntrypointsAndModules,chunkGroupInfoMap,blockConnections,blocksWithNestedBlocks,allCreatedChunkGroups)=>{/** @type {QueueItem[]} */letqueue=[];/** @type {Map<ChunkGroupInfo, Set<ChunkGroupInfo>>} */constqueueConnect=newMap();/** @type {Set<ChunkGroupInfo>} */constchunkGroupsForCombining=newSet();// Fill queue with entrypoint modules// Create ChunkGroupInfo for entrypoints//拿出之前创建的entryPoint chunkGroupfor(const[chunkGroup,modules]ofinputEntrypointsAndModules){//根据传参获取runtime 或 nameconstruntime=getEntryRuntime(compilation,chunkGroup.name,chunkGroup.options);/** @type {ChunkGroupInfo} *///创建chunkGroupInfo信息constchunkGroupInfo={//块组
chunkGroup,
runtime,//chunkGroup 可追踪的最小 module 数据集minAvailableModules: undefined,minAvailableModulesOwned: false,availableModulesToBeMerged: [],//可跳过模块skippedItems: undefined,children: undefined,availableSources: undefined,availableChildren: undefined,preOrderIndex: 0,postOrderIndex: 0,chunkLoading:
chunkGroup.options.chunkLoading!==undefined
? chunkGroup.options.chunkLoading!==false
: compilation.outputOptions.chunkLoading!==false,asyncChunks:
chunkGroup.options.asyncChunks!==undefined
? chunkGroup.options.asyncChunks
: compilation.outputOptions.asyncChunks!==false};chunkGroup.index=nextChunkGroupIndex++;//如果当前chunk依赖父级Chunk时if(chunkGroup.getNumberOfParents()>0){// minAvailableModules for child entrypoints are unknown yet, set to undefined.// This means no module is added until other sets are merged into// this minAvailableModules (by the parent entrypoints)constskippedItems=newSet();for(constmoduleofmodules){skippedItems.add(module);}//将 skippedItems 设置到当前 chunkGroupInfo.skippedItemschunkGroupInfo.skippedItems=skippedItems;//添加至 chunkGroupsForCombiningchunkGroupsForCombining.add(chunkGroupInfo);}else{// The application may start here: We start with an empty list of available moduleschunkGroupInfo.minAvailableModules=EMPTY_SET;//获取entry chunkconstchunk=chunkGroup.getEntrypointChunk();//将模块推入队列for(constmoduleofmodules){queue.push({// ADD_AND_ENTER_MODULE = 1action: ADD_AND_ENTER_MODULE,block: module,
module,
chunk,
chunkGroup,
chunkGroupInfo
});}}//设置chunkGroup对应的chunkGroupInfo信息chunkGroupInfoMap.set(chunkGroup,chunkGroupInfo);if(chunkGroup.name){//设置chunkGroup name对应的chunkGroupInfonamedChunkGroups.set(chunkGroup.name,chunkGroupInfo);}}// Fill availableSources with parent-child dependencies between entrypoints//取出 chunkGroupsForCombining 里的每个 chunkGroupInfofor(constchunkGroupInfoofchunkGroupsForCombining){const{ chunkGroup }=chunkGroupInfo;chunkGroupInfo.availableSources=newSet();for(constparentofchunkGroup.parentsIterable){//获取父 chunk 的 ChunkGroupInfo constparentChunkGroupInfo=chunkGroupInfoMap.get(parent);//将父 chunkGroupInfo 添加到子 chunkGroupInfo.availableSources 集合chunkGroupInfo.availableSources.add(parentChunkGroupInfo);if(parentChunkGroupInfo.availableChildren===undefined){parentChunkGroupInfo.availableChildren=newSet();}//将子 chunkGroupInfo 添加到父 chunkGroupInfo.availableChildren 集合parentChunkGroupInfo.availableChildren.add(chunkGroupInfo);}}// pop() is used to read from the queue// so it need to be reversed to be iterated in// correct order//将队列反转保证输出顺序正确queue.reverse();}
case PROCESS_BLOCK: {processBlock(block);break;}constprocessBlock=block=>{statProcessedBlocks++;// get prepared block info//获取同步 依赖constblockModules=getBlockModules(block,chunkGroupInfo.runtime);if(blockModules!==undefined){const{ minAvailableModules }=chunkGroupInfo;// Buffer items because order need to be reversed to get indices correct// Traverse all referenced modulesfor(leti=0;i<blockModules.length;i+=2){constrefModule=/** @type {Module} */(blockModules[i]);//如果引用的依赖已经存在于chunk里,跳过处理if(chunkGraph.isModuleInChunk(refModule,chunk)){// skip early if already connectedcontinue;}constactiveState=/** @type {ConnectionState} */(blockModules[i+1]);if(activeState!==true){skipConnectionBuffer.push([refModule,activeState]);if(activeState===false)continue;}//如果引用的依赖已经存在于父 chunk 里, 添加到 skipBufferif(activeState===true&&(minAvailableModules.has(refModule)||minAvailableModules.plus.has(refModule))){// already in parent chunks, skip it for nowskipBuffer.push(refModule);continue;}// enqueue, then add and enter to be in the correct order// this is relevant with circular dependencies//将依赖推入queueBufferqueueBuffer.push({action: activeState===true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK,block: refModule,module: refModule,
chunk,
chunkGroup,
chunkGroupInfo
});}// Add buffered items in reverse orderif(skipConnectionBuffer.length>0){let{ skippedModuleConnections }=chunkGroupInfo;if(skippedModuleConnections===undefined){chunkGroupInfo.skippedModuleConnections=skippedModuleConnections=newSet();}for(leti=skipConnectionBuffer.length-1;i>=0;i--){skippedModuleConnections.add(skipConnectionBuffer[i]);}skipConnectionBuffer.length=0;}//将所有的 skippedItems 添加到 ChunkGroupInfo.skippedItemsif(skipBuffer.length>0){let{ skippedItems }=chunkGroupInfo;if(skippedItems===undefined){chunkGroupInfo.skippedItems=skippedItems=newSet();}for(leti=skipBuffer.length-1;i>=0;i--){skippedItems.add(skipBuffer[i]);}skipBuffer.length=0;}//将queueBuffer里的同步块推入队列if(queueBuffer.length>0){for(leti=queueBuffer.length-1;i>=0;i--){queue.push(queueBuffer[i]);}queueBuffer.length=0;}}// Traverse all Blocks//迭代异步依赖for(constbofblock.blocks){iteratorBlock(b);}//如果异步依赖里还引用了其他异步依赖, 添加到 blocksWithNestedBlocks 集合if(block.blocks.length>0&&module!==block){blocksWithNestedBlocks.add(block);}};
例如在入口文件, index 引用了模块 d 和 异步模块 a ,且 a 模块 也引用了 d ,所以 a 会把 d 添加到 ChunkGroupInfo.skippedItems 来跳过打包,直接使用父模块 index 里打包的 d, 从而避免重复打包
之后就是调用 iteratorBlock 迭代处理异步依赖
constiteratorBlock=b=>{// 1. We create a chunk group with single chunk in it for this Block// but only once (blockChunkGroups map)letcgi=blockChunkGroups.get(b);/** @type {ChunkGroup} */letc;/** @type {Entrypoint} */letentrypoint;constentryOptions=b.groupOptions&&b.groupOptions.entryOptions;if(cgi===undefined){constchunkName=(b.groupOptions&&b.groupOptions.name)||b.chunkName;if(entryOptions){//...}elseif(!chunkGroupInfo.asyncChunks||!chunkGroupInfo.chunkLoading){// Just queue the block into the current chunk group//...}else{//通过chunkName获取chunkGroupInfocgi=chunkName&&namedChunkGroups.get(chunkName);//如果没有chunkGroupInfo就创建新的chunkif(!cgi){//根据异步块添加新的chunk到chunkGroupc=compilation.addChunkInGroup(b.groupOptions||b.chunkName,module,b.loc,b.request);c.index=nextChunkGroupIndex++;//创建chunkGroup信息cgi={chunkGroup: c,runtime: chunkGroupInfo.runtime,minAvailableModules: undefined,minAvailableModulesOwned: undefined,availableModulesToBeMerged: [],skippedItems: undefined,resultingAvailableModules: undefined,children: undefined,availableSources: undefined,availableChildren: undefined,preOrderIndex: 0,postOrderIndex: 0,chunkLoading: chunkGroupInfo.chunkLoading,asyncChunks: chunkGroupInfo.asyncChunks};//所有新建ChunkGroups的集合allCreatedChunkGroups.add(c);//进行相关Map的设置chunkGroupInfoMap.set(c,cgi);if(chunkName){namedChunkGroups.set(chunkName,cgi);}}else{c=cgi.chunkGroup;//isInitial用于判断当前chunk group是否在初始化页面被加载if(c.isInitial()){compilation.errors.push(newAsyncDependencyToInitialChunkError(chunkName,module,b.loc));c=chunkGroup;}//添加b的groupOptionsc.addOptions(b.groupOptions);//在chunkGroup Origin添加异步模块c.addOrigin(module,b.loc,b.request);}//添加异步块链接blockConnections.set(b,[]);}//将异步块和chunkGroupInfo添加至MapblockChunkGroups.set(b,cgi);}elseif(entryOptions){entrypoint=/** @type {Entrypoint} */(cgi.chunkGroup);}else{c=cgi.chunkGroup;}if(c!==undefined){// 2. We store the connection for the block// to connect it later if needed//保存异步块的来源chunkGroupInfo和chunkGroupblockConnections.get(b).push({originChunkGroupInfo: chunkGroupInfo,chunkGroup: c});// 3. We enqueue the chunk group info creation/updatin//将异步块对应的chunkGroupInfo加入到connectListletconnectList=queueConnect.get(chunkGroupInfo);if(connectList===undefined){connectList=newSet();queueConnect.set(chunkGroupInfo,connectList);}connectList.add(cgi);// TODO check if this really need to be done for each traversal// or if it is enough when it's queued when created// 4. We enqueue the DependenciesBlock for traversa//将异步依赖推到queueDelayed队列推迟处理queueDelayed.push({action: PROCESS_BLOCK,block: b,module: module,chunk: c.chunks[0],chunkGroup: c,chunkGroupInfo: cgi});}elseif(entrypoint!==undefined){chunkGroupInfo.chunkGroup.addAsyncEntrypoint(entrypoint);}};
case LEAVE_MODULE: {constindex=chunkGroup.getModulePostOrderIndex(module);if(index===undefined){chunkGroup.setModulePostOrderIndex(module,chunkGroupInfo.postOrderIndex++);}if(moduleGraph.setPostOrderIndexIfUnset(module,nextFreeModulePostOrderIndex)){nextFreeModulePostOrderIndex++;}break;}
constvisitModules=(//...)=>{//...while(queue.length||queueConnect.size){//...if(queueConnect.size>0){logger.time("visitModules: calculating available modules");processConnectQueue();logger.timeEnd("visitModules: calculating available modules");}//...}}
constprocessConnectQueue=()=>{// Figure out new parents for chunk groups// to get new available modules for these children//将异步模块设置到父chunkGroupInfo的子集for(const[chunkGroupInfo,targets]ofqueueConnect){// 1. Add new targets to the list of childrenif(chunkGroupInfo.children===undefined){chunkGroupInfo.children=targets;}else{for(consttargetoftargets){chunkGroupInfo.children.add(target);}}// 2. Calculate resulting available modules//计算当前chunkGroupInfo可用模块constresultingAvailableModules=calculateResultingAvailableModules(chunkGroupInfo);//...}};
首先会将之前添加的 queueConnect 取出来, chunkGroupInfo.children 表示当前块的子块 ,首次执行的时候这个 chunkGroupInfo 是 Entry GroupInfo , 在入口文件,我们引用了一个异步依赖 a ,所以 Entry GroupInfo 的子块就为 a ,如果有多个异步依赖,将会变成一个集合并将所有异步依赖添加进去
constcalculateResultingAvailableModules=chunkGroupInfo=>{if(chunkGroupInfo.resultingAvailableModules)returnchunkGroupInfo.resultingAvailableModules;constminAvailableModules=chunkGroupInfo.minAvailableModules;// Create a new Set of available modules at this point// We want to be as lazy as possible. There are multiple ways doing this:// Note that resultingAvailableModules is stored as "(a) + (b)" as it's a ModuleSetPlus// - resultingAvailableModules = (modules of chunk) + (minAvailableModules + minAvailableModules.plus)// - resultingAvailableModules = (minAvailableModules + modules of chunk) + (minAvailableModules.plus)// We choose one depending on the size of minAvailableModules vs minAvailableModules.plus//根据 minAvailableModules 大小决定 resultingAvailableModules 的计算方式letresultingAvailableModules;if(minAvailableModules.size>minAvailableModules.plus.size){// resultingAvailableModules = (modules of chunk) + (minAvailableModules + minAvailableModules.plus)//resultingAvailableModules = 空集合resultingAvailableModules=(newSet());//将所有minAvailableModules.plus的集合添加到minAvailableModulesfor(constmoduleofminAvailableModules.plus)minAvailableModules.add(module);//置空minAvailableModules.plusminAvailableModules.plus=EMPTY_SET;resultingAvailableModules.plus=minAvailableModules;chunkGroupInfo.minAvailableModulesOwned=false;}else{// resultingAvailableModules = (minAvailableModules + modules of chunk) + (minAvailableModules.plus)//resultingAvailableModules = new Set(minAvailableModules)resultingAvailableModules=(newSet(minAvailableModules));resultingAvailableModules.plus=minAvailableModules.plus;}// add the modules from the chunk group to the set//将当前chunk group的所有模块添加到集合里for(constchunkofchunkGroupInfo.chunkGroup.chunks){for(constmofchunkGraph.getChunkModulesIterable(chunk)){resultingAvailableModules.add(m);}}return(chunkGroupInfo.resultingAvailableModules=resultingAvailableModules);};
constconnectChunkGroups=(compilation,blocksWithNestedBlocks,blockConnections,chunkGroupInfoMap)=>{const{ chunkGraph }=compilation;/** * Helper function to check if all modules of a chunk are available * * @param {ChunkGroup} chunkGroup the chunkGroup to scan * @param {ModuleSetPlus} availableModules the comparator set * @returns {boolean} return true if all modules of a chunk are available */constareModulesAvailable=(chunkGroup,availableModules)=>{for(constchunkofchunkGroup.chunks){for(constmoduleofchunkGraph.getChunkModulesIterable(chunk)){if(!availableModules.has(module)&&!availableModules.plus.has(module))returnfalse;}}returntrue;};// For each edge in the basic chunk graphfor(const[block,connections]ofblockConnections){// 1. Check if connection is needed// When none of the dependencies need to be connected// we can skip all of them// It's not possible to filter each item so it doesn't create inconsistent// connections and modules can only create one version// TODO maybe decide this per runtimeif(// TODO is this needed?!blocksWithNestedBlocks.has(block)&&connections.every(({ chunkGroup, originChunkGroupInfo })=>areModulesAvailable(chunkGroup,originChunkGroupInfo.resultingAvailableModules))){continue;}// 2. Foreach edgefor(leti=0;i<connections.length;i++){const{ chunkGroup, originChunkGroupInfo }=connections[i];// 3. Connect block with chunkchunkGraph.connectBlockAndChunkGroup(block,chunkGroup);// 4. Connect chunk with parentconnectChunkGroupParentAndChild(originChunkGroupInfo.chunkGroup,chunkGroup);}}};
封装模块
上一篇讲了关于webpack构建模块做的工作,接着讲讲封装模块做了些什么。
seal
webpack在make阶段完成过后,不再接收模块,会调用compilation.seal进行模块封装。
首先会根据moduleGraph和options的配置实例化chunkGraph对象,再对应每个module,将其添加到chunkGraphForModuleMap里。
接着会调用optimizeDependencies hooks。webpack会根据mode来判断是否进行优化,比如当mode为production的时候,Optimization的配置就会生效
Terser插件也是在上面注册的,用于压缩代码,进行tree-shaking
回到compliation.seal函数, 调用optimizeDependencies hooks的位置。
optimizeDependencies hooks注册了两个插件,
SideEffectsFlagPlugin
和FlagDependencyUsagePlugin
。第一个插件用于标记模块的副作用,第二个插件用于分析导出依赖是否有被使用,在之前遍历AST的时候已经收集了导出信息,所以之后只要分析当如果有导出的依赖被使用,会创建_usedInRuntime信息加入到moduleGraph对应的exportInfoCreate Entry Chunks
回到 seal 函数,接下来需要创建入口 chunk
new Entrypoint(options)
会新建 entrypoint 实例,它扩展于 chunkGroup 类,里面包含 chunks,origin 等属性。每一个入口就是单独的一个 chunkGroup 。之后会对 entrypoint 的属性进行设置并添加至compliation.chunkGroups
集合里。connectChunkGroupAndChunk 会将 ChunkGroup 和 chunk 进行连接,将 Entry chunk 加入 entrypoint 的 chunks 里。
compliation 实例里有许多集合和 Map 用来保存chunk的相关信息。
比如 namedChunkGroups ,它用于根据 chunk name 查询对应的 ChunkGroups, 然后将对应的 chunk 加入到 ChunkGroups 里。例如,入口文件如果没有设置 chunk name,默认的 name 就为 "main",之后所有的同步依赖都会根据 "main" 找到入口块并将其打包在一起。异步的块也可以根据设置的 chunk name 选择分块或打包在一起。
比如 chunkGroups,它保存着所有 chunk 的集合,之后生成文件chunk的数量就会和 chunkGroups 的长度相同
接下来会根据entries的dependencies进行循环,取出dep根据其获取module建立chunk和module的连接信息,并添加entrypoint和module至chunkGraphInit Map里
assignDepths会遍历入口模块所有的依赖,并对其设置依赖深度。比如A模块引用了B模块,B模块引用了C模块,那么A,B,C模块对应的depth就为0,1,2。
buildChunkGraph
buildChunkGraph是构建ChunkGraph的算法,它会建立module,chunk,chunkGroup之间的关系。
buildChunkGraph分为三个步骤
visitModules
visitModules 是 chunkGraph 算法的第一步,它会遍历每个Module、Dependency 和 AsyncDepBlock,并将 Modules 和 AsyncDepBlocks 与 Chunks 连接起来
当访问 module 时: 将 module 添加到当前 Chunk ,然后访问所有 Dependencies 和 AsyncDepBlocks
访问 Dependency 时: 将会访问引用的 module
访问 AsyncDepBlock 时:创建一个新的 Chunk,或者根据该 Chunk 设置的名字去寻找是否已经创建过此块,没有就创建新的 Chunk, 有则将引用该 Chunk 的模块添加至 Chunk 的 origin 里。然后访问所有 Dependencies 和 AsyncDepBlocks
visitModules 通过使用队列迭代地工作。以前递归实现的版本会导致堆栈溢出,因此对其进行了重构
webpack 作者的对于 Chunk Graph 算法的写了一篇文章,大致解释关于该算法是怎么工作
首先 visitModules 会对之前创建的 entrypoint chunkGraph,迭代取出 chunkGroup 和 modules ,创建对应的 chunkGroupInfo 保存至 chunkGroupInfoMap 和 namedChunkGroups 里。
当 webpack 配置如下
family entry 依赖于 home entry 时,family chunkGroup 的 parent 就为 home entry
此时
chunkGroup.getNumberOfParents() > 0
, 将会走相对应的逻辑,并添加至 chunkGroupsForCombining 以后续处理最后再将 chunkGroupInfo,chunk,module 等信息添加到队列里处理,因为pop取的是最后的任务,所以为了保证顺序会 reverse,难道 webpack 作者不知道 unshift 吗? = =。
之后就是迭代队列,对之前添加至队列的任务进行逐步处理。
processQueue会根据action进行不同的处理,在之前创建chunkGroupInfo的时候,初始action的值为ADD_AND_ENTER_MODULE
ADD_AND_ENTER_MODULE Case 会判断该模块是否存在过 Chunk 里,是的话说明 connection 过了,可以退出 switch。否则就调用 connectChunkAndModule(chunk, module)
connectChunkAndModule 会新建 ChunkGraphModule 和 ChunkGraphChunk ,并在里面添加传入的 Entry chunk 和 Entry module。
接着会走到 case ENTER_MODULE
首先获取 chunkGroup 的 PreOrderIndex 索引值,没有则从0开始,该索引是自上而下的,从最上层的module开始
moduleGraph 的 PreOrderIndex 也和上面的处理一样
之后将该任务的action设置为LEAVE_MODULE并推到队列里重用
最后会走到 case PROCESS_BLOCK 并调用 processBlock 访问依赖
processBlock会获取同步的依赖,推入到 queue 下次迭代处理。
(minAvailableModules.has(refModule) || minAvailableModules.plus.has(refModule))
判断表示当前依赖已经存在父 chunk 里, 所以可以将其添加到 skippedItems。例如在入口文件,
index
引用了模块d
和 异步模块a
,且a
模块 也引用了d
,所以a
会把d
添加到 ChunkGroupInfo.skippedItems 来跳过打包,直接使用父模块index
里打包的d
, 从而避免重复打包之后就是调用 iteratorBlock 迭代处理异步依赖
iteratorBlock 会迭代所有的异步依赖,它会根据 chunkName 从 namedChunkGroups Map 获取对应的 chunkGroupInfo 信息。如果获取不到,就会调用 addChunkInGroup 新建 chunkGroup。
Dynamic Import 可以通过 Magic comments 的方式设置对应的 chunk name,所以我们可以自行设置,选择输出便于阅读的 chunk name 或者将不同的异步依赖打包在一起。
addChunkInGroup 会根据异步依赖的 chunkName 从 namedChunkGroups 获取对应 chunkGroup,如果能找到就添加并返回该 chunkGroup,否则就创建新的 chunkGroup,并对其进行相关设置
到这里 processBlock 的事情就做完了,将会 break 开始下一次循环,因为同步依赖也被添加到 queue ,所以也会取出来从 ADD_AND_ENTER_MODULE 或 PROCESS_BLOCK 步骤开始做跟前面相同的事情,直到它们的 action 为 LEAVE_MODULE
LEAVE_MODULE 用于设置 chunkGroup 和 moduleGraph 对应 module 的 PostOrderIndex
当迭代完所有的同步模块后将会回收 processQueue 栈,进行下一步的处理
processChunkGroupsForCombining 做的事情就是处理 chunkGroupsForCombining,chunkGroupsForCombining 只有在前面处理 Entry ChunkGroup 时有 dependOn 才会有相对应的集合。
processChunkGroupsForCombining 会计算父 chunk 的可用模块并 merge 到子 chunk availableModules。后面会介绍可用模块是什么。
处理完 chunkGroupsForCombining ,接着会调用 processConnectQueue 处理异步模块
首先会将之前添加的 queueConnect 取出来,
chunkGroupInfo.children
表示当前块的子块 ,首次执行的时候这个 chunkGroupInfo 是 Entry GroupInfo , 在入口文件,我们引用了一个异步依赖 a ,所以 Entry GroupInfo 的子块就为 a ,如果有多个异步依赖,将会变成一个集合并将所有异步依赖添加进去接下来会调用 resultingAvailableModules 计算当前 chunkGroup 的可用模块数量
当前 chunkGroup 可用模块个根据
minAvailableModules
和minAvailableModules.plus
的大小有两种算法minAvailableModules 为最小可用模块,是不包含模块本身
resultingAvailableModules 为计算可用模块,它等于
(minAvailableModules + modules of chunk) + (minAvailableModules.plus)
或(modules of chunk) + (minAvailableModules + minAvailableModules.plus)
当 minAvailableModules 大的时候,将 minAvailableModules.plus 集合添加至 minAvailableModules 并清空,此时
resultingAvailableModules = new set()
当 minAvailableModules 小的时候,此时
resultingAvailableModules = newSet(minAvailableModules)
,将resultingAvailableModules.plus = minAvailableModules.plus
最后再通过查找 chunkGraph 的将所有同步模块添加至 resultingAvailableModules
比如 index.js 里,一共四个依赖,三个同步依赖,一个异步的依赖。
在 Entry chunk 里, 它为最上层的依赖, 所以 minAvailableModules 为 0 。此时
resultingAvailableModules = (minAvailableModules + modules of chunk) + (minAvailableModules.plus)
,resultingAvailableModules 会把 minAvailableModules 加入到集合, 并把所有同步块添加进来,所以计算出来的可用模块一共为四个[index, b, c, d]
, 因为异步块 a 会分离出来所以并不会包含在里面在 a chunk 里, minAvailableModules 为之前的父块里的
[index, b, c, d]
,此时resultingAvailableModules = (modules of chunk) + (minAvailableModules + minAvailableModules.plus)
。它会把 minAvailableModules 添加到resultingAvailableModules.plus
再把其他同步块添加到 resultingAvailableModules。所以一共有五个可用模块resultingAvailableModules.plus: [index, b, c ,d] + resultingAvailableModules: [a]
因为 entry chunk 和 a chunk 是父子的关系,所以 a 能够用父 chunk 的依赖,这就是为什么 a chunk 里的可用模块会包含 entry chunk 里的其他依赖
计算完当前 chunkGroup 的可用模块后回到 processConnectQueue
processConnectQueue 的后半段会将 resultingAvailableModules 添加到 ConnectQueue 里每个异步 ChunkGroupInfo 的 availableModulesToBeMerged 下,并添加到 processChunkGroupsForMerging 进行处理。
processChunkGroupsForMerging 做的事情,就是合并 availableModulesToBeMerged 里的集合,得到当前 chunkGroupInfo 的 minavailableModules
假设入口
index
有c, b
两个同步依赖,c, b
都引用了异步依赖a
,a
引用了异步依赖d
,c
模块还单独引用了同步模块e
此时
chunkGroupInfo d
的 availableModulesToBeMerged 拥有两个集合,[plus: [index, c, b], d]
和[plus: [index, c, b, e], d]
最后 minavailableModules 会得到合并后的结果为
[plus: [index, c, b, e], d]
connectChunkGroups
visitModules 之后的第二个步骤就是 connectChunkGroups, connectChunkGroups 会建立异步依赖与它们的父模块的关系
connectChunkGroups 会获取所有的异步依赖,然后建立与 originChunkGroupInfo 的父子关系,originChunkGroupInfo 就是异步依赖的父 chunk
如果父 chunk 里已经包含了该模块,就跳过关系建立
例如, 入口模块
index
引用了同步依赖a
和异步依赖b
,b
引用了异步依赖a
。因为入口已经存在依赖a
了,所以b
里的异步依赖a
不需要再创建了, 所以跳过关系建立cleanupUnconnectedGroups
最后一步是调用 cleanupUnconnectedGroups,它会清理没有建立关系的 chunk,比如上面的例子,因为跳过了 connectChunkGroupParentAndChild,所以异步依赖
a
的getNumberOfParents()
为 0,会移除当前的 chunk,不额外生成单独的 chunkseal
构建完 chunkGroup 后, 之后会调用 optimizeChunks hook 处理 chunk 的优化。比如里面会有
MergeDuplicateChunksPlugin
合并重复的块,SplitChunksPlugin
进行分块处理等插件seal 在最后会调用 optimizeTree hook 的异步钩子
optimizeTree hook 回调里的第一个 hook 是 optimizeChunkModules hook,当 mode 为
production
时会往里注册一个插件叫ModuleConcatenationPlugin
,它的作用是将所有模块提升至同一个闭包环境以增加运行速度。之后会调用很多的钩子,例如给模块和 chunk 分配 ID,当构建期间没有错误时,
shouldRecord !== false
,会将模块和 chunk 信息记录到 records 里, records 会包含模块ID和模块对应的路径等信息,某些插件可以利用这些记录的信息在重构建时做持久化缓存。createModuleHashes
优化的事情都做完后,接下来就是调用 createModuleHashes 开始为模块创建 hash
The text was updated successfully, but these errors were encountered: