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(); } }
連接:https://juejin.im/post/5abf33f16fb9a028e46ec352