webpack4.0源碼分析之Tapable

1 Tapable簡介

webpack本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapablewebpack中最核心的負責編譯的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

2 hooks概覽

經常使用的鉤子主要包含如下幾種,分爲同步和異步,異步又分爲併發執行和串行執行,以下圖:數組

hooks 首先,總體感覺下鉤子的用法,以下

 

序號 鉤子名稱 執行方式 使用要點
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)的第二個參數,能夠做爲下一個監聽函數的參數

3 鉤子

sync* 鉤子

同步串行

(1) SyncHook

不關心監聽函數的返回值promise

  • usage
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)); } } 複製代碼

(2) SyncBailHook

只要監聽函數中有一個函數的返回值不爲 null,則跳過剩下全部的邏輯bash

  • usage
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; } } } } 複製代碼

(3) SyncWaterfallHook

上一個監聽函數的返回值能夠傳給下一個監聽函數併發

  • usage
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); } } } 複製代碼

(4) SyncLoopHook

當監聽函數被觸發的時候,若是該監聽函數返回true時則這個監聽函數會反覆執行,若是返回 undefined 則表示退出循環異步

  • usage
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) } } 複製代碼

async* 鉤子

異步並行

(1) AsyncParallelHook

不關心監聽函數的返回值。async

有三種註冊/發佈的模式,以下:

異步訂閱 調用方法
tap callAsync
tapAsync callAsync
tapPromise promise
  • usage - tap
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 */ 複製代碼
  • usage - tapAsync
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 */ 複製代碼
  • usage - promise
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 */ 複製代碼

(2) AsyncParallelBailHook

只要監聽函數的返回值不爲 null,就會忽略後面的監聽函數執行,直接跳躍到callAsync等觸發函數綁定的回調函數,而後執行這個被綁定的回調函數。

  • usage - tap
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 */ 複製代碼
  • usage - tapAsync
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 */ 複製代碼
  • usage - promise
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 */ 複製代碼

異步串行

(1) AsyncSeriesHook

不關係callback()的參數

  • usage - tap
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 */ 複製代碼
  • usage - tapAsync
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 */ 複製代碼
  • usage - promise
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(); } } 複製代碼

(2) AsyncSeriesBailHook

callback()的參數不爲null,就會直接執行callAsync等觸發函數綁定的回調函數

  • usage - tap
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 */ 複製代碼
  • usage - tapAsync
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 */ 複製代碼
  • usage - promise
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 */ 複製代碼

(3) AsyncSeriesWaterfallHook

上一個監聽函數的中的callback(err, data)的第二個參數,能夠做爲下一個監聽函數的參數

  • usage - tap
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 */ 複製代碼
  • usage - tapAsync
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 */ 複製代碼
  • usage - promise
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

相關文章
相關標籤/搜索