在学习这一节要了解一些词的意思:
moduleGraph:记录了模块和模块之间的关系,比如谁依赖谁、导出什么内容、前置执行顺序、后置执行顺序等等。
chunkGraph:记录了哪些chunk里有哪些module,以及哪些module被哪些chunk引用了
entrypoint:记录了入口点相关信息,包括来自哪个模块,options等
为了简化分析流程:没有引入其它模块:
进入seal阶段
设置模块和chunk之间的联系,哪个模块对应哪个chunk,所谓的chunk就是相当于编译的入口文件。module就是属于chunk,一个chunk对应一个或多个module。
这里还会获取当前入口去新增chunk:
然后执行buildChunkGraph:
在该函数中主要执行visitModules:
其中,最重要的就是processQueue方法:
第一步建立chunk和模块之间的关系,那么是怎么建立的呢?
首先根据模块去找到该模块映射的chunks,然后将目标chunk添加进入,再根据chunk找到该chunk下的modules,并将目标module添加进去。
第二步就是设置模块的执行顺序和设置模块的前置顺序:
设置模块的执行顺序
设置模块的前置顺序
执行完visitModules建立了模块和chunk之间的联系,接下来开始:
如果没有异步依赖就跳过这个过程。执行完buildGraph后:
createModuleHashes方法主要执行下图方法生成digest:
随后执行codeGeneration生成文件:
该方法首先获取该模块的chunk的hash值,并放入jobs中:
主要执行下图方法:
该方法会先获取缓存,如果没有缓存则会调用module.codeGeneration方法生成代码:
然后执行NormalModule中的codeGeneration方法生成code:
这里主要执行sourceModule:
在sourceDependency函数中会根据该依赖的构造函数去获取生成依赖的template,执行template的apply方法。比如import {a} from ‘b.js’,生成变量a的dependency 就是HarmonyExportSpecifierDependency。然后执行template的apply方法:
HarmonyExportSpecifierDependency的apply内容:
这里先执行getExportsInfo方法获取当前模块的exports,再执行getUsedName方法获取是否被使用:
在这里我们可以看到是否使用的标志是_usedInRuntime:
那么这个_usedInRuntime值是什么时候去设置的呢?在所有模块都编译完毕后,会触发 compilation.hooks.finishModules 钩子,然后在FlagDependencyExportsPlugin插件里面根据依赖去设置ModuleGraph的ModuleGraphConnection的exports信息。最后在FlagDependencyUsagePlugin插件中设置_usedInRuntime的值,我们重点看FlagDependencyUsagePlugin插件中执行过程。首先根据入口文件依赖进入processEntryDependency:
继续将当前模块入队列:
在processModule函数中或判断当前模块是否引入其它模块:
继续往下执行,这里会保存引入的模块和变量到map当中:
继续遍历map:
继续执行prcessReferencedModule函数,我们只看最关键的一段代码setUsedConditionally:
在该函数中会设置变量的_usedInRuntime值:
看到这里的小伙伴可能又有疑惑了,也没有看到什么其他的操作啊,dependency一开始就是确定的。那么依赖是怎么确定的?依赖的确定是通过传入ast去执行blockPreWalkStatements查找当前模块引入的依赖,通过walkStatements查找出自身的导出模块。
在这里会找到当前模块的ModuleGraphModule的exports信息去判断是否被用到:
如果没有被用到那么会添加HarmonyExportInitFragment,HarmonyExportInitFragment会根据未使用的变量在打包后的内容新增unused harmony exports信息。
HarmonyExportInitFragment的getContent方法:
执行完当前模块的所有依赖就会对运行时依赖执行sourceDependency方法:
最后对依赖块执行sourceDependency方法,一个blocks包括多个block,一个block包含多个依赖:
执行完sourceModule方法后开始执行addToSource方法将fragments内容放入一个数组,该数组是即将打包输出的内容,ReplaceSource会根据loc去替换原来的source:
addToSource主要循环initFragments并生成代码然后添加到source:
这里举例说明一下怎么替换的:
替换前:
根据fragments生成的内容:
打包生成的内容:
可以看出,打包后的内容是fragment里的children的内容。这里我们再分析下HarmonyImportSpecifierDependency的apply方法:
可以看到HarmonyImportSpecifierDependency会将变量进行替换。其他的dependency的作用其实都是类似的,都起到对某个片段替换的功能。
拿到所有result后经过一系列的缓存,然后进入codeGeneration回调:
emitAssets方法主要执行下图方法:
确定了输出路径,开始生成文件夹,生成文件夹后执行回调emitFiles:
此时执行完该方法就已经生成了testxx.js文件:
总结: