Tapable
簡介webpack
本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable
,webpack
中最核心的負責編譯的Compiler
和負責建立bundles的Compilation
都是Tapable
的實例。本文主要介紹一下Tapable中的鉤子函數。javascript
tapable包暴露出不少鉤子類,這些類能夠用來爲插件建立鉤子函數,主要包含如下幾種:java
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
複製代碼
全部鉤子類的構造函數都接收一個可選的參數,這個參數是一個由字符串參數組成的數組,以下:webpack
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
複製代碼
下面咱們就詳細介紹一下鉤子的用法,以及一些鉤子類實現的原理。web
經常使用的鉤子主要包含如下幾種,分爲同步和異步,異步又分爲併發執行和串行執行,以下圖: 數組
首先,總體感覺下鉤子的用法,以下序號 | 鉤子名稱 | 執行方式 | 使用要點 |
---|---|---|---|
1 | SyncHook | 同步串行 | 不關心監聽函數的返回值 |
2 | SyncBailHook | 同步串行 | 只要監聽函數中有一個函數的返回值不爲 null ,則跳過剩下全部的邏輯 |
3 | SyncWaterfallHook | 同步串行 | 上一個監聽函數的返回值能夠傳給下一個監聽函數 |
4 | SyncLoopHook | 同步循環 | 當監聽函數被觸發的時候,若是該監聽函數返回true 時則這個監聽函數會反覆執行,若是返回 undefined 則表示退出循環 |
5 | AsyncParallelHook | 異步併發 | 不關心監聽函數的返回值 |
6 | AsyncParallelBailHook | 異步併發 | 只要監聽函數的返回值不爲 null ,就會忽略後面的監聽函數執行,直接跳躍到callAsync 等觸發函數綁定的回調函數,而後執行這個被綁定的回調函數 |
7 | AsyncSeriesHook | 異步串行 | 不關係callback() 的參數 |
8 | AsyncSeriesBailHook | 異步串行 | callback() 的參數不爲null ,就會直接執行callAsync 等觸發函數綁定的回調函數 |
9 | AsyncSeriesWaterfallHook | 異步串行 | 上一個監聽函數的中的callback(err, data) 的第二個參數,能夠做爲下一個監聽函數的參數 |
SyncHook
不關心監聽函數的返回值promise
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //全部的構造函數都接收一個可選的參數,這個參數是一個字符串的數組。
// 訂閱
queue.tap('1', function (name, name2) {// tap 的第一個參數是用來標識訂閱的函數的
console.log(name, name2, 1);
return '1'
});
queue.tap('2', function (name) {
console.log(name, 2);
});
queue.tap('3', function (name) {
console.log(name, 3);
});
// 發佈
queue.call('webpack', 'webpack-cli');// 發佈的時候觸發訂閱的函數 同時傳入參數
// 執行結果:
/* webpack undefined 1 // 傳入的參數須要和new實例的時候保持一致,不然獲取不到多傳的參數 webpack 2 webpack 3 */
複製代碼
class SyncHook_MY{
constructor(){
this.hooks = [];
}
// 訂閱
tap(name, fn){
this.hooks.push(fn);
}
// 發佈
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
複製代碼
SyncBailHook
只要監聽函數中有一個函數的返回值不爲 null
,則跳過剩下全部的邏輯bash
const {
SyncBailHook
} = require("tapable");
let queue = new SyncBailHook(['name']);
queue.tap('1', function (name) {
console.log(name, 1);
});
queue.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue.tap('3', function (name) {
console.log(name, 3);
});
queue.call('webpack');
// 執行結果:
/* webpack 1 webpack 2 */
複製代碼
class SyncBailHook_MY {
constructor() {
this.hooks = [];
}
// 訂閱
tap(name, fn) {
this.hooks.push(fn);
}
// 發佈
call() {
for (let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
複製代碼
SyncWaterfallHook
上一個監聽函數的返回值能夠傳給下一個監聽函數併發
const {
SyncWaterfallHook
} = require("tapable");
let queue = new SyncWaterfallHook(['name']);
// 上一個函數的返回值能夠傳給下一個函數
queue.tap('1', function (name) {
console.log(name, 1);
return 1;
});
queue.tap('2', function (data) {
console.log(data, 2);
return 2;
});
queue.tap('3', function (data) {
console.log(data, 3);
});
queue.call('webpack');
// 執行結果:
/*
webpack 1
1 2
2 3
*/
複製代碼
class SyncWaterfallHook_MY{
constructor(){
this.hooks = [];
}
// 訂閱
tap(name, fn){
this.hooks.push(fn);
}
// 發佈
call(){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...arguments): hook(result);
}
}
}
複製代碼
SyncLoopHook
當監聽函數被觸發的時候,若是該監聽函數返回true
時則這個監聽函數會反覆執行,若是返回 undefined
則表示退出循環異步
const {
SyncLoopHook
} = require("tapable");
let queue = new SyncLoopHook(['name']);
let count = 3;
queue.tap('1', function (name) {
console.log('count: ', count--);
if (count > 0) {
return true;
}
return;
});
queue.call('webpack');
// 執行結果:
/* count: 3 count: 2 count: 1 */
複製代碼
class SyncLoopHook_MY {
constructor() {
this.hook = null;
}
// 訂閱
tap(name, fn) {
this.hook = fn;
}
// 發佈
call() {
let result;
do {
result = this.hook(...arguments);
} while (result)
}
}
複製代碼
AsyncParallelHook
不關心監聽函數的返回值。async
有三種註冊/發佈的模式,以下:
異步訂閱 | 調用方法 |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
const {
AsyncParallelHook
} = require("tapable");
let queue1 = new AsyncParallelHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// 執行結果
/* webpack 1 webpack 2 webpack 3 cost: 4.520ms */
複製代碼
let queue2 = new AsyncParallelHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// 執行結果
/* webpack 1 webpack 2 webpack 3 over time: 3004.411ms */
複製代碼
let queue3 = new AsyncParallelHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
resolve();
}, 2000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
/* webpack 1 webpack 2 webpack 3 over cost3: 3007.925ms */
複製代碼
AsyncParallelBailHook
只要監聽函數的返回值不爲 null
,就會忽略後面的監聽函數執行,直接跳躍到callAsync
等觸發函數綁定的回調函數,而後執行這個被綁定的回調函數。
let queue1 = new AsyncParallelBailHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// 執行結果:
/* webpack 1 webpack 2 cost: 4.975ms */
複製代碼
let queue2 = new AsyncParallelBailHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
return 'wrong';// 最後的回調就不會調用了
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// 執行結果:
/* webpack 1 webpack 2 webpack 3 */
複製代碼
let queue3 = new AsyncParallelBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('2', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
reject('wrong');// reject()的參數是一個不爲null的參數時,最後的回調就不會再調用了
}, 2000);
});
});
queue3.tapPromise('3', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
// 執行結果:
/* webpack 1 webpack 2 error cost3: 2009.970ms webpack 3 */
複製代碼
AsyncSeriesHook
不關係callback()
的參數
const {
AsyncSeriesHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('zfpx', err => {
console.log(err);
console.timeEnd('cost1');
});
// 執行結果
/* 1 2 3 undefined cost1: 3.933ms */
複製代碼
let queue2 = new AsyncSeriesHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', (err) => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 執行結果
/* webpack 1 webpack 2 webpack 3 undefined over cost2: 6019.621ms */
複製代碼
let queue3 = new AsyncSeriesHook(['name']);
console.time('cost3');
queue3.tapPromise('1',function(name){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 1);
resolve();
},1000)
});
});
queue3.tapPromise('2',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 2);
resolve();
},2000)
});
});
queue3.tapPromise('3',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 3);
resolve();
},3000)
});
});
queue3.promise('webapck').then(err=>{
console.log(err);
console.timeEnd('cost3');
});
// 執行結果
/* webapck 1 webapck 2 webapck 3 undefined cost3: 6021.817ms */
複製代碼
class AsyncSeriesHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
var slef = this;
var args = Array.from(arguments);
let done = args.pop();
let idx = 0;
function next(err) {
// 若是next的參數有值,就直接跳躍到 執行callAsync的回調函數
if (err) return done(err);
let fn = slef.hooks[idx++];
fn ? fn(...args, next) : done();
}
next();
}
}
複製代碼
AsyncSeriesBailHook
callback()
的參數不爲null
,就會直接執行callAsync
等觸發函數綁定的回調函數
const {
AsyncSeriesBailHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesBailHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.timeEnd('cost1');
});
// 執行結果:
/* 1 null cost1: 3.979ms */
複製代碼
let queue2 = new AsyncSeriesBailHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log(name, 1);
callback();
}, 1000)
});
queue2.tapAsync('2', function (name, callback) {
setTimeout(function () {
console.log(name, 2);
callback('wrong');
}, 2000)
});
queue2.tapAsync('3', function (name, callback) {
setTimeout(function () {
console.log(name, 3);
callback();
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 執行結果
/* webpack 1 webpack 2 wrong over cost2: 3014.616ms */
複製代碼
let queue3 = new AsyncSeriesBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 1);
resolve();
}, 1000)
});
});
queue3.tapPromise('2', function (name, callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 2);
reject();
}, 2000)
});
});
queue3.tapPromise('3', function (name, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(name, 3);
resolve();
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.log('over');
console.timeEnd('cost3');
}, err => {
console.log(err);
console.log('error');
console.timeEnd('cost3');
});
// 執行結果:
/* webpack 1 webpack 2 undefined error cost3: 3017.608ms */
複製代碼
AsyncSeriesWaterfallHook
上一個監聽函數的中的callback(err, data)
的第二個參數,能夠做爲下一個監聽函數的參數
const {
AsyncSeriesWaterfallHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(name, 1);
return 'lily'
});
queue1.tap('2', function (data) {
console.log(2, data);
return 'Tom';
});
queue1.tap('3', function (data) {
console.log(3, data);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost1');
});
// 執行結果:
/*
webpack 1
2 'lily'
3 'Tom'
null
over
cost1: 5.525ms
*/
複製代碼
let queue2 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log('1: ', name);
callback(null, 2);
}, 1000)
});
queue2.tapAsync('2', function (data, callback) {
setTimeout(function () {
console.log('2: ', data);
callback(null, 3);
}, 2000)
});
queue2.tapAsync('3', function (data, callback) {
setTimeout(function () {
console.log('3: ', data);
callback(null, 3);
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 執行結果:
/* 1: webpack 2: 2 3: 3 null over cost2: 6016.889ms */
複製代碼
let queue3 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1:', name);
resolve('1');
}, 1000)
});
});
queue3.tapPromise('2', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('2:', data);
resolve('2');
}, 2000)
});
});
queue3.tapPromise('3', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('3:', data);
resolve('over');
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.timeEnd('cost3');
}, err => {
console.log(err);
console.timeEnd('cost3');
});
// 執行結果:
/* 1: webpack 2: 1 3: 2 over cost3: 6016.703ms */
複製代碼
class AsyncSeriesWaterfallHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
let self = this;
var args = Array.from(arguments);
let done = args.pop();
console.log(args);
let idx = 0;
let result = null;
function next(err, data) {
if (idx >= self.hooks.length) return done();
if (err) {
return done(err);
}
let fn = self.hooks[idx++];
if (idx == 1) {
fn(...args, next);
} else {
fn(data, next);
}
}
next();
}
}
複製代碼