从源码解读webpack的编译流程

462 阅读12分钟

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

webpack编译总览

下图为流程图: webpack.drawio.png

一、执行webpack()

如果向webpack中传入回调函数,回调函数会在 webpack compiler 运行时被执行。

如果你不向 webpack 传入可执行的回调函数, 它会返回一个 webpack Compiler 实例。 你可以通过手动执行它或者为它的构建时添加一个监听器, 就像 CLI 类似。Compiler 实例提供了以下方法:

  • .run(callback)
  • .watch(watchOptions, handler)
/* webpack.js */
const webpack = (options, callback) => {
  /* 省略…… */
  if (callback) {
    try {
      // 生成一个编译器compliler
      const { compiler, watch, watchOptions } = create();
      if (watch) {
        // 监听并执行
        compiler.watch(watchOptions, callback);
      } else {
        // 执行
        compiler.run((err, stats) => {
          // 关闭
          compiler.close(err2 => {
            callback(err || err2, stats);
          });
        });
      }
      return compiler;
    } catch (err) {
      process.nextTick(() => callback(err));
      return null;
    }
  } else {
    const { compiler, watch } = create();
    if (watch) {
      util.deprecate(
        () => { },
        "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
        "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
      )();
    }
    return compiler;
  }
}

create()

webpack内部首先定义create方法并调用,用于返回compilerwatchwatchOptions

/* webpack.js */
const webpack = (options, callback) => {
  const create = () => {
    /* 省略…… */
    let compiler;
    let watch = false;
    let watchOptions;
    // 判断当前webpack是否有多配置
    if (Array.isArray(options)) {
      compiler = createMultiCompiler(options);
      watch = options.some(options => options.watch);
      watchOptions = options.map(options => options.watchOptions || {});
    } else {
      const webpackOptions = (options);
      compiler = createCompiler(webpackOptions);
      watch = webpackOptions.watch;
      watchOptions = webpackOptions.watchOptions || {};
    }
    return { compiler, watch, watchOptions };
  };
  /* 省略…… */
  // 生成一个编译器compliler
  const { compiler, watch, watchOptions } = create();
  /* 省略…… */
}

二、创建compiler

createCompiler(webpackOptions)

create方法中定义了两个方法(createMultiCompilercreateCompiler)用于创建并返回compiler,并执行一些后续相关操作。

createCompiler内部工作:

  1. 处理用于NodeEnvironmentPlugin插件的infrastructureLogging配置;
  2. 创建一个Compiler实例;
  3. 执行NodeEnvironmentPlugin插件;
    • 设置了node文件监听系统
      • compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem);
  4. 执行外部配置插件;
  5. 处理webpack其余配置项;
  6. 在编译器准备环境时,配置文件中初始化插件后,触发compiler的environment钩子;
  7. 编译器环境设置完成后,触发compiler的afterEnvironment钩子;
  8. 根据webpack配置项执行各种内部操作;
    • 处理webpack选项中的entry;
      • new EntryOptionPlugin().apply(compiler);
    • entry被处理后触发钩子;
      • compiler.hooks.entryOption.call(options.context, options.entry);
    • 初始化内部插件集合完成设置后触发钩子;
      • compiler.hooks.afterPlugins.call(compiler);
    • 设置resolver;
    • resolver设置完成后触发钩子;
      • compiler.hooks.afterResolvers.call(compiler);
  9. 编译器对象被初始化时,触发compiler的initialize钩子;
  10. 返回compiler;

webpack 使用 WebpackOptionsDefaulter 和 WebpackOptionsApply 来配置 Compiler 实例以及所有内置插件。

/* webpack.js */
/* createCompiler */
const createCompiler = rawOptions => {
  const options = getNormalizedWebpackOptions(rawOptions);
  applyWebpackOptionsBaseDefaults(options); // 处理用于NodeEnvironmentPlugin插件的infrastructureLogging配置
  const compiler = new Compiler(options.context, options);
  new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging
  }).apply(compiler);
  if (Array.isArray(options.plugins)) {
    // 执行外部插件
    for (const plugin of options.plugins) {
      if (typeof plugin === "function") {
        plugin.call(compiler, compiler);
      } else {
        plugin.apply(compiler);
      }
    }
  }
  applyWebpackOptionsDefaults(options); // 处理webpack其余的配置项
  // 在编译器准备环境时调用,时机就在配置文件中初始化插件之后。
  compiler.hooks.environment.call();
  // 当编译器环境设置完成后,在 environment hook 后直接调用。
  compiler.hooks.afterEnvironment.call();
  // 执行webpack内部设置的所有选项
  new WebpackOptionsApply().process(options, compiler);
  // 当编译器对象被初始化时调用。
  compiler.hooks.initialize.call();
  return compiler;
};

通常情况下,仅会创建一个主要 Compiler 实例, 虽然可以创建一些子 compiler 来代理到特定任务。 Compiler 基本上只是执行最低限度的功能,以维持生命周期运行的功能。 它将所有的加载、打包和写入工作, 都委托到注册过的插件上。

/* webpack.js */
/* createMultiCompiler */
const createMultiCompiler = (childOptions, options) => {
  const compilers = childOptions.map(options => createCompiler(options));
  const compiler = new MultiCompiler(compilers, options);
  for (const childCompiler of compilers) {
    if (childCompiler.options.dependencies) {
      compiler.setDependencies(
        childCompiler,
        childCompiler.options.dependencies
      );
    }
  }
  return compiler;
};

new Compiler(options.context, options)

Compiler 模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。

Compiler 实例上的 hooks 属性,用于将一个插件注册 到 Compiler 的生命周期中的所有钩子事件上。 

/* compiler.js */
const {
  SyncHook,
  SyncBailHook,
  AsyncParallelHook,
  AsyncSeriesHook
} = require("tapable");

class Compiler {
  /**
   * @param {string} context the compilation path
   * @param {WebpackOptions} options options
   */
  constructor(context, options = /** @type {WebpackOptions} */ ({})) {
    this.hooks = Object.freeze({
      initialize: new SyncHook([]),

      shouldEmit: new SyncBailHook(["compilation"]),
      done: new AsyncSeriesHook(["stats"]),
      afterDone: new SyncHook(["stats"]),
      
      /* 省略…… 具体内容看源码 */

      infrastructureLog: new SyncBailHook(["origin", "type", "args"]),

      // TODO the following hooks are weirdly located here
      // TODO move them for webpack 5
      environment: new SyncHook([]),
      afterEnvironment: new SyncHook([]),
      afterPlugins: new SyncHook(["compiler"]),
      afterResolvers: new SyncHook(["compiler"]),
      entryOption: new SyncBailHook(["context", "entry"])
    });

    this.webpack = webpack;

    this.name = undefined;
    this.parentCompilation = undefined;
    this.root = this;
    this.outputPath = "";
    this.watching = undefined;
    this.resolverFactory = new ResolverFactory();
    /* 省略…… 具体内容看源码 */

    this.running = false;

    this.idle = false;

    this._assetEmittingSourceCache = new WeakMap();
    this._assetEmittingWrittenFiles = new Map();
    this._assetEmittingPreviousFiles = new Set();
  }
}

三、启动构建

compiler创建完毕后,启动监听并执行编译:compiler.watch()。(也可不启动监听直接执行编译:compiler.run()),通常这两种模式分别用在开发环境和生产环境。

/* webpack.js */
const webpack = (options, callback) => {
  /* 省略…… */
  if (watch) {
    // 监听并执行
    compiler.watch(watchOptions, callback);
  } else {
    // 执行
    compiler.run((err, stats) => {
      // 关闭
      compiler.close(err2 => {
        callback(err || err2, stats);
      });
    });
  }
  return compiler;
}

Compiler 支持可以监控文件系统的 监听(watching) 机制,并且在文件修改时重新编译。 当处于监听模式(watch mode)时, compiler 会触发诸如 watchRunwatchClose 和 invalid 等额外的事件。 通常在 开发环境 中使用, 也常常会在 webpack-dev-server 这些工具的底层调用, 由此开发人员无须每次都使用手动方式重新编译。 还可以通过 CLI 进入监听模式。

1、开发构建

① compiler.watch(watchOptions, callback)

compiler内部定义watch方法,用于启动webpack的监听模式编译流程。

/* compiler.js */
class Compiler {
  /* 省略…… */
  watch(watchOptions, handler) {
    if (this.running) {
      return handler(new ConcurrentCompilationError());
    }

    this.running = true;
    this.watchMode = true;
    this.watching = new Watching(this, watchOptions, handler);
    return this.watching;
  }
  /* 省略…… */
}

调用 watch 方法会触发 webpack 执行,但之后会监听变更(很像 CLI 命令: webpack --watch), 一旦 webpack 检测到文件变更,就会重新执行编译。 该方法返回一个 Watching 实例。

② new Watching(compiler, watchOptions, callback)

/* Watching.js */
class Watching {
  constructor(compiler, watchOptions, handler) {
    /* 省略…… */
    process.nextTick(() => {
      // 初次执行
      if (this._initial) this._invalidate();
    });
  }
}

_invalidate()对构建条件进行判断,在合适的时机发送构建请求,调用_go

/* Watching.js */
_invalidate(
  fileTimeInfoEntries,
  contextTimeInfoEntries,
  changedFiles,
  removedFiles
) {
  if (this.suspended || (this._isBlocked() && (this.blocked = true))) {
    this._mergeWithCollected(changedFiles, removedFiles);
    return;
  }
  // 如果正在执行,则先将变动收集
  if (this.running) {
    this._mergeWithCollected(changedFiles, removedFiles);
    this.invalid = true;
  } else {
    this._go(
      fileTimeInfoEntries,
      contextTimeInfoEntries,
      changedFiles,
      removedFiles
    );
  }
}

③ compiler.compile(onCompiled)

_go内部定义run方法,run内部会执行compiler.compile(onCompiled)

/* Watching.js */
_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
  this.running = true; // 设置执行状态
  // 用_mergeWithCollected合并or收集变更的文件内容
  /* 省略代码…… */
  this.compiler.modifiedFiles = this._collectedChangedFiles;
  this._collectedChangedFiles = undefined;
  this.compiler.removedFiles = this._collectedRemovedFiles;
  this._collectedRemovedFiles = undefined;

  const run = () => {
    /* 省略…… */
    this.invalid = false;
    this._invalidReported = false;
    // 在监听模式下,一个新的 compilation 触发之后,但在 compilation 实际开始之前执行。
    this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
      if (err) return this._done(err);
      const onCompiled = (err, compilation) => {
        /* 省略…… */
      };
      this.compiler.compile(onCompiled);
    });
  };
  // 构建
  run();
}

接下来看compiler.compile方法:
compile方法内部会生成一个compilation对象。

/* compiler.js */
compile(callback) {
  const params = this.newCompilationParams();
  // 在创建 compilation parameter 之后执行。
  this.hooks.beforeCompile.callAsync(params, err => {
    // beforeCompile 之后立即调用,但在一个新的 compilation 创建之前。
    this.hooks.compile.call(params);
    // 创建一个Compilation模块
    const compilation = this.newCompilation(params);
    // compilation 结束之前执行。
    this.hooks.make.callAsync(compilation, err => {
      this.hooks.finishMake.callAsync(compilation, err => {
        process.nextTick(() => {
          // 完成编译并调用给定的回调。
          compilation.finish(err => {
            // 封闭编译。
            compilation.seal(err => {
              // compilation 结束和封印之后执行。
              this.hooks.afterCompile.callAsync(compilation, err => {
                // 执行传入的回调:即为下文中的onComplied方法。
                return callback(null, compilation);
              });
            });
          });
        });
      });
    });
  });
}

onComplied方法:

/* Watching.js */
const onCompiled = (err, compilation) => {
  
  // 在输出 asset 之前调用。返回一个布尔值,告知是否输出。
  if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
    return this._done(null, compilation);
  }
  process.nextTick(() => {
    this.compiler.emitAssets(compilation, err => {
      this.compiler.emitRecords(err => {
        if (compilation.hooks.needAdditionalPass.call()) {
          // 在 compilation 完成时执行。
          this.compiler.hooks.done.callAsync(stats, err => {
            // This hook allows you to do a one more additional pass of the build.
            this.compiler.hooks.additionalPass.callAsync(err => {
              this.compiler.compile(onCompiled);
            });
          });
          return;
        }
        return this._done(null, compilation);
      });
    });
  });
};

④ watching.watch(files, dirs, missing)

onCompiled回调的最后,内部会开启一个文件监听。


/* Watching.js */
_done(err, compilation) {
  this.running = false;
  /* 省略代码 */
  const cbs = this.callbacks;
  this.callbacks = [];
  
  // 在 compilation 完成时执行。
  this.compiler.hooks.done.callAsync(stats, err => {
    
    this.compiler.cache.storeBuildDependencies(
      compilation.buildDependencies,
      err => {
        process.nextTick(() => {
          if (!this.closed) {
            // 开启文件监听
            this.watch(
              compilation.fileDependencies,
              compilation.contextDependencies,
              compilation.missingDependencies
            );
          }
        });
        for (const cb of cbs) cb(null);
        this.compiler.hooks.afterDone.call(stats);
      }
    );
  });
}

/* 省略…… */
watch(files, dirs, missing) {
this.pausedWatcher = null;
// 向监听系统中传入监听回调
this.watcher = this.compiler.watchFileSystem.watch(
  files,
  dirs,
  missing,
  this.lastWatcherStartTime,
  this.watchOptions,
  (
    err,
    fileTimeInfoEntries,
    contextTimeInfoEntries,
    changedFiles,
    removedFiles
  ) => {
    if (err) {
      /* 省略…… 错误处理 */
    }
    // 监听到文件变更后执行
    this._invalidate(
      fileTimeInfoEntries,
      contextTimeInfoEntries,
      changedFiles,
      removedFiles
    );
    this._onChange();
  },
  (fileName, changeTime) => {
   /* 省略…… compilation不可用处理 */
  }
);
}

2、生产构建

compiler.run(callback)

使用 run 方法启动所有编译工作。 完成之后,执行传入的的 callback 函数。 最终记录下来的概括信息(stats)和错误(errors),都应在这个 callback 函数中获取。

/* webpack.js */
compiler.run((err, stats) => {
  // 关闭
  compiler.close(err2 => {
    callback(err || err2, stats);
  });
});
/* Compiler.js */
class Compiler {
  run(callback) {
    if (this.running) {
      return callback(new ConcurrentCompilationError());
    }
    this.running = true;
    
    const onCompiled = (err, compilation) => {
      // 在输出 asset 之前调用。返回一个布尔值,告知是否输出。
      if (this.hooks.shouldEmit.call(compilation) === false) {
        // 在 compilation 完成时执行。
        this.hooks.done.callAsync(stats, err => {
          return finalCallback(null, stats);
        });
        return;
      }

      process.nextTick(() => {
        this.emitAssets(compilation, err => {
          if (compilation.hooks.needAdditionalPass.call()) {
            // 在 compilation 完成时执行。
            this.hooks.done.callAsync(stats, err => {
              // This hook allows you to do a one more additional pass of the build.
              this.hooks.additionalPass.callAsync(err => {
                this.compile(onCompiled);
              });
            });
            return;
          }

          this.emitRecords(err => {
            // 在 compilation 完成时执行。
            this.hooks.done.callAsync(stats, err => {
              this.cache.storeBuildDependencies(
                compilation.buildDependencies,
                err => {
                  return finalCallback(null, stats);
                }
              );
            });
          });
        });
      });
    };

    const run = () => {
      // 在开始执行一次构建之前调用,compiler.run 方法开始执行后立刻进行调用。
      this.hooks.beforeRun.callAsync(this, err => {
        // 在开始读取 records 之前调用。
        this.hooks.run.callAsync(this, err => {
          this.readRecords(err => {
            this.compile(onCompiled);
          });
        });
      });
    };

    if (this.idle) {
      this.cache.endIdle(err => {
        this.idle = false;
        run();
      });
    } else {
      run();
    }
  }
}

四、执行构建

compiler.compile(onCompiled)

compiler.compile内部工作:

  1. 设置compilation的parameter;
    • 实例normalModuleFactory并触发compiler的normalModuleFactory钩子;
    • 实例contextModuleFactory并触发compiler的contextModuleFactory钩子;
  2. 触发compiler的beforeCompiler钩子;
  3. 触发compiler的compile钩子;
  4. 创建compilation;
  5. 触发compiler的thisCompilation钩子;
  6. 触发compiler的compilation钩子;
  7. 触发compiler的make钩子(compilation结束之前执行);
    • EntryPlugin插件:执行compilation的addEntry方法,为编译添加入口:
      • compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {compilation.addEntry(context, dep, options, err => {}})
    • PrefetchPlugin插件:
      • ① compilation.addModuleChain(context, dependency, callback)
      • ② compilation.addModuleTree({ context, dependency }, callback)
      • ③ compilation.handleModuleCreation(options, callback)
      • ④ compilation.factorizeModule(options, (err, factoryResult) => {this.addModule(factoryResult.module, () => { this._handleModuleBuildAndDependencies()})})
      • ⑤ compilation.factorizeQueue.add(options, callback)(入参同上)
      • ⑥ compilation._factorizeModule(options, (e, r) => {this.handleResult(entry, e, r)} )
      • ⑦ normalModuleFactory.create(options, callback) (该实例在compiler.conpile中创建,在PrefetchPlugin中设置)
      • ⑧ create内部触发normalModuleFactory的factorize钩子(初始化解析之前调用)
      • ⑨ foctorize钩子内部触发normalModuleFactory的resolve钩子(请求被解析前调用)
      • 执行resolver.resolve()
        • 解析得到resolvedResource、resolvedResourceResolveData和loaders
      • ①① 创建Parser
        • normalModuleFactory.hooks.createParser.for(type).call(parserOptions)钩子去通知各插件创建对应类型的parser
      • ①② 触发normalModuleFactory的afterResolve钩子(解析后调用)
      • ①③ 触发normalModuleFactory的createModule钩子(创建normalModule实例之前调用)
      • ①④ 创建Module
        • createModule = new NormalModule(normalModuleCreateData)
      • ①⑤ 触发normalModuleFactory的module钩子(创建normalModule实例之后调用)
      • ①⑥ factorizeQueue.handleResult(entry, null, factoryResult),其中factoryResult.module = createModule
      • ①⑦ compilation.addModule(factoryResult.module, callback)
      • ①⑧ 编译module
        • normalModuleFactory.buildModule()
      • ①⑨ compilation.processModuleDependencies()
      • 重复步骤③~①⑨
  8. 触发compiler的finishMake钩子;
  9. 依次执行compilation的finish、seal方法;
  10. 触发compiler的afterCompile方法。
/* Compiler.js */
compile(callback) {
  const params = this.newCompilationParams();
  // 在创建 compilation parameter 之后执行。
  this.hooks.beforeCompile.callAsync(params, err => {
    if (err) return callback(err);
    // beforeCompile 之后立即调用,但在一个新的 compilation 创建之前。
    this.hooks.compile.call(params);

    const compilation = this.newCompilation(params);
    // compilation 结束之前执行。
    this.hooks.make.callAsync(compilation, err => {
      this.hooks.finishMake.callAsync(compilation, err => {
        process.nextTick(() => {
          compilation.finish(err => {
            compilation.seal(err => {
              this.hooks.afterCompile.callAsync(compilation, err => {
                return callback(null, compilation);
              })
            })
          })
        })
      })
    })
    /* 省略…… */
  });
}

Compiler 使用 NormalModuleFactory 模块生成各类模块。从入口点开始,此模块会分解每个请求,解析文件内容以查找进一步的请求,然后通过分解所有请求以及解析新的文件来爬取全部文件。在最后阶段,每个依赖项都会成为一个模块实例。

Compiler 使用 ContextModuleFactory 模块从 webpack 独特的 require.context API 生成依赖关系。它会解析请求的目录,为每个文件生成请求,并依据传递来的 regExp 进行过滤。最后匹配成功的依赖关系将被传入 NormalModuleFactory

/* Compiler.js */
newCompilationParams() {
  const params = {
    normalModuleFactory: this.createNormalModuleFactory(),
    contextModuleFactory: this.createContextModuleFactory()
  };
  return params;
}
newCompilation(params) {
  const compilation = this.createCompilation(params);
  compilation.name = this.name;
  compilation.records = this.records;
  // 初始化 compilation 时调用,在触发 compilation 事件之前调用。
  this.hooks.thisCompilation.call(compilation, params);
  // compilation 创建之后执行。
  this.hooks.compilation.call(compilation, params);
  return compilation;
}
createCompilation(params) {
  this._cleanupLastCompilation();
  return (this._lastCompilation = new Compilation(this, params));
}

每执行一次compilercompile方法都会创建一个compilation对象。

五、创建compilation

new Compilation(compiler, params)

Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

class Compilation {
  /* 省略代码…… */
}

六、执行resolver

解析器是使用 enhanced-resolve 库创建的。Resolver 类 拓展了 tapable 类,并使用 tapable 来提供了一些钩子。 enhanced-resolve 可以直接用于创建新的解析器, 但是,任何 compiler 实例 都有一些解析器实例,可以 被 tap 进去。

resolverFactory对象在创建compiler实例时生成,并传入normalModuleFactory中。

/* Compiler.js */
class Compiler {
  constructor() {
      this.resolverFactory = new ResolverFactory();
  }
}
createNormalModuleFactory() {
  this._cleanupLastNormalModuleFactory();
  const normalModuleFactory = new NormalModuleFactory({
      context: this.options.context,
      fs: this.inputFileSystem,
      resolverFactory: this.resolverFactory,
      options: this.options.module,
      associatedObjectForCache: this.root,
      layers: this.options.experiments.layers
  });
  this._lastNormalModuleFactory = normalModuleFactory;
  // NormalModuleFactory 创建之后调用。
  this.hooks.normalModuleFactory.call(normalModuleFactory);
  return normalModuleFactory;
}

resolveResource

/* NormoalModuleFactory.js */
resolveResource(
  contextInfo,
  context,
  unresolvedResource,
  resolver,
  resolveContext,
  callback
) {
  resolver.resolve(
    contextInfo,
    context,
    unresolvedResource,
    resolveContext,
    (err, resolvedResource, resolvedResourceResolveData) => {
      if (err) {
        /* 省略代码…… */
      }
      callback(err, resolvedResource, resolvedResourceResolveData);
    }
  );
}

resolveLoader

/* NormoalModuleFactory.js */
resolveRequestArray(
  contextInfo,
  context,
  array,
  resolver,
  resolveContext,
  callback
) {
  if (array.length === 0) return callback(null, array);
  asyncLib.map(
    array,
    (item, callback) => {
      resolver.resolve(
        contextInfo,
        context,
        item.loader,
        resolveContext,
        (err, result) => {
          if (
            err &&
            /^[^/]*$/.test(item.loader) &&
            !/-loader$/.test(item.loader)
          ) {
            /* 省略代码 */
          }
          if (err) return callback(err);

          const parsedResult = this._parseResourceWithoutFragment(result);
          const resolved = {
            loader: parsedResult.path,
            options:
              item.options === undefined
                ? parsedResult.query
                  ? parsedResult.query.slice(1)
                  : undefined
                : item.options,
            ident: item.options === undefined ? undefined : item.ident
          };
          return callback(null, resolved);
        }
      );
    },
    callback
  );
}

七、创建对应的parser

type不同创建的parser类型不同。

/* NormalFactory.js */
createParser(type, parserOptions = {}) {
  parserOptions = mergeGlobalOptions(
    this._globalParserOptions,
    type,
    parserOptions
  );
  // 在 Parser 实例创建前调用。parserOptions 是 module.parser 中对应标识符或空对象的选项。
  const parser = this.hooks.createParser.for(type).call(parserOptions);
  if (!parser) {
    throw new Error(`No parser registered for ${type}`);
  }
  // 在创建 Parser 实例后触发。
  this.hooks.parser.for(type).call(parser, parserOptions);
  return parser;
}
/* AssetModulesPlugin.js */
normalModuleFactory.hooks.createParser
.for("asset/inline")
.tap(plugin, parserOptions => {
  const AssetParser = getAssetParser();

  return new AssetParser(true);
});
/* JavascriptModulesPlugin.js */
normalModuleFactory.hooks.createParser
  .for("javascript/auto")
  .tap("JavascriptModulesPlugin", options => {
    return new JavascriptParser("auto");
  });
normalModuleFactory.hooks.createParser
  .for("javascript/dynamic")
  .tap("JavascriptModulesPlugin", options => {
    return new JavascriptParser("script");
  });
normalModuleFactory.hooks.createParser
  .for("javascript/esm")
  .tap("JavascriptModulesPlugin", options => {
    return new JavascriptParser("module");
  });

合并resolveData

将解析到的loaderresourceparser添加到resolveData中,之后创建module时会将此数据传入到moduleFactory中。

/* NormoalModuleFactory.js */
try { // 处理resolveData,添加loader、parser等
  Object.assign(data.createData, {
    layer:
      layer === undefined ? contextInfo.issuerLayer || null : layer,
    request: stringifyLoadersAndResource(
      allLoaders,
      resourceData.resource
    ),
    userRequest,
    rawRequest: request,
    loaders: allLoaders,
    resource: resourceData.resource,
    context:
      resourceData.context || getContext(resourceData.resource),
    matchResource: matchResourceData
      ? matchResourceData.resource
      : undefined,
    resourceResolveData: resourceData.data,
    settings,
    type,
    parser: this.getParser(type, settings.parser),
    parserOptions: settings.parser,
    generator: this.getGenerator(type, settings.generator),
    generatorOptions: settings.generator,
    resolveOptions
  });
} catch (e) {
  return callback(e);
}

八、创建并编译module

new normalModule()

/* NormoalModuleFactory.js */
class NormalModuleFactory extends ModuleFactory {
  custructor() {
    // 在初始化解析之前调用。它应该返回 undefined 以继续。
    this.hooks.factorize.tapAsync(
      {
        name: "NormalModuleFactory",
        stage: 100
      },
      (resolveData, callback) => {
        // 在请求被解析之前调用。可以通过返回 false 来忽略依赖项。返回一个模块实例将结束进程。否则,返回 undefined 以继续。
        this.hooks.resolve.callAsync(resolveData, (err, result) => {
          this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
            const createData = resolveData.createData;
            // 在创建 NormalModule 实例之前调用。
            this.hooks.createModule.callAsync(
              createData,
              resolveData,
              (err, createdModule) => {
                if (!createdModule) {
                  if (!resolveData.request) {
                    return callback(new Error("Empty dependency (no request)"));
                  }
                  createdModule = new NormalModule((createData));
                }
                // 在创建 NormalModule 实例后调用。
                createdModule = this.hooks.module.call(
                  createdModule,
                  createData,
                  resolveData
                );
                return callback(null, createdModule);
              }
            );
          });
        });
      }
    );
  }
}

compilation.buildModule()

/* Compilation.js */
_buildModule(module, callback) {
  /* 省略代码…… */

  module.needBuild(
    {
      compilation: this,
      fileSystemInfo: this.fileSystemInfo,
      valueCacheVersions: this.valueCacheVersions
    },
    (err, needBuild) => {

      if (!needBuild) {
        /* 省略代码 */
      }

      this.hooks.buildModule.call(module);
      this.builtModules.add(module);
      module.build(
        this.options,
        this,
        this.resolverFactory.get("normal", module.resolveOptions),
        this.inputFileSystem,
        err => {
          /* 省略代码 */
        }
      );
    }
  );
}

九、依次编译所有模块

compilation.processModuleDependencies()

/* Compilation.js */
_processModuleDependencies(module, callback) {
  try {
    /** @type {DependenciesBlock[]} */
    const queue = [module];
    do {
      const block = queue.pop();
      if (block.dependencies) {
        currentBlock = block;
        let i = 0;
        for (const dep of block.dependencies) processDependency(dep, i++);
      }
      if (block.blocks) {
        for (const b of block.blocks) queue.push(b);
      }
    } while (queue.length !== 0);
  } catch (e) {
    return callback(e);
  }
}

processModuleDependencies方法会根据依赖关系依次编译每个模块。将所有模块都处理完毕后,根据依赖关系图以及前面各个步骤得到的模块信息,再组织和拼装对应的模板,再把相应的内容填充到模板中,最后“渲染”出目标代码。