webpack 4 源码主流程分析(六):构建 module(上)
初始化 module
接上文,在 resolver
函数回调里,触发 normalModuleFactory.hooks
:afterResolve
之后,回调里执行:
1 | let createdModule = this.hooks.createModule.call(result); // result 即为 resolver 返回的组合对象 data |
这里触发 normalModuleFactory.hooks
:createModule
,如果钩子里没有项目配置的自定义 module
,则使用 webpack
生成的 module
。
得到 module
实例,接着触发 normalModuleFactory.hooks
:module
之后,跳出 factory
函数,执行 factory
函数回调进行依赖缓存后,跳出 create
函数执行 moduleFactory.create
的回调。回调里执行:
1 | const addModuleResult = this.addModule(module); // 将这个 `module` 保存到全局的 `Compilation.modules` 数组中和 `_modules` 对象中,判断`_modules`是否有该 module 来设置是否已加载的标识 |
然后调用 this.buildModule
进入 build
阶段。该方法做了回调缓存后,触发 compilation.hooks
:buildModule
,然后执行 module.build()
。
构建 module
在 /node_modules/webpack/lib/NormalModule.js
文件里执行 module.build
, 设置一些属性后,直接调用了 this.doBuild
。
该方法里先执行了 this.createLoaderContext
得到loaderContext
,为所有的 loader
提供上下文环境并共享,然后调用了 runLoaders
:
1 | runLoaders( |
loader-runner
该方法来自 loader-runner
,通过各种 loader
处理源码后,得到一个处理后的 string
或 buffer
(可能还有个 sourcemap
)。
还可以解析自定义 loader
编写一个 loader。
主要流程为:
runLoaders
-> iteratePitchingLoaders(按正序 require 每个 loader)
-> loadLoader(对应的 loader 导出的函数赋值到 loaderContext.loader[].normal、pitch函数赋值到loaderContext.loader[].pitch,然后执行pitch函数(如果有的话))
-> processResource(转换 buffer 和设置 loaderIndex)
-> iterateNormalLoaders(倒序执行所有 loader)
-> runSyncOrAsync(同步或者异步执行 loader)
pitch 函数
每个
loader
可以挂载一个pitch
函数,该函数主要是用于利用module
的request
,来提前做一些拦截处理的工作,并不实际处理module
内容文档。正序
require
loader
并执行其pitch
方法(loadLoader
里),在执行后的回调里,如果有除了err
的参数还有其他参数,则执行iterateNormalLoaders
越过剩下的未require
的loader
直接进入到执行loader
的步骤。如果想没有其他参数,则执行iteratePitchingLoaders
进行下个loader
的require
。如代码所示:1
2
3
4
5
6if (args.length > 0) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}倒序执行每个
loader
的normal
方法 。
核心代码解析
1 | // 该方法按正序 require 每个 loader |
本章小结
- 实例化
NormalModule
得到初始化的module
(方法链:moduleFactory.create 回调里->buildModule->module.build->module.doBuild->runLoaders
),然后在build
过程中先run loader
处理源码,得到一个编译后的字符串或buffer
。 - 在
run loader
的过程中,先正序执行了每个loader
的pitch
,然后倒序执行了每个loader
的normal
。