Open CommanderXL opened 4 years ago
想问下这个地方,mini-css-extract-plugin 为啥不直接再起一个 compiler,而用 childCompiler?因为只有部分勾子,运行速度理论上快?
@BUPTlhuanyu 我是这样理解的:其实这里 childCompiler
就是一个新起的 compiler
,只不过 childCompiler
和所在的原有的 compiler
之间是有一定联系的,也就是在 childCompiler
初始化阶段所 继承/复用 的相关配置流程信息。childCompiler
算是对于一个新的 compiler
的一个封装,去解决在构建过程中可能需要进行的其他的编译相关的操作。
另外就是在 webpack5 (5.32.0+) 里面提供了 importModule
的方法去简化了构建过程中要进行其他编译流程操作
感谢回复,其实是这样最近遇到一个 + child process + 异步 编译 less 的问题,我有一些 loader:
extract-xxx
thread-loader
loaderA
loaderB
loaderC
前两个都只有 pitch,第一个用到了 childCompiler,第二个用到了 child Process 开启多进程打包,这些loader之间需要传递数据。现在在处理 less 的时候遇到字体url的时候,走到自定义的处理字体的loader,这个loader其实回往 compilation 上塞数据,这个 compilation 是子还是父呢?从断点来看是子。
虽然速度优化了50%,现在还需要确定下面的一些疑点:
老哥能否指点一二?或者有啥比较好的涉及上面三个问题的源码分析的文章?webpack 源码太多😄,有点难读,想白嫖,哈哈。
@BUPTlhuanyu
vue-loader
还有这个 issue
里面提到的关于 mini-css-extract-plugin
都是这样: 一个自定义的匹配规则 -> loader pitch -> 返回一个期望的 js module -> 利用 childCompiler 新的编译构建。
childCompiler 子编译
webpack 子编译可以理解成创建了一个新的构建流程。webpack 内部的 compilation 的实例上提供了创建子编译流程的 API:createChildCompiler。
那么这个子编译流程到底和父编译流程有哪些差异呢?
通过代码我们发现在创建子编译 compiler 的过程中是过滤掉了
make
/compiler
/emit
/afterEmit
等 hooks 的触发函数的,即子编译流程相对于父编译流程来说的话不具备完整的构建流程。例如在父编译的流程开始阶段会触发 hooks.make 钩子,这样完成入口文件的添加及开始相关的编译流程,而子编译要想完成编译文件的工作的话就需要你手动的在创建子编译的时候添加入口插件(例如 SingleEntryPlugin)。父编译阶段使用 compiler 实例上的 run 方法开始进行,而子编译阶段有一个独立的 runAsChild 方法用以开始编译,其中在 runAsChild 方法的 callback 中可以看到子编译阶段是没有单独的 emitAssets 的阶段的。在子编译阶段如果需要输出文件的话,是需要挂载到父编译的 compilation.assets 上的:那么 childCompiler 子编译具体有哪些使用场景呢?在 webpack 官方的抽离 css chunk 的插件当中mini-css-extract-plugin就是使用到了 childCompiler 子编译去完成 css 的抽离工作,它主要体现了这个插件内部会提供了一个单独的 pitch loader,使用这个 pitch loader 进行样式模块(例如css/stylus/scss/less)的流程处理的拦截工作,在拦截的过程当中为每个样式模块都创建新的 childCompiler,这个 childCompiler 主要完成的工作就是专门针对这个样式模块进行编译相关的工作。可以想象的到就是每一个样式模块完成编译的工作后,都会生成一个 css chunk file。当然我们最终希望的是这些 css chunk file 最终能合并到一个 css chunk 文件当中,最后项目上线后,只需要加载少量的 css 文件。因此在 mini-css-extract-plugin 插件内部,每个样式模块通过子编译的流程后,是直接删除掉了 compilation.chunks 当中包含的所有的 file,即这些 css 模块最终不会被挂载到父编译的 assets 上,这样也不会为每个样式模块输出一个 css chunk file。这个插件等每个样式模块的子编译流程结束后,都会新建一个 css module,这个 css module 依赖类型为插件内部自己定义的,并且会作为当前正在编译的 module 依赖而被添加到当前模块当中。接下来,在父编译的 createChunkAssets 流程当中,分别触发 maniTemplate.hooks.renderManifest 和 chunkTemplate.hooks.renderManifest 的钩子的时候,会分别将 chunk 当中所包含的 css module 过滤出来,得到 css module 的集合,这样最终在输出文件的时候就会输出 css chunk 文件,这些 css chunk 文件当中就是分别包含了 css module 的集合而输出的。
PS:不过在你写插件或者 loader 的过程中,需要注意的一个地方就是一些 hooks,例如 thisCompilation 是不会被 childCompiler 继承的,因此如果有些插件注册的相关的 hooks 正好是这个,那么在你创建了 childCompiler 需要手动的调用这些插件的 apply 方法并传入 childCompiler,这样这些插件才能在 childCompiler 当中工作起来。这里也可以很明显的感受到在 compiler.js 当中触发 hooks.thisCompilation 和 hooks.compilation 2个钩子的区别。hooks.compilation 会被 childCompiler 继承,在 childCompiler 编译流程当中还会触发对应的钩子函数,而 hooks.thisCompilation 上绑定的钩子函数只适用于当前的 compiler 编译流程,如果是需要在其他的编译流程(childCompiler)当中使用的话,那么就需要手动的添加这些钩子。
主 compiler 在创建子编译的过程当中
compilation.createChildCompiler
会将主 compiler 上已经注册好的 hooks 一并在childCompiler
上注册好。例如在主 compiler 上注册了hooks.finishMake
的回调,那么在 childCompiler 编译流程当中,会触发这个hooks.finishMake
所注册好的回调。一个是钩子数量上有差异,另外就是 childCompiler 会复用主 compiler 上注册好的对应的 hooks。