引言
Tapable 是webpack中的基礎類,類似於node中的EventEmitter,都是註冊監聽,然後收發事件,監聽函式執行的過程,自身可以被繼承或混入到其它模組中。
webpack本質上是一種事件流的機制,它的工作流程就是將各個外掛串聯起來,而實現這一切的核心就是Tapable,webpack中最核心的負責編譯的Compiler和負責建立bundles的Compilation都是Tapable的例項。
所以如果你想了解webpack的原始碼,那麼先來了解一下Tapable這個基礎類顯得尤為必要,那接下來讓我帶你先來了解一下這個類上的常用的 9個鉤子函式吧!
安裝
npm i Tapable -d
複製程式碼
hooks分類
常用的鉤子主要包含以下幾種,分為同步和非同步,非同步又分為併發執行和序列執行,如下圖:
hooks詳解
每一個鉤子都是一個建構函式,所有的建構函式都接收一個可選的引數 這個引數是一個陣列,陣列裡面可以放一些引數 例如:‘name’,當觸發hook事件時 需要傳入name 引數,然後監聽函式中可以獲取name引數。
const hook = new Hook([‘name’])
複製程式碼
那怎麼註冊事件呢?
根據不同的鉤子函式使用不同的方法註冊事件,常用的註冊事件的方法有:tap, tapPromise, tapAsync。
那怎麼觸發事件呢?
同樣的,也是根據不同的鉤子函式 使用的觸發事件的方法也不相同,常用的觸發事件的方法有:call,promise, callAsync。
Sync*型別的hooks
註冊在該鉤子下面的外掛的執行順序都是順序執行。
只能使用tap註冊,不能使用tapPromise和tapAsync註冊
1、SyncHook
序列同步執行 不關心返回值
複製程式碼
用法
const { SyncHook } = require(`tapable`);
const mySyncHook = new SyncHook([`name`, `age`]);
// 為什麼叫tap水龍頭 接收兩個引數,第一個引數是名稱(備註:沒有任何意義) 第二個引數是一個函式 接收一個引數 name這個name和上面的name對應 age和上面的age對應
mySyncHook.tap(`1`, function (name, age) {
console.log(name, age, 1)
return `wrong` // 不關心返回值 這裡寫返回值對結果沒有任何影響
});
mySyncHook.tap(`2`, function (name, age) {
console.log(name, age, 2)
});
mySyncHook.tap(`3`, function (name, age) {
console.log(name, age, 3)
});
mySyncHook.call(`liushiyu`, `18`);
// 執行的結果
// liushiyu 18 1
// liushiyu 18 2
// liushiyu 18 3
複製程式碼
SyncHook原始碼大致實現
class SyncHook {
constructor () {
this.hooks = [];
}
tap (name, fn) {
this.hooks.push(fn)
}
call () {
this.hooks.forEach(hook => hook(...arguments));
}
}
複製程式碼
2、SyncBailHook
序列同步執行 有一個返回值不為null就跳過剩下的邏輯
Bail 是保險的意思 有一個出錯就不往下執行了
複製程式碼
用法 同SyncHook 的用法
const { SyncBailHook } = require(`tapable`);
const mySyncBailHook = new SyncBailHook([`name`, `age`]);
// 輸出結果
// liushiyu 18 1
// return 的值不是null 所以剩下的邏輯就不執行了
複製程式碼
SyncBailHook原始碼大致實現
class SyncBailHook {
constructor () {
this.hooks = [];
}
tap (name, fn) {
this.hooks.push(fn)
}
call () {
for(let i=0; i<this.hooks.length; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
複製程式碼
3、SyncWaterfallHook
下一個任務要拿到上一個任務的返回值
複製程式碼
用法
const { SyncWaterfallHook } = require(`tapable`);
const mySyncWaterfallHook = new SyncWaterfallHook([`name`]);
mySyncWaterfallHook.tap(`1`, function (name) {
console.log(name, `1`)
return `1`
})
mySyncWaterfallHook.tap(`2`, function (name) {
console.log(name, `2`)
return `2`
})
mySyncWaterfallHook.tap(`3`, function (name) {
console.log(name, `3`)
})
mySyncWaterfallHook.call(`liu`)
// 輸出結果
// liu 1
// 1 2
// 2 3
複製程式碼
SyncWaterfallHook原始碼大致實現
class SyncWaterfallHook {
constructor () {
this.hooks = [];
}
tap (name, fn) {
this.hooks.push(fn)
}
call () {
let result = null
for(let i=0; i< this.hooks.length; i++) {
let hook = this.hooks[i];
if (!i) {
result = hook(...arguments)
} else {
result = hook(result)
}
}
}
}
複製程式碼
4、SyncLoopHook
監聽函式返回true表示繼續迴圈,返回undefine表示結束迴圈
複製程式碼
用法
const { SyncLoopHook } = require(`tapable`);
const mySyncLoopHook = new SyncLoopHook([`name`]);
let count = 0;
mySyncLoopHook.tap(`1`, function (name) {
console.log(count++);
if (count < name) {
return true
} else {
return
}
});
mySyncLoopHook.call(`4`);
// 輸出結果
//0
//1
//2
//3
複製程式碼
SyncLoopHook原始碼大致實現
class SyncLoopHook {
constructor () {
this.hook;
}
tap (name, fn) {
this.hook = fn
}
call () {
let result = this.hook(...arguments);
// do{
// result = this.hook(...arguments)
// } while(result)
while(result) {
result = this.hook(...arguments)
}
}
}
複製程式碼
Async*型別的hooks
支援tap、tapPromise、tapAsync註冊
每次都是呼叫tap、tapSync、tapPromise註冊不同型別的外掛鉤子,通過呼叫call、callAsync 、promise方式呼叫。其實呼叫的時候為了按照一定的執行策略執行,呼叫compile方法快速編譯出一個方法來執行這些外掛。
非同步併發執行
1、AsyncParallelHook
非同步併發執行
複製程式碼
用法
有三種註冊方式
// 第一種註冊方式 tap
myAsyncParallelHook.tap(`1`, function (name) {
console.log(1, name)
})
myAsyncParallelHook.tap(`2`, function (name) {
console.log(2, name)
})
myAsyncParallelHook.tap(`3`, function (name) {
console.log(3, name)
})
myAsyncParallelHook.callAsync(`liu`, function () {
console.log(`over`)
});
// 1 `liu`
// 2 `liu`
// 3 `liu`
// over
// 第二種註冊方式 tapAsync 凡事有非同步 必有回撥
console.time(`cost`)
myAsyncParallelHook.tapAsync(`1`, function (name, callback) {
setTimeout(function () {
console.log(1, name)
callback()
}, 1000)
// callback()
})
myAsyncParallelHook.tapAsync(`2`, function (name, callback) {
setTimeout(function () {
console.log(2, name)
callback()
}, 2000)
// callback()
})
myAsyncParallelHook.tapAsync(`3`, function (name, callback) {
setTimeout(function () {
console.log(3, name)
callback()
}, 3000)
})
myAsyncParallelHook.callAsync(`liu`, () => {
console.log(`over`)
console.timeEnd(`cost`)
});
並行執行 花費的總時間是時間最長的那個
//1 `liu`
//2 `liu`
//3 `liu`
//over
//cost: 3005.083ms
// 第三種註冊方式 tapPromise
console.time(`cost`)
myAsyncParallelHook.tapPromise(`1`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1, name)
resolve()
}, 1000)
})
})
myAsyncParallelHook.tapPromise(`2`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2, name)
resolve()
}, 2000)
})
})
myAsyncParallelHook.tapPromise(`3`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(3, name)
resolve()
}, 3000)
})
})
myAsyncParallelHook.promise(`liu`).then(function () {
console.log(`ok`)
console.timeEnd(`cost`)
}, function () {
console.log(`error`)
console.timeEnd(`cost`)
});
// 1 `liu`
// 2 `liu`
// 3 `liu`
// ok
// cost: 3001.903ms
複製程式碼
…
2、AsyncParallelBailHook
有一個失敗了 其他的都不用走了
複製程式碼
用法
const { AsyncParallelBailHook } = require(`tapable`);
const myAsyncParallelBailHook = new AsyncParallelBailHook([`name`]);
// 第一種註冊方式 tap
myAsyncParallelBailHook.tap(`1`, function (name) {
console.log(1, name)
return `wrong`
})
myAsyncParallelBailHook.tap(`2`, function (name) {
console.log(2, name)
})
myAsyncParallelBailHook.tap(`3`, function (name) {
console.log(3, name)
})
myAsyncParallelBailHook.callAsync(`liu`, function () {
console.log(`over`)
});
// 1 `liu`
// over
// 第二種註冊方式 tapAsync 凡事有非同步 必有回撥
console.time(`cost`)
myAsyncParallelBailHook.tapAsync(`1`, function (name, callback) {
setTimeout(function () {
console.log(1, name)
return `wrong`;// 最後的回撥就不會呼叫了
callback()
}, 1000)
// callback()
})
myAsyncParallelBailHook.tapAsync(`2`, function (name, callback) {
setTimeout(function () {
console.log(2, name)
callback()
}, 2000)
// callback()
})
myAsyncParallelBailHook.tapAsync(`3`, function (name, callback) {
setTimeout(function () {
console.log(3, name)
callback()
}, 3000)
})
myAsyncParallelBailHook.callAsync(`liu`, () => {
console.log(`over`)
console.timeEnd(`cost`)
});
// 1 `liu`
// 2 `liu`
// 3 `liu`
// 第三種註冊方式 tapPromise
console.time(`cost`)
myAsyncParallelBailHook.tapPromise(`1`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1, name)
reject(`wrong`);// reject()的引數是一個不為null的引數時,最後的回撥就不會再呼叫了
}, 1000)
})
})
myAsyncParallelBailHook.tapPromise(`2`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2, name)
resolve()
}, 2000)
})
})
myAsyncParallelBailHook.tapPromise(`3`, function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(3, name)
resolve()
}, 3000)
})
})
myAsyncParallelBailHook.promise(`liu`).then(function () {
console.log(`ok`)
console.timeEnd(`cost`)
}, function () {
console.log(`error`)
console.timeEnd(`cost`)
});
// 1 `liu`
// error
// cost: 1006.030ms
// 2 `liu`
// 3 `liu`
複製程式碼
非同步序列執行
1、AsyncSeriesHook
用法
let { AsyncSeriesHook } = require(`tapable`);
let myAsyncSeriesHook = new AsyncSeriesHook([`name`]);
console.time(`coast`)
myAsyncSeriesHook.tapAsync(`1`, function (name, cb) {
setTimeout(function () {
console.log(`1`, name)
cb()
}, 1000)
});
myAsyncSeriesHook.tapAsync(`2`, function (name, cb) {
setTimeout(function () {
console.log(`2`, name)
cb()
}, 2000)
});
myAsyncSeriesHook.tapAsync(`3`, function (name, cb) {
setTimeout(function () {
console.log(`3`, name)
cb()
}, 3000)
});
myAsyncSeriesHook.callAsync(`liu`, function () {
console.log(`over`)
console.timeEnd(`coast`)
})
// 1 liu
// 2 liu
// 3 liu
// over
// coast: 6010.515ms 非同步序列執行消耗的時間是所有的總和
複製程式碼
AsyncSeriesHook原始碼大致實現
class AsyncSeriesHook{
constructor() {
this.hooks = [];
}
tapAsync () {
this.hooks.push(arguments[arguments.length-1]);
}
callAsync () {
let args = Array.from(arguments); //將傳進來的引數轉化為陣列
let done = args.pop(); // 取出陣列的最後一項 即成功後的回撥函式
let index = 0;
let that = this;
function next(err) {
if (err) return done();
let fn = that.hooks[index++];
fn ? fn(...args, next) : done();
}
next()
}
}
複製程式碼
2、AsyncSeriesBailHook
用法
// 非同步序列執行
let { AsyncSeriesBailHook } = require(`tapable`);
let myAsyncSeriesBailHook = new AsyncSeriesBailHook([`name`]);
console.time(`coast`)
myAsyncSeriesBailHook.tapAsync(`1`, function (name, cb) {
setTimeout(function () {
console.log(`1`, name)
cb(`wrong`)
}, 1000)
});
myAsyncSeriesBailHook.tapAsync(`2`, function (name, cb) {
setTimeout(function () {
console.log(`2`, name)
cb()
}, 2000)
});
myAsyncSeriesBailHook.tapAsync(`3`, function (name, cb) {
setTimeout(function () {
console.log(`3`, name)
cb()
}, 3000)
});
myAsyncSeriesBailHook.callAsync(`liu`, function () {
console.log(`over`)
console.timeEnd(`coast`)
})
// 1 liu
// over
// coast: 1004.175ms
複製程式碼
AsyncSeriesBailHook 原始碼的大致實現
class AsyncSeriesBailHook{
constructor() {
this.hooks = [];
}
tapAsync () {
this.hooks.push(arguments[arguments.length-1]);
}
callAsync () {
let args = Array.from(arguments); //將傳進來的引數轉化為陣列
let done = args.pop(); // 取出陣列的最後一項 即成功後的回撥函式
let index = 0;
let that = this;
function next(err) {
if (err) return done();
let fn = that.hooks[index++];
fn ? fn(...args, next) : done();
}
next()
}
}
複製程式碼
3、AsyncSeriesWaterfallHook
下一個任務要拿到上一個任務的返回值
複製程式碼
用法
// 非同步序列執行
let { AsyncSeriesWaterfallHook } = require(`tapable`);
let myAsyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook([`name`]);
console.time(`coast`)
myAsyncSeriesWaterfallHook.tapAsync(`1`, function (name, cb) {
setTimeout(function () {
console.log(`1`, name)
cb(null, `aa`)
}, 1000)
});
myAsyncSeriesWaterfallHook.tapAsync(`2`, function (name, cb) {
setTimeout(function () {
console.log(`2`, name)
cb(null, `bb`)
}, 2000)
});
myAsyncSeriesWaterfallHook.tapAsync(`3`, function (name, cb) {
setTimeout(function () {
console.log(`3`, name)
cb(null, `cc`)
}, 3000)
});
myAsyncSeriesWaterfallHook.callAsync(`liu`, function () {
console.log(`over`)
console.timeEnd(`coast`)
})
// 1 liu
// 2 aa
// 3 bb
// over
// coast: 6011.774ms
複製程式碼
AsyncSeriesWaterfallHook 原始碼的大致實現
class AsyncSeriesWaterfallHook{
constructor() {
this.hooks = [];
}
tapAsync () {
this.hooks.push(arguments[arguments.length-1]);
}
callAsync () {
let args = Array.from(arguments); //將傳進來的引數轉化為陣列
let done = args.pop(); // 取出陣列的最後一項 即成功後的回撥函式
let index = 0;
let that = this;
function next(err, data) {
if(index>=that.hooks.length) return done();
if (err) return done(err);
let fn = that.hooks[index++];
if (index == 1) {
fn(...args, next)
} else {
fn(data, next)
}
}
next()
}
}
複製程式碼