Webpack 核心模塊 tapable 解析

在這裏插入圖片描述

原文出自:https://www.pandashen.com


前言

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 核心庫的類,爲我們提供不同的事件流執行機制,我們稱爲 “鉤子”。

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

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

Sync 類型的鉤子

1、SyncHook

SyncHook 爲串行同步執行,不關心事件處理函數的返回值,在觸發事件之後,會按照事件註冊的先後順序執行所有的事件處理函數。

// 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,也可在參數不足時拋出異常,不靈活,後面的例子中就不再這樣寫了。

2、SyncBailHook

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

// 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,第一次執行後沒有返回值則繼續循環執行,如果有返回值則立即停止循環,即實現 “保險” 的功能。

3、SyncWaterfallHook

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

// 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 進行歸併,將第一個事件處理函數執行後的返回值作爲歸併的初始值,依次調用其餘事件處理函數並傳遞上一次歸併的返回值。

4、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 類型可以使用 taptapAsynctapPromise 註冊不同類型的插件 “鉤子”,分別通過 callcallAsyncpromise 方法調用,我們下面會針對 AsyncParallelHookAsyncSeriesHookasyncpromise 兩種方式分別介紹和模擬。

1、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 來實現的。

2、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 源碼中,註冊事件的方法 tabtapAsynctapPromise 和觸發事件的方法 callcallAsyncpromise 都是通過 compile 方法快速編譯出來的,我們本文中這些方法的實現只是遵照了 tapable 庫這些 “鉤子” 的事件處理機制進行了模擬,以方便我們瞭解 tapable,爲學習 Webpack 原理做了一個鋪墊,在 Webpack 中,這些 “鉤子” 的真正作用就是將通過配置文件讀取的插件與插件、加載器與加載器之間進行連接,“並行” 或 “串行” 執行,相信在我們對 tapable 中這些 “鉤子” 的事件機制有所瞭解之後,再重新學習 Webpack 的源碼應該會有所頭緒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章