# 源码解析五

# 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);
 } 
}

AST

webpack 解析 AST 语法用的是 acorn

解析成AST最大作用就是收集模块依赖关系,webpack会遍历AST对象,遇到不同类型的节点执行对应的函数。比如调试代码中出现的import { helloWorld } from './helloworld.js' 或const xxx = require('XXX')的模块引入语句,webpack会记录下这些依赖项,并记录在module.dependencies数组中。到这里,入口module的解析过程就完成了,解析后的module大家有兴趣可以打印出来看下,这里我只截图了module.dependencies数组。

dependencies

每个 module 解析完成之后,都会触发 Compilation例对象的succeedModule钩子,订阅这个钩子获取到刚解析完的 module 对象。 随后,webpack会遍历module.dependencies数组,递归解析它的依赖模块生成module,最终我们会得到项目所依赖的所有 modules。遍历的逻辑在afterBuild() -> processModuleDependencies() -> addModuleDependencies() -> factory.create()

make阶段到此结束,接下去会触发compilation.seal方法,进入下一个阶段。

更新时间: 9/23/2020, 3:25:31 PM