webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。

Webpack 可以认为是一种基于事件流的编程范例,内部的工作流程都是基于 插件 机制串接起来

而将这些插件粘合起来的就是webpack自己写的基础类 Tapable 是,plugin方法就是该类暴露出来的;

基于该类规范而其的 Webpack 体系保证了插件的有序性,使得整个系统非常有弹性,扩展性很好;然而有一个致命的缺点就是调试、看源码真是很痛苦,各种跳来跳去;(基于事件流的写法,和程序语言中的 goto 语句很类似)

在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括

  • plugin(name:string, handler:function)注册插件到Tapable对象中

  • apply(…pluginInstances: (AnyPlugin|function)[])调用插件的定义,将事件监听器注册到Tapable实例注册表中

  • applyPlugins*(name:string, …)多种策略细致地控制事件的触发,包括applyPluginsAsync、applyPluginsParallel等方法实现对事件触发的控制,实现

    1. 多个事件连续顺序执行

    2. 并行执行

    3. 异步执行

    4. 一个接一个地执行插件,前面的输出是后一个插件的输入的瀑布流执行顺序

    5. 在允许时停止执行插件,即某个插件返回了一个undefined的值,即退出执行

我们可以看到,Tapable就像nodejs中EventEmitter,提供对事件的注册on和触发emit,理解它很重要

Tapable中的钩子函数

tapable包暴露出很多钩子类,这些类可以用来为插件创建钩子函数。

从 https://github.com/webpack/tapable,lib/index.js看出,tapable提供了九种钩子:

const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
 } = require("tapable");

所有钩子类的构造函数都接收一个可选的参数,这个参数是一个由字符串参数组成的数组,如下:

const hook = new SyncHook(["arg1", "arg2", "arg3"]);

new Hook 新建钩子

  • tapable 暴露出来的都是类方法,new 一个类方法获得我们需要的钩子。

  • class 接受数组参数options,非必传。类方法会根据传参,接受同样数量的参数。

下面我们就详细介绍一下钩子的用法,以及一些钩子类实现的原理。

hooks概览

常用的钩子主要包含以下几种,分为同步和异步,异步又分为并发执行和串行执行,如下图:

首先,整体感受下钩子的用法,如下

钩子名称 执行方式 使用要点
SyncHook 同步串行 不关心监听函数的返回值
SyncBailHook 同步串行 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑
SyncWaterfallHook 同步串行 上一个监听函数的返回值可以传给下一个监听函数
SyncLoopHook 同步循环 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
AsyncParallelHook 异步并发 不关心监听函数的返回值
AsyncParallelBailHook 异步并发 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
AsyncSeriesHook 异步串行 不关系callback()的参数
AsyncSeriesBailHook 异步串行 callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数
AsyncSeriesWaterfallHook 异步串行 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

钩子分为同步VS 异步,细分为  并行VS串行,在根据返回值,细分为不同种类。

  • BasicHook: 执行每一个,不关心函数的返回值,有 SyncHook、AsyncParallelHook、AsyncSeriesHook。

  • BailHook: 顺序执行 Hook,遇到第一个结果 result !== undefined 则返回,不再继续执行。有:SyncBailHook、AsyncSeriseBailHook, AsyncParallelBailHook。

  • WaterfallHook: 类似于 reduce,如果前一个 Hook 函数的结果 result !== undefined,则 result 会作为后一个 Hook 函数的第一个参数。既然是顺序执行,那么就只有 Sync 和 AsyncSeries 类中提供这个Hook:SyncWaterfallHook,AsyncSeriesWaterfallHook

  • LoopHook: 不停的循环执行 Hook,直到所有函数结果 result === undefined。同样的,由于对串行性有依赖,所以只有 SyncLoopHook 和 AsyncSeriseLoopHook (PS:暂时没看到具体使用 Case)

Tabable 关键词解析

type function
Hook 所有钩子的后缀
Waterfall 同步方法,但是它会传值给下一个函数
Bail 熔断:当函数有任何返回值,就会在当前执行函数停止
Loop 监听函数返回true表示继续循环,返回undefine表示结束循环
Sync 同步方法
AsyncSeries 异步串行钩子
AsyncParallel 异步并行执行钩子

我们可以根据自己的开发需求,选择适合的同步/异步钩子。

Tapable Hook类

class Hook {
constructor(args) {
if(!Array.isArray(args)) args = [];
this._args = args; // 实例钩子的时候的string类型的数组
this.taps = []; // 消费者
this.interceptors = []; // interceptors
this.call = this._call =  // 以sync类型方式来调用钩子
this._createCompileDelegate("call", "sync");
this.promise = 
this._promise = // 以promise方式
this._createCompileDelegate("promise", "promise");
this.callAsync = 
this._callAsync = // 以async类型方式来调用
this._createCompileDelegate("callAsync", "async");
this._x = undefined; // 
} _createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
} _createCompileDelegate(name, type) {
const lazyCompileHook = (...args) => {
this[name] = this._createCall(type);
return this[name](...args);
};
return lazyCompileHook;
}
// 调用tap 类型注册
tap(options, fn) {
// ...
options = Object.assign({ type: "sync", fn: fn }, options);
// ...
this._insert(options);  // 添加到 this.taps中
}
// 注册 async类型的钩子
tapAsync(options, fn) {
// ...
options = Object.assign({ type: "async", fn: fn }, options);
// ...
this._insert(options); // 添加到 this.taps中
}
注册 promise类型钩子
tapPromise(options, fn) {
// ...
options = Object.assign({ type: "promise", fn: fn }, options);
// ...
this._insert(options); // 添加到 this.taps中
} }

每次都是调用tap、tapSync、tapPromise注册不同类型的插件钩子,通过调用call、callAsync 、promise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。

tabpack提供了同步&异步绑定钩子的方法,并且他们都有绑定事件和执行事件对应的方法。

Async* Sync*
绑定:tapAsync/tapPromise/tap 绑定:tap
执行:callAsync/promise 执行:call

call/callAsync 执行绑定事件

const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

//绑定事件到webapck事件流
hook1.tap('hook1', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3)) //1,2,3 //执行绑定的事件
hook1.call(1,2,3)

举个栗子

  • 定义一个Car方法,在内部hooks上新建钩子。分别是同步钩子 accelerate、break(accelerate接受一个参数)、异步钩子calculateRoutes

  • 使用钩子对应的绑定和执行方法

  • calculateRoutes使用tapPromise可以返回一个promise对象。

//引入tapable
const {
    SyncHook,
    AsyncParallelHook
} = require('tapable'); //创建类
class Car {
    constructor() {
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            break: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
        };
    }
} const myCar = new Car(); //绑定同步钩子
myCar.hooks.break.tap("WarningLampPlugin", () => console.log('WarningLampPlugin')); //绑定同步钩子 并传参
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`)); //绑定一个异步Promise钩子
myCar.hooks.calculateRoutes.tapPromise("calculateRoutes tapPromise", (source, target, routesList, callback) => {
    // return a promise
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log(`tapPromise to ${source}${target}${routesList}`)
            resolve();
        },1000)
    })
}); //执行同步钩子
myCar.hooks.break.call();
myCar.hooks.accelerate.call('hello'); console.time('cost'); //执行异步钩子
myCar.hooks.calculateRoutes.promise('i', 'love', 'tapable').then(() => {
    console.timeEnd('cost');
}, err => {
    console.error(err);
    console.timeEnd('cost');
})

运行结果

WarningLampPlugin
Accelerating to hello
tapPromise to ilovetapable
cost: 1003.898ms

calculateRoutes也可以使用tapAsync绑定钩子,注意:此时用callback结束异步回调。

myCar.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => {
    // return a promise
    setTimeout(() => {
        console.log(`tapAsync to ${source}${target}${routesList}`)
        callback();
    }, 2000)
}); myCar.hooks.calculateRoutes.callAsync('i', 'like', 'tapable', err => {
    console.timeEnd('cost');
    if(err) console.log(err)
})

sync* 钩子

对于Sync*类型的钩子来说。

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。

  • 只能使用tap注册,不能使用tapPromise和tapAsync注册

// 所有的钩子都继承于Hook
class Sync* extends Hook { 
tapAsync() { // Sync*类型的钩子不支持tapAsync
throw new Error("tapAsync is not supported on a Sync*");
}
tapPromise() {// Sync*类型的钩子不支持tapPromise
throw new Error("tapPromise is not supported on a Sync*");
}
compile(options) { // 编译代码来按照一定的策略执行Plugin
factory.setup(this, options);
return factory.create(options);
}
}

同步串行

SyncHook

不关心监听函数的返回值

SyncHook的用法及实现
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
*/

通过上面如何使用的案例看出,主要是三个步骤(以同步钩子为例)

  1. new SyncHook(['xx']) 实例化Hook

  2. hook.tap('xxx', () => {}) 注册钩子

  3. hook.call(args) 调用钩子

原理

SyncHook是一个很典型的通过发布订阅方式实现的

class SyncHook_MY{
    constructor(){
        this.hooks = [];
    }     // 订阅
    tap(name, fn){
        this.hooks.push(fn);
    }     // 发布
    call(){
        this.hooks.forEach(hook => hook(...arguments));
    }
}

SyncBailHook

只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑

SyncBailHook的用法及实现

SyncBailHook为同步串行的执行关系,只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑,用法如下:

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
*/
原理
// 钩子是同步的,bail -> 保险
class SyncBailHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    // 当前函数的返回值
    let ret;
    // 当前要先执行第一个
    let index = 0;
    do {
      ret = this.tasks[index++](...args);
    } while (ret === undefined && index < this.tasks.length);
  }
}

SyncWaterfallHook的用法及实现

上一个监听函数的返回值可以传给下一个监听函数

SyncWaterfallHook为同步串行的执行关系,上一个监听函数的返回值可以传给下一个监听函数,用法如下:

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
*/
SyncWaterfallHook的实现:
// 钩子是同步的
class SyncWaterfallHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    let [first, ...others] = this.tasks;
    let ret = first(...args);
    others.reduce((a, b) => {
      return b(a);
    }, ret);
  }
}
// 简化版
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;
            }
        }
    }
}

SyncLoopHook的用法及实现

当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环

SyncLoopHook为同步循环的执行关系,当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环,用法如下:

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
*/
SyncLoopHook的实现:
// 钩子是同步的
class SyncLoopHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach(task => {
      let ret;
      do {
        ret = task(...args);
      } while (ret != undefined);
    });
  }
}

async* 钩子

对于Async*类型钩子

  • 支持tap、tapPromise、tapAsync注册

有三种注册/发布的模式,如下:

异步订阅 调用方法
tap callAsync
tapAsync callAsync
tapPromise promise

异步并行

AsyncParallelHook的用法及实现

不关心监听函数的返回值。

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
*/
AsyncParallelHook的实现:
class SyncParralleHook {  constructor() {    this.tasks = [];
  }  tapAsync(name, task) {    this.tasks.push(task);
  }  callAsync(...args) {    // 拿出最终的函数
    let finalCallBack = args.pop();    let index = 0;    // 类似Promise.all
    let done = () => {
      index++;      if (index === this.tasks.length) {
        finalCallBack();
      }
    };    this.tasks.forEach(task => {
      task(...args, done);
    });
  }
}

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
*/
AsyncSeriesHook的实现:
class SyncSeriesHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = () => {
      if (this.tasks.length === index) return finalCallback();
      let task = this.tasks[index++];
      task(...args, next);
    };
    next();
  }
}

异步串行

AsyncSeriesWaterfallHook的用法及实现

不关系callback()的参数

AsyncSeriesWaterfallHook为异步串行的执行关系,上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

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();
    }
}

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
*/

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();
    }
}

参考文章:

webpack插件机制之Tapable https://juejin.cn/post/6844903774645911566

干货!撸一个webpack插件(内含tapable详解+webpack流程) https://juejin.cn/post/6844903713312604173

webpack详解 https://juejin.cn/post/6844903573675835400

webpack4.0源码分析之Tapable https://juejin.cn/post/6844903588112629767

Webpack 源码(一)—— Tapable 和 事件流 https://segmentfault.com/a/1190000008060440

转载本站文章《webpack原理(3):Tapable源码分析及钩子函数作用分析》,
请注明出处:https://www.zhoulujun.cn/html/tools/Bundler/webpackTheory/8754.html

webpack原理(3):Tapable源码分析及钩子函数作用分析的更多相关文章

  1. webpack核心模块tapable源码解析

    上一篇文章我写了tapable的基本用法,我们知道他是一个增强版版的发布订阅模式,本文想来学习下他的源码.tapable的源码我读了一下,发现他的抽象程度比较高,直接扎进去反而会让人云里雾里的,所以本 ...

  2. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  3. 65、Spark Streaming:数据接收原理剖析与源码分析

    一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...

  4. webpack4核心模块tapable源码解析

    _ 阅读目录 一:理解Sync类型的钩子 1. SyncHook.js 2. SyncBailHook.js 3. SyncWaterfallHook.js 4. SyncLoopHook.js 二: ...

  5. 深入理解NIO(三)—— NIO原理及部分源码的解析

    深入理解NIO(三)—— NIO原理及部分源码的解析 欢迎回到淦™的源码看爆系列 在看完前面两个系列之后,相信大家对NIO也有了一定的理解,接下来我们就来深入源码去解读它,我这里的是OpenJDK-8 ...

  6. sobel算子原理及opencv源码实现

    sobel算子原理及opencv源码实现 简要描述 sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测. 原理 算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去 ...

  7. [源码]Delphi源码免杀之函数动态调用 实现免杀的下载者

    [免杀]Delphi源码免杀之函数动态调用 实现免杀的下载者 2013-12-30 23:44:21         来源:K8拉登哥哥's Blog   自己编译这份代码看看 过N多杀软  没什么技 ...

  8. vue源码cached高阶函数解析

    1.源代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <tit ...

  9. JMeter 源码二次开发函数示例

    JMeter 源码二次开发函数示例 一.JMeter 5.0 版本 实际测试中,依靠jmeter自带的函数已经无法满足我们需求,这个时候就需要二次开发.本次导入的是jmeter 5.0的源码进行实际的 ...

  10. Locust源码目录结构及模块作用

    Locust源码目录结构及模块作用如下: 参考文章:https://blog.csdn.net/biheyu828/article/details/84031942

随机推荐

  1. 【matplotlib 实战】--雷达图

    雷达图(Radar Chart),也被称为蛛网图或星型图,是一种用于可视化多个变量之间关系的图表形式.雷达图是一种显示多变量数据的图形方法.通常从同一中心点开始等角度间隔地射出三个以上的轴,每个轴代表 ...

  2. 实用的命令行终端增强软件:Tabby

    还是那句话:出众的软件有很多,适合自己的才是最好的. 一.软件介绍 Tabby是一个开源免费软件,支持Windows.macOS和Linux系统.它提供了一个高度可定制的终端界面,可以通过多种方式添加 ...

  3. animate.css 动画种类(详细)

    作者:WangMin 格言:努力做好自己喜欢的每一件事 以下为各种动画类型包含的不同动画效果类,仅供参考.具体可查看animate.css 官网. bounce 弹跳 2. flash 闪烁 3. p ...

  4. VS2022修改cs文件模板

    在路径:C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\ItemTemplates\AspNetCore\Co ...

  5. Util应用框架基础(四) - 验证

    本节介绍Util应用框架如何进行验证. 概述 验证是业务健壮性的基础. .Net 提供了一套称为 DataAnnotations 数据注解的方法,可以对属性进行一些基本验证,比如必填项验证,长度验证等 ...

  6. c#中命令模式详解

    基本介绍:   命令模式,顾名思义就是将命令抽象化,然后将请求者和接收者通过命令进行绑定.   而命令的请求者只管下达命令,命令的接收者只管执行命令.   从而实现了解耦,请求者和接受者二者相对独立. ...

  7. A-B数对 (hash映射)

    题目大意: 第一行输入N,C 第二行输入n个数字 输出,求A - B = C的数对个数 样例 4 1 1 1 2 3 输出 3 思路:用STL容器map,map<num, times>,建 ...

  8. C/C++ 运用VMI接口查询系统信息

    Windows Management Instrumentation(WMI)是一种用于管理和监视Windows操作系统的框架.它为开发人员.系统管理员和自动化工具提供了一种标准的接口,通过这个接口, ...

  9. 聊聊分布式 SQL 数据库Doris(八)

    稀疏索引 密集索引:文件中的每个搜索码值都对应一个索引值,就是叶子节点保存了整行. 稀疏索引:文件只为索引码的某些值建立索引项. 稀疏索引的创建过程包括将集合中的元素分段,并给每个分段中的最小元素创建 ...

  10. Django学习(二) 之 模板的使用

    写在前面 昨晚应该是睡的最好一天吧,最近一个月睡眠好差,睡不着不说,而且半夜总醒,搞的第二天就会超没精神. 昨天下午去姐姐家,小外甥直接进屋就问我说: 老舅,你都很长时间没来啦,**(前女友)怎么哪去 ...