深刻理解Webpack核心模塊Tapable鉤子[同步版]

記錄下本身在前端路上爬坑的經歷 加深印象,正文開始~前端

tapable是webpack的核心依賴庫 想要讀懂webpack源碼 就必須首先熟悉tapable
ok.下面是webapck中引入的tapable鉤子 因而可知 在webpack中tapable的重要性node

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

這些鉤子可分爲同步的鉤子和異步的鉤子,Sync開頭的都是同步的鉤子,Async開頭的都是異步的鉤子。而異步的鉤子又可分爲並行和串行,其實同步的鉤子也能夠理解爲串行的鉤子。react

本文將根據如下章節分別梳理每一個鉤子webpack

同步鉤子
  • SyncHook
  • SyncBailHook
  • SyncWaterfallHook
  • SyncLoopHook

首先安裝tapable
npm i tapable -Dgit

SyncHook

SyncHook是簡單的同步鉤子,它很相似於發佈訂閱。首先訂閱事件,觸發時按照順序依次執行,因此說同步的鉤子都是串行的。github

const { SyncHook } = require('tapable');

class Hook{
    constructor(){
        /** 1 生成SyncHook實例 */
        this.hooks = new SyncHook(['name']);
    }
    tap(){
        /** 2 註冊監聽函數 */
        this.hooks.tap('node',function(name){
            console.log('node',name);
        });
        this.hooks.tap('react',function(name){
            console.log('react',name);
        });
    }
    start(){
        /** 3出發監聽函數 */
        this.hooks.call('call end.');
    }
}

let h = new Hook();

h.tap();/** 相似訂閱 */ 
h.start();/** 相似發佈 */

/* 打印順序:
    node call end.
    react call end.
*/

能夠看到 它是按照順序依次打印的,其實說白了就是發佈和訂閱。接下來咱們就手動實現它。web

class SyncHook{ // 定義一個SyncHook類
    constructor(args){ /* args -> ['name']) */
        this.tasks = [];
    }
    /** tap接收兩個參數 name和fn */
    tap(name,fn){
        /** 訂閱:將fn放入到this.tasks中 */
        this.tasks.push(fn);
    }
    start(...args){/** 接受參數 */
        /** 發佈:將this.taks中的fn依次執行 */
        this.tasks.forEach((task)=>{
            task(...args);
        });
    }
}

let h = new SyncHook(['name']);

/** 訂閱 */
h.tap('react',(name)=>{
    console.log('react',name);
});
h.tap('node',(name)=>{
    console.log('node',name);
});
/** 發佈 */
h.start('end.');

/* 打印順序:
    react end.
    node end.
*/
SyncBailHook

SyncBailHook 從字面意思上理解爲帶有保險的同步的鉤子,帶有保險意思是 根據每一步返回的值來決定要不要繼續往下走,若是return了一個非undefined的值 那就不會往下走,注意 若是什麼都不return 也至關於return了一個undefined。npm

const { SyncBailHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new SyncBailHook(['name']);
    }
    tap(){
        this.hooks.tap('node',function(name){
            console.log('node',name);
            /** 此處return了一個非undefined
             *  代碼到這裏就不會繼續執行餘下的鉤子
             */
            return 1;
        });
        this.hooks.tap('react',function(name){
            console.log('react',name);
        });
    }
    start(){
        this.hooks.call('call end.');
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印順序:
    node call end.
*/

手動實現segmentfault

class SyncHook{
    constructor(args){ 
        this.tasks = [];
    }
    tap(name,fn){
        this.tasks.push(fn);
    }
    start(...args){
        let index = 0;
        let result;
        /** 利用do while先執行一次的特性 */
        do{
            /** 拿到每一次函數的返回值 result */
            result = this.tasks[index++](...args);
            /** 若是返回值不爲undefined或者執行完畢全部task -> 中斷循環 */
        }while(result === undefined && index < this.tasks.length);
    }
}

let h = new SyncHook(['name']);

h.tap('react',(name)=>{
    console.log('react',name);
    return 1;
});
h.tap('node',(name)=>{
    console.log('node',name);
});

h.start('end.');

/* 打印順序:
   react end.
*/
SyncWaterfallHook

SyncWaterfallHook是同步的瀑布鉤子,瀑布怎麼理解呢? 其實就是說它的每一步都依賴上一步的執行結果,也就是上一步return的值就是下一步的參數。異步

const { SyncWaterfallHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new SyncWaterfallHook(['name']);
    }
    tap(){
        this.hooks.tap('node',function(name){
            console.log('node',name);
            /** 此處返回的值做爲第二步的結果 */
            return '第一步返回的結果';
        });
        this.hooks.tap('react',function(data){
            /** 此處data就是上一步return的值 */
            console.log('react',data);
        });
    }
    start(){
        this.hooks.call('callend.');
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印順序:
    node callend.
    react 第一步返回的結果
*/

手動實現:

class SyncWaterFallHook{
    constructor(args){
        this.tasks = [];
    }
    tap(name,fn){
        this.tasks.push(fn);
    }
    start(...args){
        /** 解構 拿到tasks中的第一個task -> first */
        let [first, ...others] = this.tasks;
        /** 利用reduce() 累計執行 
         * 首先傳入第一個 first 並執行
         * l是上一個task n是當前task
         * 這樣知足了 下一個函數依賴上一個函數的執行結果
        */
        others.reduce((l,n)=>{
            return n(l);
        },first(...args));
    }
}

let h = new SyncWaterFallHook(['name']);

/** 訂閱 */
h.tap('react',(name)=>{
    console.log('react',name);
    return '我是第一步返回的值';
});
h.tap('node',(name)=>{
    console.log('node',name);
});
/** 發佈 */
h.start('end.');

/* 打印順序:
    react end.
    node 我是第一步返回的值
*/
SyncLoopHook

SyncLoopHook是同步的循環鉤子。 循環鉤子很好理解,就是在知足必定條件時 循環執行某個函數:

const { SyncLoopHook } = require('tapable');

class Hook{
    constructor(){
        /** 定義一個index */
        this.index = 0;
        this.hooks = new SyncLoopHook(['name']);
    }
    tap(){
        /** 箭頭函數 綁定this */
        this.hooks.tap('node',(name) => {
            console.log('node',name);
            /** 當不知足條件時 會循環執行該函數 
             * 返回值爲udefined時 終止該循環執行
            */
            return ++this.index === 5?undefined:'學完5遍node後再學react';
        });
        this.hooks.tap('react',(data) => {
            console.log('react',data);
        });
    }
    start(){
        this.hooks.call('callend.');
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印順序:
    node callend.
    node callend.
    node callend.
    node callend.
    node callend.
    react callend.
*/

能夠看到 執行了5遍node callend後再繼續往下執行。也就是當返回undefined時 纔會繼續往下執行:

class SyncWaterFallHook{
    constructor(args){
        this.tasks = [];
    }
    tap(name,fn){
        this.tasks.push(fn);
    }
    start(...args){
        let result;
        this.tasks.forEach((task)=>{
            /** 注意 此處do{}while()循環的是每一個單獨的task */
            do{
                /** 拿到每一個task執行後返回的結果 */
                result = task(...args);
                /** 返回結果不是udefined時 會繼續循環執行該task */
            }while(result !== undefined);
        });
    }
}

let h = new SyncWaterFallHook(['name']);
let total = 0;
/** 訂閱 */
h.tap('react',(name)=>{
    console.log('react',name);
    return ++total === 3?undefined:'繼續執行';
});
h.tap('node',(name)=>{
    console.log('node',name);
});
/** 發佈 */
h.start('end.');

/* 打印順序:
    react end.
    react end.
    react end.
    node end.
*/

能夠看到, 執行了3次react end.後 才繼續執行了下一個task -> node end。至此,咱們把tapable的全部同步鉤子都解析完畢. 異步鉤子比同步鉤子麻煩些,咱們會在下一章節開始解析異步的鉤子.

傳送門:深刻理解Webpack核心模塊Tapable鉤子(異步版)

代碼:mock-webpack-tapable

相關文章
相關標籤/搜索