钩子注入的原理机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了钩子注入的原理机制相关的知识,希望对你有一定的参考价值。

参考技术A

钩子是WINDOWS中消息处理机制的一个要点,通过安装各种钩子,应用程序能够设置相应的子例程来监视系统里的消息传递以及在这些消息到达目标窗口程序之前处理它们。 钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息,鼠标钩子可以截获鼠标消息,外壳钩子可以截获启动和关闭应用程序的消息,日志钩子可以监视和记录输入事件。
若在dll中使用SetWindowsHookEx设置一全局钩子,系统会将其加载入使用user32的进程中,因而它也可被利用为无进程木马的进程注入手段。
木马编写者首先把一个实际为木马主体的dll文件载入内存,然后通过“线程注射”技术将其注入其他进程的内存空间,最后这个dll里的代码就成为其他进程的一部分来实现了自身的隐藏执行,通过调用“hook”机制。
钩子(hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
这个dll木马便实现了监视用户的输入输出操作,截取有用的资料等操作。这种木马的实际执行体是一个dll文件,由于Windows系统自身就包含着大量的dll文件,谁也无法一眼看出哪个dll文件不是系统自带的,所以这种木马的隐蔽性又提高了一级,而且它的执行方式也更加隐蔽,这是由Windows系统自身特性决定的,Windows自身就是大量使用dll的系统,许多dll文件在启动时便被相关的应用程序加载进内存里执行了,可是有谁在进程里直接看到过某个dll在运行的?因为系统是把dll视为一种模块性质的执行体来调用的,它内部只包含了一堆以函数形式输出的模块,也就是说每个dll都需要由一个用到它的某个函数的exe来加载,当dll里的函数执行完毕后就会返回一个运行结果给调用它的exe,然后dll进程退出内存结束这次执行过程,这就是标准的dll运行周期,而采用了“线程注射”技术的dll则不是这样,它们自身虽然也是导出函数,但是它们的代码是具备执行逻辑的,这种模块就像一个普通exe,只是它不能直接由自身启动,而是需要有一个特殊作用的程序(称为加载者)产生的进程把这个dll的主体函数载入内存中执行,从而让它成为一个运行中的木马程序。

webpack原理:Tapable源码分析及钩子函数作用分析

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 关键词解析

 

typefunction
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

以上是关于钩子注入的原理机制的主要内容,如果未能解决你的问题,请参考以下文章

常见注入手法第四讲,SetWindowsHookEx全局钩子注入.以及注入QQ32位实战.

钩子教程 - 原理

13- 钩子

Hook(钩子技术)基本知识讲解,原理

Ring3下绕过Windows写时复制机制实现全局EAT钩子

webpack原理:Tapable源码分析及钩子函数作用分析