Open zhangzheng-zz opened 3 years ago
如果传入了 callback 会在 compiler.run执行 之后执行 callback,否则是返回 compiler 最后会开启 compiler.run 进行编译。 compiler 做了什么? 注册插件,开启钩子。https://webpack.docschina.org/api/compiler-hooks/ 钩子是什么?run 是什么类型的钩子?
钩子类的编写基本一致,基于Hook创造自己的类,修饰类并实现自己的compile方法,compile是为了生成call方法,理解流水线。
const hook = new Hook(args, name);
// 修饰 hook 构造函数为类 SyncHook
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
// compile
hook.compile = COMPILE;
发布订阅模式:tap注册 call触发 Hook分为同步异步:SyncHook AsyncSeriesHook Async Hook 三种注册方式: sync、async、promise
let sh = new SyncHook(["name"]);
// 注册
sh.tap("A", name => {
console.log("A:", name);
});
sh.tap("B", name => {
console.log("B:", name);
});
sh.tap(
{
name: "C",
before: "B"
},
name => {
console.log("C:", name);
}
);
执行结果: taps: [A,C,B] A -> C -> B 源码在 Hook.js 134行的 _insert 方法,思路:
// 每次注册事件时,将call重置,需要重新编译生成call方法
this._resetCompilation();
Hook 基类中call的实现 js 惰性函数:https://segmentfault.com/a/1190000010783034
const CALL_DELEGATE = function (...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
调用 实例的 compile,compile 必须在每一个钩子类中自己实现!SyncHook为例。
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type,
});
}
SyncHook 类
const COMPILE = function (options) {
// 将回调函数形成的数组存在 _x
// this._x = [fn1,fn2...]
factory.setup(this, options);
// 拼接字符串形成 call 方法返回 new Function(str)
// var _x = this._x
return factory.create(options);
};
call 函数的实现,通过 new Function()将 taps 里面保存的回调函数拼接成按顺序执行的函数字符串转为函数调用
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(name);
var _fn1 = _x[1];
_fn1(name);
var _fn2 = _x[2];
_fn2(name);
webpack.js -> require webpack-cli -> runCLI -> (new WebpackCLI() -> require('webpack')) cli.run(args)
-> program.action -> webpack
webpack(config) -> compile.run()
compile.run()
之前做了什么?// 实例化 compiler
const compiler = new Compiler(options.context)
// 处理后的 options
compiler.options = options
// 为 compiler 封装并注入了文件存取的 api 涉及钩子 beforeRun
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler)
// 注册 用户配置的 plugins
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
// 在编译器准备环境时调用,时机就在配置文件中初始化插件之后。
compiler.hooks.environment.call()
// 当编译器环境设置完成后,在 environment hook 后直接调用。
compiler.hooks.afterEnvironment.call()
// 根据配置注册内部 plugins
// 涉及到入口的处理
// 注册了 compilation make 两个钩子
// new EntryOptionPlugin().apply(compiler);
new WebpackOptionsApply().process(options, compiler)
compiler.hooks.initialize.call()
....
const run = () => {
// 这里就会调用 NodeEnvironmentPlugin
this.hooks.beforeRun.callAsync(this, err => {
......
// 重点 onCompiled 先记下,会在 compile 完成后调用
this.compile(onCompiled);
});
};
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
this.hooks.compile.call(params);
// 实例化 compilation
// 调用了 compile 的 thisCompilation和compilation两个钩子
const compilation = this.newCompilation(params);
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 => {
});
});
});
});
});
});
});
}
compile 的调用链:beforeCompile -> compilation 实例化 -> thisCompilation 和 compilation -> make -> finishMake -> finish -> seal -> afterCompile 最后再是回调 onCompiled
onCompiled 文件输出
compile run 到 make 之前完成了 compile 的流程,调用 make 会开始 compilation 阶段开始编译文件的过程,从入口看起:addEntry
// EntryPlugin.js
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
const { entry, options, context } = this;
const dep = EntryPlugin.createDependency(entry, options);
// 开始 addEntry
compilation.addEntry(context, dep, options, err => {
callback(err);
});
});
https://juejin.cn/post/6844903987129352206
webpack4 流程写得较好的: https://github.com/6fedcom/fe-blog