本文主要讲解
tapable
上一篇文章我们分析当执行 webpack 命令的时候,会从 webpack 目录执行到 webpack-cli 目录,最终又回到 webpack 目录,这一篇文章就主要讲解回到 webpack 目录中又做了哪些事情?
校验参数是否合法
通过 validateSchema 方法校验传入参数是否合法。
应用默认值
不关心内部实现的同学,可以忽略此段长篇大论🤪
options = new WebpackOptionsDefaulter().process(options);
WebpackOptionsDefaulter 内主要做了两件事。
-
每一配置项均设置默认值或处理逻辑。
-
对配置项分类,共划分 4 类
undefined、make、call和append。-
undefined
如果未设值,则使用
defaults内的默认值。 -
make
如果未设值,则调用通过调用
defaults内对应配置项的方法来生成值。 -
call
不管是否有值,都会执行对应配置项的处理逻辑。
-
append
传入的配置项后加上默认值。
-
所有的配置项都处理完毕后,会得到最终 webpack 所需要的配置项。
生成 compiler 对象,开始编译
实例化 Compiler 类,并把实例化后对象注入到所有的 plugin 中,最后调用对象上的 run 方法开始编译。
Tapable
由于笔者未用过
Tapable,所以先讲解用法,对使用已掌握的童鞋可以直接忽略,对tapable源码不熟的童鞋可以直接拉到最后。
Tapable 是一个类似于 Node.js 中 EventEmitter 的库,专注于事件的触发与处理,同时它也是 webpack 出的一个小型的库,它允许你创建钩子,为钩子挂载函数,最后调用挂载函数,其更像一个发布-订阅系统。
Tapable 共有 9 种钩子,共分为 2 大类,一类为 sync 同步钩子,一类为 async 异步钩子。sync 同步钩子分为 SyncHook、SyncBailHook、SyncWaterfallHook 和 SyncLoopHook 4 种。async 异步钩子分为 AsyncParallelHook、AsyncParallelBailHook、AsyncSeriesHook、AsyncSeriesBailHook 和 Async SeriesWaterfallHook 5 种。同步的钩子,只能通过 tap 调用,而异步的钩子则可以通过 tapPromise、tapAsync 和 tap 来调用。
const { SyncHook } = require("tapable");
const hook = new SyncHook();
hook.tap("subscribe", () => {
console.log("subscribe");
});
hook.call(); // subscribe
使用
SyncHook
SyncHook 是最基础的钩子,它可以挂载多个函数,挂载的函数依次执行,没有返回值。
const syncHook = new SyncHook();
syncHook.tap("syncHook1",() => {
console.log("syncHook1");
});
syncHook.tap("syncHook2",() => {
console.log("syncHook2");
});
syncHook.call();
// syncHook1
// syncHook2
SyncWaterfallHook
SyncWaterfallHook 可以挂载多个函数,函数依次执行,下一个挂载函数会利用上一个函数的返回值作为参数。
const syncWaterfallHook = new SyncWaterfallHook(["arg1"]);
syncWaterfallHook.tap("syncWaterfallHook1", arg1 => {
console.log("syncWaterfallHook1", arg1);
return arg1 + 1;
});
syncWaterfallHook.tap("syncWaterfallHook2", arg1 => {
console.log("syncWaterfallHook2", arg1);
return arg1 + 1;
});
syncWaterfallHook.call(1);
// syncWaterfallHook1 1
// syncWaterfallHook2 2
syncBailHook
syncBailHook 可以挂载多个函数,函数依次执行,当有一个函数有返回值,则接下来的函数都不会执行。
const syncBailHook = new SyncBailHook();
syncBailHook.tap("syncBailHook1", () => {
console.log("syncBailHook1");
return false;
});
syncBailHook.tap("syncBailHook2", () => {
console.log("syncBailHook2");
});
syncBailHook.call();
// syncBailHook1
SyncLoopHook
当监听的函数返回 true,会一直执行此函数,直至返回 false。
let syncLoopHook = new SyncLoopHook();
let count = 3;
syncLoopHook.tap("syncLoopHook1", () => {
console.log(count);
if (count) {
count--;
return true;
}
return;
});
syncLoopHook.call();
// 3
// 2
// 1
// 0
AsyncSeriesHook
AsyncSeriesHook 异步钩子,可以挂载多个函数,每个函数都会等待上一个函数执行完,再执行。
const asyncSeriesHook = new AsyncSeriesHook(["arg"]);
asyncSeriesHook.tapPromise("asyncSeriesHook1", arg => {
return new Promise(resolve => {
setTimeout(() => {
console.log("asyncSeriesHook1", arg);
resolve(arg + 1);
}, 1000);
});
});
asyncSeriesHook.tapPromise("asyncSeriesHook2", arg => {
return new Promise(resolve => {
setTimeout(() => {
console.log("asyncSeriesHook2", arg);
}, 1000);
});
});
asyncSeriesHook.callAsync(1);
// asyncSeriesHook1 1
// asyncSeriesHook2 1
AsyncSeriesBailHook
AsyncSeriesBailHook 异步钩子,可以挂载多个函数,只要一个异步函数 reject 或异步函数有返回值,则直接进入回调函数执,且后续异步不再执行。如果异步执行结果 undefined,则执行下一个函数。
const asyncSeriesBailHook = new AsyncSeriesBailHook(["arg1"]);
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook1", arg1 => {
return new Promise(resolve => {
setTimeout(() => {
console.log("asyncSeriesBailHook1", arg1);
resolve();
}, 1000);
});
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook2", arg1 => {
return new Promise(resolve => {
setTimeout(() => {
console.log("asyncSeriesBailHook2", arg1);
resolve(arg1 + 1);
}, 1000);
});
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook3", arg1 => {
return new Promise(resolve => {
setTimeout(() => {
console.log("asyncSeriesBailHook3", arg1);
}, 1000);
});
});
asyncSeriesBailHook.callAsync(1, err => {
console.log("err", err);
});
// asyncSeriesBailHook1 1
// asyncSeriesBailHook2 1
// err null
AsyncSeriesWaterfallHook
AsyncSeriesWaterfallHook 异步钩子,可以挂载多个函数,只要有一个异步函数 reject ,则直接进入回调函数执行,且后续异步不再执行。如果异步函数有返回值,则会把返回值传入到下一个函数。
const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(["arg1"]);
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook1", arg1 => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("AsyncSeriesWaterfallHook1", arg1);
resolve(arg1 + 1);
}, 2000);
});
});
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook2", arg1 => {
return new Promise(resolve => {
setTimeout(() => {
console.log("AsyncSeriesWaterfallHook2", arg1);
resolve();
}, 2000);
});
});
asyncSeriesWaterfallHook.callAsync(1, err => {
console.log(err);
});
// AsyncSeriesWaterfallHook1 1
// AsyncSeriesWaterfallHook2 2
// null
AsyncParallelHook
AsyncParallelHook 异步钩子,可以挂载多个函数,同时触发异步函数执行,和 Promise.all 类似,只要有一个异步函数错误,整体结果就错误。
const asyncParallelHook = new AsyncParallelHook(["arg1"]);
asyncParallelHook.tapPromise("asyncParallelHook1", arg1 => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("asyncParallelHook1", arg1);
resolve(arg1 + 1);
}, 1000);
});
});
asyncParallelHook.tapPromise("asyncParallelHook2", arg1 => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("asyncParallelHook2", arg1);
reject(arg1 + 1);
}, 1000);
});
});
asyncParallelHook.callAsync(1, err => {
console.log(err);
});
//asyncParallelHook1 1
//asyncParallelHook2 1
// 2
AsyncParallelBailHook
AsyncParallelBailHook 和 AsyncParallelHook 特性一样。唯一不一样的就是,如果绑定的第一个异步函数出错,那整体状态就出错,成功则成功,并立马执行回调函数。不以其余绑定的异步函数成功与否作为依据。
const asyncParallelBailHook = new AsyncParallelBailHook(["arg"]);
asyncParallelBailHook.tapPromise("asyncParallelBailHook1", arg => {
return new Promise((resolve, reject) => {
console.log("asyncParallelBailHook1", arg);
reject(arg + 1);
});
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook2", arg => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("asyncParallelBailHook2", arg);
resolve(arg + 1);
}, 100);
});
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook3", arg => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("asyncParallelBailHook3", arg);
resolve(arg + 1);
}, 1000);
});
});
asyncParallelBailHook.promise(1).then(
() => {
console.log("successful");
},
() => {
console.log("error");
}
);
//asyncParallelBailHook1 1
// error
// asyncParallelBailHook2 1
// asyncParallelBailHook3 1
源码分析
打开 node_modules/lib/index.js 文件,可以看到 Tapable 向外界暴露的不止 9 个钩子,还有 Tapable、HookMap 和 MultiHook。这三个不是重点,我们忽略它。
所有钩子都继承 Hook 类,每个钩子的方法基本上完全一样,都有 compile 方法,只有两点比较例外。
AsyncSeriesWaterfallHook和SyncWaterfallHook重写了Hook的构造函数- 同步钩子全都重写了
tapAsync和tapPromise那当我们绑定一个钩子时候,Tapable做了哪些呢?Tapable会根据传入的参数生成options,并把生成的options对象插入到taps属性上。
当通过 call、callAsync 或 promise 调用钩子时候,最终会调用各自类上重写后的 compile 方法。
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
factory 值从何而来,它是每一个钩子对应的 xxxHookCodeFactory 类的实例,所有的 xxxHookCodeFactory 类均继承 HookCodeFactory。当我们调用的时候,HookCodeFactory 内会组装执行代码,组装完成后便执行此段代码。
具体每一个钩子的原理,我觉得webpack4.0 源码分析之 Tapable就写得十分不错,这里就不赘述了。
总结
参考文献
最后
笔者建议按照顺序食用,效果更佳哦。如果本文对您有所帮助,还请不要吝啬您的点赞,每一个点赞都是对笔者最大的鼓励。