webpack怎麼能只是會用呢,核心中的核心tapable瞭解下?

前言

爲何咱們要學tapable,由於....webpack源碼裏面都是用的tapable來實現鉤子掛載的,做爲一個有點追求的code,webpack怎麼能只知足於用呢?固然是要去看源碼,寫loader,plugin啦.在這以前,要是不清楚tapable的用法,源碼那是更不用看了,看不懂.....因此,今天來說一下tapable吧webpack

1. tapable

webpack本質上是一種事件流的機制,他的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable,webpack中最核心的負責編譯的Compiler和負責建立的bundles的Compilation都是Tapable的實例web

tapable建立實例時傳遞的參數對於程序運行並無任何做用,只是給源碼閱讀者提供幫助redux

一樣的,在使用tap*註冊監聽時,傳遞的第一個參數,也只是一個標識,並不會在程序運行中產生任何影響。而第二個參數則是回調函數promise

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

複製代碼

實現oop

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傳進來的全部參數,以後的每一個回調函數只接收到一個參數,就是上一個回調函數的返回值.post

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方法,都是將一個函數的返回值做爲參數傳遞給下一個函數.ui

對下面實現的call方法若是有疑惑,看不大懂的同窗能夠移步我以前對於compose函數的解讀,裏面有詳細的介紹,這裏就很少加贅述了this

Redux進階compose方法的實現與解析

class SyncWaterHook{
    constructor(){
        this.listeners = [];
    }
    tap(formal,listener){
        this.listener.unshift(listener);
    }
    call(...args){
        this.listeners.reduce((a,b)=>(...args)=>a(b(...args)))(...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點了,爲了可以得到長壽成就,今天就先寫到這裏吧,後續幾個方法,過兩天再更新上來

結語

若是以爲還能夠,能在諸君的編碼之路上帶來一點幫助,請點贊鼓勵一下,謝謝!

相關文章
相關標籤/搜索