# 源码解析三
上章说到,webpack-cli 检测合并完就会去执行 webpack(options),去到 webpack/lib/webpack.js,我们打印下 options 来看看
我们来看看 webpack 这个函数具体做了什么
# webpack
const webpack = (options, callback) => {
const webpackOptionsValidationErrors = validateSchema(
webpackOptionsSchema,
options
);
if (webpackOptionsValidationErrors.length) {
throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
}
let compiler;
if (Array.isArray(options)) {
compiler = new MultiCompiler(
Array.from(options).map(options => webpack(options))
);
} else if (typeof options === "object") {
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.options = new WebpackOptionsApply().process(options, compiler);
} else {
throw new Error("Invalid argument: options");
}
if (callback) {
if (typeof callback !== "function") {
throw new Error("Invalid argument: callback");
}
if (
options.watch === true ||
(Array.isArray(options) && options.some(o => o.watch))
) {
const watchOptions = Array.isArray(options)
? options.map(o => o.watchOptions || {})
: options.watchOptions || {};
return compiler.watch(watchOptions, callback);
}
compiler.run(callback);
}
return compiler;
};
上面打印 options 是个对象,因此会走第二个分支, 去 require("./WebpackOptionsDefaulter") 看看
# WebpackOptionsDefaulter
class WebpackOptionsDefaulter extends OptionsDefaulter {
super();
this.set("entry", "./src");
this.set("devtool", "make", options =>
options.mode === "development" ? "eval" : false
);
this.set("cache", "make", options => options.mode === "development");
this.set("context", process.cwd());
this.set("target", "web");
...
}
}
看一下就差不多懂了,就是默认设置一些参数配置,插件,loader 等等,搞到这 就很蛋疼了,整了大半圈尼玛还在 webpack 配置这里转圈,我 😓
# new Compiler
上面整完 options 后,执行了 new Compiler, 并传入了 options.context,老规矩,进去 Compiler 进去看看呗
const {Tapable,SyncHook,SyncBailHook,AsyncParallelHook,AsyncSeriesHook} = require("tapable");
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
// ...
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
// ...
}
/** @type {boolean} */
this.running = false;
/** @type {boolean} */
this.watchMode = false;
}
watch()
run()
emitAssets()
}
可以看到 Compiler 继承 Tapable,然后定义了一堆 hooks 和方法
# tapable
这里简单说下 tapable,SyncHook 是处理串行同步执行的文件,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数。如下:
// 创建实列
const syncHook = new SyncHook(["name", "age"]);
// 注册事件
syncHook.tap("1", (name, age) => {
console.log("1", name, age);
});
syncHook.tap("2", (name, age) => {
console.log("2", name, age);
});
syncHook.tap("3", (name, age) => {
console.log("3", name, age);
});
// 触发事件,让监听函数执行
syncHook.call("kongzhiEvent-1", 18);
可以看到执行了 call 所有的执行了各自的打印 1/2/3,并且输出了 name:kongzhiEvent-1,age:18
# plugin
回到对应上面,判断配置options里面的plugins,并且依次执行
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
上面可以看到,如果 plugin 是一个函数,执行 call并传入 compiler,否则通过 plugin.apply 来执行,我们之前有编写 plugin 的经验 如下:
class HtmlAfterPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(pluginName, (compilation) => {
})
}
}
之前不是很懂为什么要编写这么一陀东西 compiler.hooks.compilation.tap,现在有种恍然大悟,我的plugin 只要执行 tap监听,你那边一 call 我所有不都可以收到了嘛😊
# Cli
上面返回 compiler 后,让我们再次回到 cli里面,这里有这么一个判断
function compilerCallback(err, stats){
}
if (firstOptions.watch || options.watch) {
// ...
compiler.watch(watchOptions, compilerCallback);
} else {
compiler.run((err, stats) => {
if (compiler.close) {
compiler.close(err2 => {
compilerCallback(err || err2, stats);
});
} else {
compilerCallback(err, stats);
}
});
}
这里就是进行一个判断是否有 watch,有则走 compiler.watch,无则走 compiler.run,这里我们走compiler.run,进入 webpack 核心构建流程!
# 小结
- 通过实例 WebpackOptionsDefaulter 会给 webpack 设置默认配置
- 实例话 Compiler,它继承 Tapable,是 webpack 的核心
- 遍历执行 plugins,每个 plugin 执行 call 或者 apply 一下,传入 compiler
- compiler 执行 call
- 最后根据配置是否有 watch 来决定程序走向。