# 源码解析五
# doBuild
上章我们通过一些方法得到 module 对象,并且触发 moduleFactory.create 的回调。
// 将这个 `module` 保存到全局的 `Compilation.modules` 数组中和 `_modules` 对象中,判断`_modules`是否有该 module 来设置是否已加载的标识
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
// 如果是入口文件还会将 modules 保存到 `Compilation.entries`
onModule(module);
dependency.module = module;
// 添加该 `module` 被哪些模块依赖
module.addReason(null, dependency);
然后调用 this.buildModule 进入 build 阶段。该方法做了回调缓存后,触发 compilation.hooks:buildModule,然后执行 module.build()。
class NormalModule extends Module {
// ...
build(options, compilation, resolver, fs, callback) {
return this.doBuild(options, compilation, resolver, fs, err => {
})
}
}
在 执行 build 方法的时候,又去调用了 this.doBuild,再 doBuild 里面又去调用 runLoaders
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
},
(err, result) => {
//...
}
);
可以看到该方法 require(loader-runner) 通过各种 loader 处理源码后,得到一个处理后的 string 或 buffer(可能还有个 sourcemap)。
主要流程为:
runLoaders -> iteratePitchingLoaders(按正序 require 每个 loader) -> loadLoader(对应的 loader 导出的函数赋值到 loaderContext.loader[].normal、pitch函数赋值到loaderContext.loader[].pitch,然后执行pitch函数(如果有的话)) -> processResource(转换 buffer 和设置 loaderIndex) -> iterateNormalLoaders(倒序执行所有 loader)-> runSyncOrAsync(同步或者异步执行 loader)
以上流程摘自 loader运行流程原谅我,我是真看不动了。。。。
# parse
从源码可以看到执行完 runLoaders 后在回调方法里面执行了 createSource 创建 _source
this._source = this.createSource(
this.binary ? asBuffer(source) : asString(source),
resourceBuffer,
sourceMap
);
this._sourceSize = null;
this._ast =
typeof extraInfo === "object" &&
extraInfo !== null &&
extraInfo.webpackAST !== undefined
? extraInfo.webpackAST
: null;
return callback();
看这段代码应该就是判断 loader 的 result 是否有第三个参数对象并且里面存在 webpackAST 属性,如果有则为 ast 赋值到 _ast 上。 然后在其 build 的回调里面执行了如下:
try {
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options
},
(err, result) => {
if (err) {
handleParseError(err);
} else {
handleParseResult(result);
}
}
);
现在 再去 webpack/lib/Parser.js 执行 Parser.parse。
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
Parser.parse 执行的为 Parser 静态方法,该方法里主要执行:
const acorn = require("acorn");
const acornParser = acorn.Parser;
static parse(code, options) {
try {
ast = acornParser.parse(code, parserOptions);
}
}
webpack 解析 AST 语法用的是 acorn
解析成AST最大作用就是收集模块依赖关系,webpack会遍历AST对象,遇到不同类型的节点执行对应的函数。比如调试代码中出现的import { helloWorld } from './helloworld.js' 或const xxx = require('XXX')的模块引入语句,webpack会记录下这些依赖项,并记录在module.dependencies数组中。到这里,入口module的解析过程就完成了,解析后的module大家有兴趣可以打印出来看下,这里我只截图了module.dependencies数组。
每个 module 解析完成之后,都会触发 Compilation例对象的succeedModule钩子,订阅这个钩子获取到刚解析完的 module 对象。 随后,webpack会遍历module.dependencies数组,递归解析它的依赖模块生成module,最终我们会得到项目所依赖的所有 modules。遍历的逻辑在afterBuild() -> processModuleDependencies() -> addModuleDependencies() -> factory.create()
make阶段到此结束,接下去会触发compilation.seal方法,进入下一个阶段。