本文主要讲解
webpack
与tapable
node
上一篇文章咱们分析当执行 webpack
命令的时候,会从 webpack
目录执行到 webpack-cli
目录,最终又回到 webpack
目录,这一篇文章就主要讲解回到 webpack
目录中又作了哪些事情?webpack
首先打开 webpack/lib/webpack.js
文件,找到 webpack
方法。方法内部首先会验证 options
是否合法,而后会根据传入的 options
是否为数组,生成不一样的 compiler
对象。web
若是为非数组则实例化 Compiler
,Compiler
对象继承 Tapable
,若是是数组则实例化 MultiCompiler
,数组每一项均实例化 Compiler
。最终给生成 compiler
对象注入 plugins
,再根据 target
,选择性注入插件。好比 target
为 web
时,注入 xxx 插件,最后视参数调用 run
方法仍是 watch
方法。数组
因为笔者未用过
Tapable
,因此先讲解用法,对使用已掌握的童鞋能够直接忽略,对tapable
源码不熟的童鞋能够直接拉到最后。promise
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
来调用。async
const { SyncHook } = require("tapable");
const hook = new SyncHook();
hook.tap("subscribe", () => {
console.log("subscribe");
});
hook.call(); // subscribe
复制代码
SyncHook
是最基础的钩子,它能够挂载多个函数,挂载的函数依次执行,没有返回值。函数
const syncHook = new SyncHook();
syncHook.tap("syncHook1",() => {
console.log("syncHook1");
});
syncHook.tap("syncHook2",() => {
console.log("syncHook2");
});
syncHook.call();
// syncHook1
// syncHook2
复制代码
SyncWaterfallHook
能够挂载多个函数,函数依次执行,下一个挂载函数会利用上一个函数的返回值做为参数。oop
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
能够挂载多个函数,函数依次执行,当有一个函数有返回值,则接下来的函数都不会执行。源码分析
const syncBailHook = new SyncBailHook();
syncBailHook.tap("syncBailHook1", () => {
console.log("syncBailHook1");
return false;
});
syncBailHook.tap("syncBailHook2", () => {
console.log("syncBailHook2");
});
syncBailHook.call();
// syncBailHook1
复制代码
当监听的函数返回 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
异步钩子,能够挂载多个函数,每一个函数都会等待上一个函数执行完,再执行。
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
异步钩子,能够挂载多个函数,只要一个异步函数 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
异步钩子,能够挂载多个函数,只要有一个异步函数 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
异步钩子,能够挂载多个函数,同时触发异步函数执行,和 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
和 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就写得十分不错,这里就不赘述了。
笔者建议按照顺序食用,效果更佳哦。若是本文对您有所帮助,还请不要吝啬您的点赞,每个点赞都是对笔者最大的鼓励。