Webpack 核心模塊 tapable 解析(各個Hook詳解)

前言

Webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器,是對前端項目實現自動化和優化必不可少的工具,Webpack 的 loader(加載器)和 plugin(插件)是由 Webpack 開發者和社區開發者共同貢獻的,而目前又沒有比較系統的開發文檔,想寫加載器和插件必需要懂 Webpack 的原理,即看懂 Webpack 的源碼,tapable 則是 Webpack 依賴的核心庫,能夠說不懂 tapable 就看不懂 Webpack 源碼,因此本篇會對 tapable 提供的類進行解析和模擬。前端

tapable 介紹

Webpack 本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是 tapable,Webpack 中最核心的,負責編譯的 Compiler 和負責建立 bundlesCompilation 都是 tapable 構造函數的實例。數組

打開 Webpack 4.0 的源碼中必定會看到下面這些以 SyncAsync 開頭,以 Hook 結尾的方法,這些都是 tapable 核心庫的類,爲咱們提供不一樣的事件流執行機制,咱們稱爲 「鉤子」。promise

// 引入 tapable 以下
const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
 } = require("tapable");
複製代碼
複製代碼

上面的實現事件流機制的 「鉤子」 大方向能夠分爲兩個類別,「同步」 和 「異步」,「異步」 又分爲兩個類別,「並行」 和 「串行」,而 「同步」 的鉤子都是串行的。

Sync 類型的鉤子

一、SyncHook

SyncHook 爲串行同步執行,不關心事件處理函數的返回值,在觸發事件以後,會按照事件註冊的前後順序執行全部的事件處理函數。bash

// SyncHook 鉤子的使用
const { SyncHook } = require("tapable");

// 建立實例
let syncHook = new SyncHook(["name", "age"]);

// 註冊事件
syncHook.tap("1", (name, age) => console.log("1", name, age));
syncHook.tap("2", (name, age) => console.log("2", name, age));
syncHook.tap("3", (name, age) => console.log("3", name, age));

// 觸發事件,讓監聽函數執行
syncHook.call("panda", 18);

// 1 panda 18
// 2 panda 18
// 3 panda 18
複製代碼
複製代碼

tapable 解構的 SyncHook 是一個類,註冊事件需先建立實例,建立實例時支持傳入一個數組,數組內存儲事件觸發時傳入的參數,實例的 tap 方法用於註冊事件,支持傳入兩個參數,第一個參數爲事件名稱,在 Webpack 中通常用於存儲事件對應的插件名稱(名字隨意,只是起到註釋做用), 第二個參數爲事件處理函數,函數參數爲執行 call 方法觸發事件時所傳入的參數的形參。併發

// 模擬 SyncHook 類
class SyncHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 也可在參數不足時拋出異常
        if (args.length < this.args.length) throw new Error("參數不足");

        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 依次執行事件處理函數
        this.tasks.forEach(task => task(...args));
    }
}
複製代碼
複製代碼

tasks 數組用於存儲事件處理函數,call 方法調用時傳入參數超過建立 SyncHook 實例傳入的數組長度時,多餘參數可處理爲 undefined,也可在參數不足時拋出異常,不靈活,後面的例子中就再也不這樣寫了。異步

二、SyncBailHook

SyncBailHook 一樣爲串行同步執行,若是事件處理函數執行時有一個返回值不爲空(即返回值爲 undefined),則跳過剩下未執行的事件處理函數(如類的名字,意義在於保險)。async

// SyncBailHook 鉤子的使用
const { SyncBailHook } = require("tapable");

// 建立實例
let syncBailHook = new SyncBailHook(["name", "age"]);

// 註冊事件
syncBailHook.tap("1", (name, age) => console.log("1", name, age));

syncBailHook.tap("2", (name, age) => {
    console.log("2", name, age);
    return "2";
});

syncBailHook.tap("3", (name, age) => console.log("3", name, age));

// 觸發事件,讓監聽函數執行
syncBailHook.call("panda", 18);

// 1 panda 18
// 2 panda 18
複製代碼
複製代碼

經過上面的用法能夠看出,SyncHookSyncBailHook 在邏輯上只是 call 方法不一樣,致使事件的執行機制不一樣,對於後面其餘的 「鉤子」,也是 call 的區別,接下來實現 SyncBailHook 類。函數

// 模擬 SyncBailHook 類
class SyncBailHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 依次執行事件處理函數,若是返回值不爲空,則中止向下執行
        let i = 0, ret;
        do {
            ret = this.tasks[i++](...args);
        } while (!ret);
    }
}
複製代碼
複製代碼

在上面代碼的 call 方法中,咱們設置返回值爲 ret,第一次執行後沒有返回值則繼續循環執行,若是有返回值則當即中止循環,即實現 「保險」 的功能。工具

三、SyncWaterfallHook

SyncWaterfallHook 爲串行同步執行,上一個事件處理函數的返回值做爲參數傳遞給下一個事件處理函數,依次類推,正因如此,只有第一個事件處理函數的參數能夠經過 call 傳遞,而 call 的返回值爲最後一個事件處理函數的返回值。oop

// SyncWaterfallHook 鉤子的使用
const { SyncWaterfallHook } = require("tapable");

// 建立實例
let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);

// 註冊事件
syncWaterfallHook.tap("1", (name, age) => {
    console.log("1", name, age);
    return "1";
});

syncWaterfallHook.tap("2", data => {
    console.log("2", data);
    return "2";
});

syncWaterfallHook.tap("3", data => {
    console.log("3", data);
    return "3"
});

// 觸發事件,讓監聽函數執行
let ret = syncWaterfallHook.call("panda", 18);
console.log("call", ret);

// 1 panda 18
// 2 1
// 3 2
// call 3
複製代碼
複製代碼

SyncWaterfallHook 名稱中含有 「瀑布」,經過上面代碼能夠看出 「瀑布」 形象生動的描繪了事件處理函數執行的特色,與 SyncHookSyncBailHook 的區別就在於事件處理函數返回結果的流動性,接下來看一下 SyncWaterfallHook 類的實現。

// 模擬 SyncWaterfallHook 類
class SyncWaterfallHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 依次執行事件處理函數,事件處理函數的返回值做爲下一個事件處理函數的參數
        let [first, ...others] = this.tasks;
        return reduce((ret, task) => task(ret), first(...args));
    }
}
複製代碼
複製代碼

上面代碼中 call 的邏輯是將存儲事件處理函數的 tasks 拆成兩部分,分別爲第一個事件處理函數,和存儲其他事件處理函數的數組,使用 reduce 進行歸併,將第一個事件處理函數執行後的返回值做爲歸併的初始值,依次調用其他事件處理函數並傳遞上一次歸併的返回值。

四、SyncLoopHook

SyncLoopHook 爲串行同步執行,事件處理函數返回 true 表示繼續循環,即循環執行當前事件處理函數,返回 undefined 表示結束循環,SyncLoopHookSyncBailHook 的循環不一樣,SyncBailHook 只決定是否繼續向下執行後面的事件處理函數,而 SyncLoopHook 的循環是指循環執行每個事件處理函數,直到返回 undefined 爲止,纔會繼續向下執行其餘事件處理函數,執行機制同理。

// SyncLoopHook 鉤子的使用
const { SyncLoopHook } = require("tapable");

// 建立實例
let syncLoopHook = new SyncLoopHook(["name", "age"]);

// 定義輔助變量
let total1 = 0;
let total2 = 0;

// 註冊事件
syncLoopHook.tap("1", (name, age) => {
    console.log("1", name, age, total1);
    return total1++ < 2 ? true : undefined;
});

syncLoopHook.tap("2", (name, age) => {
    console.log("2", name, age, total2);
    return total2++ < 2 ? true : undefined;
});

syncLoopHook.tap("3", (name, age) => console.log("3", name, age));

// 觸發事件,讓監聽函數執行
syncLoopHook.call("panda", 18);

// 1 panda 18 0
// 1 panda 18 1
// 1 panda 18 2
// 2 panda 18 0
// 2 panda 18 1
// 2 panda 18 2
// 3 panda 18
複製代碼
複製代碼

經過上面的執行結果能夠清楚的看到 SyncLoopHook 的執行機制,但有一點須要注意,返回值必須嚴格是 true 纔會觸發循環,屢次執行當前事件處理函數,必須嚴格返回 undefined,纔會結束循環,去執行後面的事件處理函數,若是事件處理函數的返回值不是 true 也不是 undefined,則會死循環。

在瞭解 SyncLoopHook 的執行機制之後,咱們接下來看看 SyncLoopHookcall 方法是如何實現的。

// 模擬 SyncLoopHook 類
class SyncLoopHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 依次執行事件處理函數,若是返回值爲 true,則繼續執行當前事件處理函數
        // 直到返回 undefined,則繼續向下執行其餘事件處理函數
        this.tasks.forEach(task => {
            let ret;
            do {
                ret = this.task(...args);
            } while (ret === true || !(ret === undefined));
        });
    }
}
複製代碼
複製代碼

在上面代碼中能夠看到 SyncLoopHookcall 方法的實現更像是 SyncHookSyncBailHookcall 方法的結合版,外層循環整個 tasks 事件處理函數隊列,內層經過返回值進行循環,控制每個事件處理函數的執行次數。

注意:在 Sync 類型 「鉤子」 下執行的插件都是順序執行的,只能使用 tab 註冊。

Async 類型的鉤子

Async 類型可使用 taptapSynctapPromise 註冊不一樣類型的插件 「鉤子」,分別經過 callcallAsyncpromise 方法調用,咱們下面會針對 AsyncParallelHookAsyncSeriesHookasyncpromise 兩種方式分別介紹和模擬。

一、AsyncParallelHook

AsyncParallelHook 爲異步並行執行,經過 tapAsync 註冊的事件,經過 callAsync 觸發,經過 tapPromise 註冊的事件,經過 promise 觸發(返回值能夠調用 then 方法)。

(1) tapAsync/callAsync

callAsync 的最後一個參數爲回調函數,在全部事件處理函數執行完畢後執行。

// AsyncParallelHook 鉤子:tapAsync/callAsync 的使用
const { AsyncParallelHook } = require("tapable");

// 建立實例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 註冊事件
console.time("time");
asyncParallelHook.tapAsync("1", (name, age, done) => {
    settimeout(() => {
        console.log("1", name, age, new Date());
        done();
    }, 1000);
});

asyncParallelHook.tapAsync("2", (name, age, done) => {
    settimeout(() => {
        console.log("2", name, age, new Date());
        done();
    }, 2000);
});

asyncParallelHook.tapAsync("3", (name, age, done) => {
    settimeout(() => {
        console.log("3", name, age, new Date());
        done();
        console.timeEnd("time");
    }, 3000);
});

// 觸發事件,讓監聽函數執行
asyncParallelHook.callAsync("panda", 18, () => {
    console.log("complete");
});

// 1 panda 18 2018-08-07T10:38:32.675Z
// 2 panda 18 2018-08-07T10:38:33.674Z
// 3 panda 18 2018-08-07T10:38:34.674Z
// complete
// time: 3005.060ms
複製代碼
複製代碼

異步並行是指,事件處理函數內三個定時器的異步操做最長時間爲 3s,而三個事件處理函數執行完成總共用時接近 3s,因此三個事件處理函數是幾乎同時執行的,不需等待。

全部 tabAsync 註冊的事件處理函數最後一個參數都爲一個回調函數 done,每一個事件處理函數在異步代碼執行完畢後調用 done 函數,則能夠保證 callAsync 會在全部異步函數都執行完畢後執行,接下來看一看 callAsync 是如何實現的。

// 模擬 AsyncParallelHook 類:tapAsync/callAsync
class AsyncParallelHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tabAsync(name, task) {
        this.tasks.push(task);
    }
    callAsync(...args) {
        // 先取出最後傳入的回調函數
        let finalCallback = args.pop();

        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 定義一個 i 變量和 done 函數,每次執行檢測 i 值和隊列長度,決定是否執行 callAsync 的回調函數
        let i = 0;
        let done = () => {
            if (++i === this.tasks.length) {
                finalCallback();
            }
        };

        // 依次執行事件處理函數
        this.tasks.forEach(task => task(...args, done));
    }
}
複製代碼
複製代碼

callAsync 中,將最後一個參數(全部事件處理函數執行完畢後執行的回調)取出,並定義 done 函數,經過比較 i 和存儲事件處理函數的數組 taskslength 來肯定回調是否執行,循環執行每個事件處理函數並將 done 做爲最後一個參數傳入,因此每一個事件處理函數內部的異步操做完成時,執行 done 就是爲了檢測是否是該執行 callAsync 的回調,當全部事件處理函數均執行完畢知足 done 函數內部 ilength 相等的條件時,則調用 callAsync 的回調。

(2) tapPromise/promise

要使用 tapPromise 註冊事件,對事件處理函數有一個要求,必須返回一個 Promise 實例,而 promise 方法也返回一個 Promise 實例,callAsync 的回調函數在 promise 方法中用 then 的方式代替。

// AsyncParallelHook 鉤子:tapPromise/promise 的使用
const { AsyncParallelHook } = require("tapable");

// 建立實例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 註冊事件
console.time("time");
asyncParallelHook.tapPromise("1", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncParallelHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 觸發事件,讓監聽函數執行
asyncParallelHook.promise("panda", 18).then(ret => {
    console.log(ret);
});

// 1 panda 18 2018-08-07T12:17:21.741Z
// 2 panda 18 2018-08-07T12:17:22.736Z
// 3 panda 18 2018-08-07T12:17:23.739Z
// time: 3006.542ms
// [ '1', '2', '3' ]
複製代碼
複製代碼

上面每個 tapPromise 註冊事件的事件處理函數都返回一個 Promise 實例,並將返回值傳入 resolve 方法,調用 promise 方法觸發事件時,若是全部事件處理函數返回的 Promise 實例結果都成功,會將結果存儲在數組中,並做爲參數傳遞給 promisethen 方法中成功的回調,若是有一個失敗就是將失敗的結果返回做爲參數傳遞給失敗的回調。

// 模擬 AsyncParallelHook 類 tapPromise/promise
class AsyncParallelHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tapPromise(name, task) {
        this.tasks.push(task);
    }
    promise(...args) {
        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 將全部事件處理函數轉換成 Promise 實例,併發執行全部的 Promise
        return Promise.all(this.tasks.map(task => task(...args)));
    }
}
複製代碼
複製代碼

其實根據上面對於 tapPromisepromise 使用的描述就能夠猜到,promise 方法的邏輯是經過 Promise.all 來實現的。

二、AsyncSeriesHook

AsyncSeriesHook 爲異步串行執行,與 AsyncParallelHook 相同,經過 tapAsync 註冊的事件,經過 callAsync 觸發,經過 tapPromise 註冊的事件,經過 promise 觸發,能夠調用 then 方法。

(1) tapAsync/callAsync

AsyncParallelHookcallAsync 方法相似,AsyncSeriesHookcallAsync 方法也是經過傳入回調函數的方式,在全部事件處理函數執行完畢後執行 callAsync 的回調函數。

// AsyncSeriesHook 鉤子:tapAsync/callAsync 的使用
const { AsyncSeriesHook } = require("tapable");

// 建立實例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 註冊事件
console.time("time");
asyncSeriesHook.tapAsync("1", (name, age, next) => {
    settimeout(() => {
        console.log("1", name, age, new Date());
        next();
    }, 1000);
});

asyncSeriesHook.tapAsync("2", (name, age, next) => {
    settimeout(() => {
        console.log("2", name, age, new Date());
        next();
    }, 2000);
});

asyncSeriesHook.tapAsync("3", (name, age, next) => {
    settimeout(() => {
        console.log("3", name, age, new Date());
        next();
        console.timeEnd("time");
    }, 3000);
});

// 觸發事件,讓監聽函數執行
asyncSeriesHook.callAsync("panda", 18, () => {
    console.log("complete");
});

// 1 panda 18 2018-08-07T14:40:52.896Z
// 2 panda 18 2018-08-07T14:40:54.901Z
// 3 panda 18 2018-08-07T14:40:57.901Z
// complete
// time: 6008.790ms
複製代碼
複製代碼

異步串行是指,事件處理函數內三個定時器的異步執行時間分別爲 1s2s3s,而三個事件處理函數執行完總共用時接近 6s,因此三個事件處理函數執行是須要排隊的,必須一個一個執行,當前事件處理函數執行完才能執行下一個。

AsyncSeriesHook 類的 tabAsync 方法註冊的事件處理函數參數中的 next 能夠與 AsyncParallelHook 類中 tabAsync 方法參數的 done 進行類比,同爲回調函數,不一樣點在於 AsyncSeriesHookAsyncParallelHookcallAsync 方法的 「並行」 和 「串行」 的實現方式。

// 模擬 AsyncSeriesHook 類:tapAsync/callAsync
class AsyncSeriesHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tabAsync(name, task) {
        this.tasks.push(task);
    }
    callAsync(...args) {
        // 先取出最後傳入的回調函數
        let finalCallback = args.pop();

        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 定義一個 i 變量和 next 函數,每次取出一個事件處理函數執行,並維護 i 的值
        // 直到全部事件處理函數都執行完,調用 callAsync 的回調
        // 若是事件處理函數中沒有調用 next,則沒法繼續
        let i = 0;
        let next = () => {
            let task = this.tasks[i++];
            task ? task(...args, next) : finalCallback();
        };
        next();
    }
}
複製代碼
複製代碼

AsyncParallelHook 是經過循環依次執行了全部的事件處理函數,done 方法只爲了檢測是否已經知足條件執行 callAsync 的回調,若是中間某個事件處理函數沒有調用 done,只是不會調用 callAsync 的回調,可是全部的事件處理函數都執行了。

AsyncSeriesHooknext 執行機制更像 ExpressKoa 中的中間件,在註冊事件的回調中若是不調用 next,則在觸發事件時會在沒有調用 next 的事件處理函數的位置 「卡死」,即不會繼續執行後面的事件處理函數,只有都調用 next 才能繼續,而最後一個事件處理函數中調用 next 決定是否調用 callAsync 的回調。

(2) tapPromise/promise

AsyncParallelHook 相似,tapPromise 註冊事件的事件處理函數須要返回一個 Promise 實例,promise 方法最後也返回一個 Promise 實例。

// AsyncSeriesHook 鉤子:tapPromise/promise 的使用
const { AsyncSeriesHook } = require("tapable");

// 建立實例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 註冊事件
console.time("time");
asyncSeriesHook.tapPromise("1", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncSeriesHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 觸發事件,讓監聽函數執行
asyncSeriesHook.promise("panda", 18).then(ret => {
    console.log(ret);
});

// 1 panda 18 2018-08-07T14:45:52.896Z
// 2 panda 18 2018-08-07T14:45:54.901Z
// 3 panda 18 2018-08-07T14:45:57.901Z
// time: 6014.291ms
// [ '1', '2', '3' ]
複製代碼
複製代碼

分析上面的執行過程,全部的事件處理函數都返回了 Promise 的實例,若是想實現 「串行」,則須要讓每個返回的 Promise 實例都調用 then,並在 then 中執行下一個事件處理函數,這樣就保證了只有上一個事件處理函數執行完後纔會執行下一個。

// 模擬 AsyncSeriesHook 類 tapPromise/promise
class AsyncSeriesHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tapPromise(name, task) {
        this.tasks.push(task);
    }
    promise(...args) {
        // 傳入參數嚴格對應建立實例傳入數組中的規定的參數,執行時多餘的參數爲 undefined
        args = args.slice(0, this.args.length);

        // 將每一個事件處理函數執行並調用返回 Promise 實例的 then 方法
        // 讓下一個事件處理函數在 then 方法成功的回調中執行
        let [first, ...others] = this.tasks;
        return others.reduce((promise, task) => {
            return promise.then(() => task(...args));
        }, first(...args));
    }
}
複製代碼
複製代碼

上面代碼中的 「串行」 是使用 reduce 歸併來實現的,首先將存儲全部事件處理函數的數組 tasks 解構成兩部分,第一個事件處理函數和存儲其餘事件處理函數的數組 others,對 others 進行歸併,將第一個事件處理函數執行後返回的 Promise 實例做爲歸併的初始值,這樣在歸併的過程當中上一個值始終是上一個事件處理函數返回的 Promise 實例,能夠直接調用 then 方法,並在 then 的回調中執行下一個事件處理函數,直到歸併完成,將 reduce 最後返回的 Promise 實例做爲 promise 方法的返回值,則實現 promise 方法執行後繼續調用 then 來實現後續邏輯。

對其餘異步鉤子補充

在上面 Async 異步類型的 「鉤子中」,咱們只着重介紹了 「串行」 和 「並行」(AsyncParallelHookAsyncSeriesHook)以及回調和 Promise 的兩種註冊和觸發事件的方式,還有一些其餘的具備必定特色的異步 「鉤子」 咱們並無進行分析,由於他們的機制與同步對應的 「鉤子」 很是的類似。

AsyncParallelBailHookAsyncSeriesBailHook 分別爲異步 「並行」 和 「串行」 執行的 「鉤子」,返回值不爲 undefined,即有返回值,則當即中止向下執行其餘事件處理函數,實現邏輯可結合 AsyncParallelHookAsyncSeriesHookSyncBailHook

AsyncSeriesWaterfallHook 爲異步 「串行」 執行的 「鉤子」,上一個事件處理函數的返回值做爲參數傳遞給下一個事件處理函數,實現邏輯可結合 AsyncSeriesHookSyncWaterfallHook

總結

tapable 源碼中,註冊事件的方法 tabtapSynctapPromise 和觸發事件的方法 callcallAsyncpromise 都是經過 compile 方法快速編譯出來的,咱們本文中這些方法的實現只是遵守了 tapable 庫這些 「鉤子」 的事件處理機制進行了模擬,以方便咱們瞭解 tapable,爲學習 Webpack 原理作了一個鋪墊,在 Webpack 中,這些 「鉤子」 的真正做用就是將經過配置文件讀取的插件與插件、加載器與加載器之間進行鏈接,「並行」 或 「串行」 執行,相信在咱們對 tapable 中這些 「鉤子」 的事件機制有所瞭解以後,再從新學習 Webpack 的源碼應該會有所頭緒。

相關文章
相關標籤/搜索