# 源码解析三

上章说到,webpack-cli 检测合并完就会去执行 webpack(options),去到 webpack/lib/webpack.js,我们打印下 options 来看看 webpack-init

我们来看看 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 核心构建流程!

# 小结

  1. 通过实例 WebpackOptionsDefaulter 会给 webpack 设置默认配置
  2. 实例话 Compiler,它继承 Tapable,是 webpack 的核心
  3. 遍历执行 plugins,每个 plugin 执行 call 或者 apply 一下,传入 compiler
  4. compiler 执行 call
  5. 最后根据配置是否有 watch 来决定程序走向。
更新时间: 9/23/2020, 3:25:31 PM