webpack怎么能只是会用呢,核心中的核心tapable了解下?

栏目: 编程工具 · 发布时间: 7年前

内容简介:为什么我们要学tapable,因为....webpack源码里面都是用的tapable来实现钩子挂载的,作为一个有点追求的code,webpack怎么能只满足于用呢?当然是要去看源码,写loader,plugin啦.在这之前,要是不清楚tapable的用法,源码那是更不用看了,看不懂.....所以,今天来讲一下tapable吧webpack本质上是一种事件流的机制,他的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建的bun

为什么我们要学tapable,因为....webpack源码里面都是用的tapable来实现钩子挂载的,作为一个有点追求的code,webpack怎么能只满足于用呢?当然是要去看源码,写loader,plugin啦.在这之前,要是不清楚tapable的用法,源码那是更不用看了,看不懂.....所以,今天来讲一下tapable吧

1. tapable

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

tapable创建实例时传递的参数对于程序运行并没有任何作用,只是给源码阅读者提供帮助

同样的,在使用tap*注册监听时,传递的第一个参数,也只是一个标识,并不会在程序运行中产生任何影响。而第二个参数则是回调函数

2.tapable的用法

const {
    SyncHook,
    SyncBailHook,
    SyncWaterHook,
    SyncLoopHook
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
} = require("tapable");
复制代码
序号 钩子名称 执行方式 使用要点
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*类型的钩子

  • 注册在该钩子下面的插件的执行顺序都是顺序执行
  • 只能使用tap注册,不能使用tapPromise和tapAsync注册

3.1 SyncHook

串行同步执行,不关心返回值 在SyncHook的实例上注册了tap之后,只要实例调用了call方法,那么这些tap的回掉函数一定会顺序执行一遍

let queue = new SyncHook(['没任何作用的参数']);

queue.tap(1,(name,age)=>{
    console.log(name,age)
})
queue.tap(2,(name,age)=>{
    console.log(name,age)
})
queue.tap(3,(name,age)=>{
    console.log(name,age)
})
queue.call('bearbao',8)

// 输出结果
// 'bearbao' 8
// 'bearbao' 8
// 'bearbao' 8

复制代码

3.1.1 SyncHook实现

class SyncHook {
    constructor(){
        this.listeners = [];
    }
    tap(formal,listener){
        this.listeners.push(listener)
    }
    call(...args){
        this.listeners.forEach(l=>l(...args))
    }
}
复制代码

3.2 SyncBailHook

串行同步执行,有一个返回值不为null则跳过剩下的逻辑

let queue = new SyncBailHook(['name'])

queue.tap(1,name=>{
  console.log(name)  
})
queue.tap(1,name=>{
  console.log(name) 
  return '1'
})
queue.tap(1,name=>{
  console.log(name)  
})

queue.call('bearbao')
// 输出结果,只执行前面两个回调,第三个不执行
// bearbao
// bearbao

复制代码

实现

class SyncBailHook {
    constructor(){
        this.listeners = [];
    }
    tap(formal,listener){
        this.listeners.push(listener)
    }
    call(...args){
        for(let i=0;i<this.listeners.length;i++){
            if(this.listeners[i]()) break;
        }
    }
}
复制代码

3.3 SyncWaterHook

串行同步执行,第一个注册的回调函数会接收call传进来的所有参数,之后的每个回调函数只接收到一个参数,就是上一个回调函数的返回值.

let queue = new SyncWaterHook(['name','age']);

queue.tap(1,(name,age)=>{
    console.log(name,age)
    return 1
})
queue.tap(2,(ret)=>{
    console.log(ret)
    return 2
})
queue.tap(3,(ret)=>{
    console.log(ret)
    return 3
})

queue.call('bearbao', 3)

// 输出结果
// bearbao 3
// 1
// 2
复制代码

SyncWaterHook 实现. SyncWaterHook这个方法很像redux中的compose方法,都是将一个函数的返回值作为参数传递给下一个函数.

对下面实现的call方法如果有疑惑,看不大懂的同学可以移步我之前对于compose函数的解读,里面有详细的介绍,这里就不多加赘述了

Redux进阶compose方法的实现与解析

class SyncWaterHook{
    constructor(){
        this.listeners = [];
    }
    tap(formal,listener){
        this.listener.push(listener);
    }
    call(...args){
        this.listeners.reduce((a,b)=>(...args)=>a(b(...args)))
    }
}

复制代码

3.4 SyncLoopHook

串行同步执行, 监听函数返回true表示继续循环,返回undefined表示循环结束

let queue = new SyncLoopHook;
let index = 0;
queue.tap(1,_=>{
    index++
    if(index<3){
        console.log(index);
        return true
    }
})
queue.call();

// 输出结果
// 1
// 2
复制代码

SyncLoopHook实现

class SyncLoopHook{
    constructor() {
        this.tasks=[];
    }
    tap(name,task) {
        this.tasks.push(task);
    }
    call(...args) {    
        this.tasks.forEach(task => {
            let ret=true;
            do {
                ret = task(...args);
            }while(ret)
        });
    }
}

复制代码

4. Async*类型的钩子

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

4.1 AsyncParallel

异步并行执行

4.1.1 AsyncParallelHook

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

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

异步订阅 调用方法
tap callAsync
tapAsync callAsync
tapPromise promise
  • 通过tap来使用

触发函数的参数,出了最后一个参数是异步监听回调函数执行完成之后的回调,其他的参数都是传递给回调函数的参数

let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tap('1',function(name){
    console.log(name,1);
});
queue.tap('2',function(name){
    console.log(name,2);
});
queue.tap('3',function(name){
    console.log(name,3);
});
queue.callAsync('bearbao',err=>{
    console.log(err);
    console.timeEnd('cost');
});

// 执行结果
/* 
 bearbao 1
 bearbao 2
 bearbao 3
cost: 4.720ms
*/
复制代码

实现

class AsyncParallelHook {
    constructor(){
        this.listeners = [];
    }
    tap(name,listener){
        this.listeners.push(listener);
    }
    callAsync(){
        this.listeners.forEach(listener=>listener(...arguments));
        Array.from(arguments).pop()();
    }
}

复制代码
  • 通过tapAsync来注册

注意,这里有个特殊的地方,如何确认某个回调执行完了呢?,每个监听回调的最后一个参数是一个回调函数,当执行callback之后,会认为当前函数执行完毕

let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
    setTimeout(function(){
        console.log(name, 1);
        callback();
    },1000)
});
queue.tapAsync('2',function(name,callback){
    setTimeout(function(){
        console.log(name, 2);
        callback();
    },2000)
});
queue.tapAsync('3',function(name,callback){
    setTimeout(function(){
        console.log(name, 3);
        callback();
    },3000)
});
queue.callAsync('bearbao',err=>{
    console.log(err);
    console.timeEnd('cost');
});

// 输出结果
/*
bearbao 1
bearbao 2
bearbao 3
cost: 3000.448974609375ms
*/
复制代码

实现

class AsyncParallelHook {
    constructor(){
        this.listeners = [];
    }
    tapAsync(name,listener){
        this.listeners.push(listener);
    }
    callAsync(...arg){
        let callback = arg.pop();
        let i = 0;
        let done = ()=>{
            if(++i==this.listeners.length){
                callback()
            }
        }
        this.listeners.forEach(listener=>listener(...arg,done));
        
    }
}

复制代码
  • 使用tapPromise

使用tapPromise注册监听时,每个回调函数的返回值必须是一个Promise的实例

let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(1);
            resolve();
        },1000)
    });

});
queue.tapPromise('2',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(2);
            resolve();
        },2000)
    });
});
queue.tapPromise('3',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(3);
            resolve();
        },3000)
    });
});
queue.promise('bearbao').then(()=>{
    console.timeEnd('cost');
})

// 执行记过
/*
 1
 2
 3
cost: 3000.448974609375ms
*/
复制代码

实现

class AsyncParallelHook {
    constructor(){
        this.listeners = [];
    }
    tapPromise(name,listener){
        this.listeners.push(listener);
    }
    promise(...arg){
        let i = 0;
        return Promise.all(this.listeners.map(l=>l(arg)))
    }
}

复制代码

5. 好困好困

一不小心又到1点了,为了能够获得长寿成就,今天就先写到这里吧,后续几个方法,过两天再更新上来

结语

如果觉得还可以,能在诸君的编码之路上带来一点帮助,请点赞鼓励一下,谢谢!


以上所述就是小编给大家介绍的《webpack怎么能只是会用呢,核心中的核心tapable了解下?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTTP Essentials

HTTP Essentials

Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99

The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换