Webpack 進階之源碼分析(二)

本文主要講解 webpacktapablenode

上一篇文章咱們分析當執行 webpack 命令的時候,會從 webpack 目錄執行到 webpack-cli 目錄,最終又回到 webpack 目錄,這一篇文章就主要講解回到 webpack 目錄中又作了哪些事情?webpack

首先打開 webpack/lib/webpack.js 文件,找到 webpack 方法。方法內部首先會驗證 options 是否合法,而後會根據傳入的 options 是否爲數組,生成不一樣的 compiler 對象。web

若是爲非數組則實例化 CompilerCompiler 對象繼承 Tapable,若是是數組則實例化 MultiCompiler,數組每一項均實例化 Compiler。最終給生成 compiler 對象注入 plugins,再根據 target,選擇性注入插件。好比 targetweb 時,注入 xxx 插件,最後視參數調用 run 方法仍是 watch 方法。數組

Tapable

因爲筆者未用過 Tapable,因此先講解用法,對使用已掌握的童鞋能夠直接忽略,對 tapable 源碼不熟的童鞋能夠直接拉到最後。promise

Tapable 是一個相似於 Node.jsEventEmitter 的庫,專一於事件的觸發與處理,同時它也是 webpack 出的一個小型的庫,它容許你建立鉤子,爲鉤子掛載函數,最後調用掛載函數,其更像一個發佈-訂閱系統。異步

Tapable 共有 9 種鉤子,共分爲 2 大類,一類爲 sync 同步鉤子,一類爲 async 異步鉤子。sync 同步鉤子分爲 SyncHookSyncBailHookSyncWaterfallHookSyncLoopHook 4 種。async 異步鉤子分爲 AsyncParallelHookAsyncParallelBailHookAsyncSeriesHookAsyncSeriesBailHookAsync SeriesWaterfallHook 5 種。同步的鉤子,只能經過 tap 調用,而異步的鉤子則能夠經過 tapPromisetapAsynctap 來調用。async

const { SyncHook } = require("tapable");
const hook = new SyncHook();
hook.tap("subscribe", () => {
    console.log("subscribe");
});

hook.call(); // subscribe
複製代碼

使用

SyncHook

SyncHook 是最基礎的鉤子,它能夠掛載多個函數,掛載的函數依次執行,沒有返回值。函數

const syncHook = new SyncHook();
syncHook.tap("syncHook1",() => {
    console.log("syncHook1");
});
syncHook.tap("syncHook2",() => {
    console.log("syncHook2");
});
syncHook.call();
// syncHook1
// syncHook2
複製代碼

SyncWaterfallHook

SyncWaterfallHook 能夠掛載多個函數,函數依次執行,下一個掛載函數會利用上一個函數的返回值做爲參數。oop

const syncWaterfallHook = new SyncWaterfallHook(["arg1"]);
syncWaterfallHook.tap("syncWaterfallHook1", arg1 => {
    console.log("syncWaterfallHook1", arg1);
    return arg1 + 1;
});
syncWaterfallHook.tap("syncWaterfallHook2", arg1 => {
    console.log("syncWaterfallHook2", arg1);
    return arg1 + 1;
});
syncWaterfallHook.call(1);
// syncWaterfallHook1 1
// syncWaterfallHook2 2
複製代碼

syncBailHook

syncBailHook 能夠掛載多個函數,函數依次執行,當有一個函數有返回值,則接下來的函數都不會執行。源碼分析

const syncBailHook = new SyncBailHook();
syncBailHook.tap("syncBailHook1", () => {
    console.log("syncBailHook1");
    return false;
});
syncBailHook.tap("syncBailHook2", () => {
    console.log("syncBailHook2");
});
syncBailHook.call();
// syncBailHook1
複製代碼

SyncLoopHook

當監聽的函數返回 true,會一直執行此函數,直至返回 false

let syncLoopHook = new SyncLoopHook();
let count = 3;
syncLoopHook.tap("syncLoopHook1", () => {
    console.log(count);
    if (count) {
        count--;
        return true;
    }
    return;
});
syncLoopHook.call();
// 3
// 2
// 1
// 0
複製代碼

AsyncSeriesHook

AsyncSeriesHook 異步鉤子,能夠掛載多個函數,每一個函數都會等待上一個函數執行完,再執行。

const asyncSeriesHook = new AsyncSeriesHook(["arg"]);
asyncSeriesHook.tapPromise("asyncSeriesHook1", arg => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesHook1", arg);
            resolve(arg + 1);
        }, 1000);
    });
});
asyncSeriesHook.tapPromise("asyncSeriesHook2", arg => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesHook2", arg);
        }, 1000);
    });
});
asyncSeriesHook.callAsync(1);
// asyncSeriesHook1 1
// asyncSeriesHook2 1
複製代碼

AsyncSeriesBailHook

AsyncSeriesBailHook 異步鉤子,能夠掛載多個函數,只要一個異步函數 reject 或異步函數有返回值,則直接進入回調函數執,且後續異步再也不執行。若是異步執行結果 undefined,則執行下一個函數。

const asyncSeriesBailHook = new AsyncSeriesBailHook(["arg1"]);
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook1", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook1", arg1);
            resolve();
        }, 1000);
    });
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook2", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook2", arg1);
            resolve(arg1 + 1);
        }, 1000);
    });
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook3", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook3", arg1);
        }, 1000);
    });
});
asyncSeriesBailHook.callAsync(1, err => {
    console.log("err", err);
});
// asyncSeriesBailHook1 1
// asyncSeriesBailHook2 1
// err null
複製代碼

AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook 異步鉤子,能夠掛載多個函數,只要有一個異步函數 reject ,則直接進入回調函數執行,且後續異步再也不執行。若是異步函數有返回值,則會把返回值傳入到下一個函數。

const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(["arg1"]);
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook1", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("AsyncSeriesWaterfallHook1", arg1);
            resolve(arg1 + 1);
        }, 2000);
    });
});
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook2", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("AsyncSeriesWaterfallHook2", arg1);
            resolve();
        }, 2000);
    });
});
asyncSeriesWaterfallHook.callAsync(1, err => {
    console.log(err);
});
// AsyncSeriesWaterfallHook1 1
// AsyncSeriesWaterfallHook2 2
// null
複製代碼

AsyncParallelHook

AsyncParallelHook 異步鉤子,能夠掛載多個函數,同時觸發異步函數執行,和 Promise.all 相似,只要有一個異步函數錯誤,總體結果就錯誤。

const asyncParallelHook = new AsyncParallelHook(["arg1"]);
asyncParallelHook.tapPromise("asyncParallelHook1", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelHook1", arg1);
            resolve(arg1 + 1);
        }, 1000);
    });
});
asyncParallelHook.tapPromise("asyncParallelHook2", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelHook2", arg1);
            reject(arg1 + 1);
        }, 1000);
    });
});
asyncParallelHook.callAsync(1, err => {
    console.log(err);
});
//asyncParallelHook1 1
//asyncParallelHook2 1
// 2
複製代碼

AsyncParallelBailHook

AsyncParallelBailHookAsyncParallelHook 特性同樣。惟一不同的就是,若是綁定的第一個異步函數出錯,那總體狀態就出錯,成功則成功,並立馬執行回調函數。不以其他綁定的異步函數成功與否做爲依據。

const asyncParallelBailHook = new AsyncParallelBailHook(["arg"]);
asyncParallelBailHook.tapPromise("asyncParallelBailHook1", arg => {
    return new Promise((resolve, reject) => {
        console.log("asyncParallelBailHook1", arg);
        reject(arg + 1);
    });
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook2", arg => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelBailHook2", arg);
            resolve(arg + 1);
        }, 100);
    });
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook3", arg => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelBailHook3", arg);
            resolve(arg + 1);
        }, 1000);
    });
});
asyncParallelBailHook.promise(1).then(
    () => {
        console.log("successful");
    },
    () => {
        console.log("error");
    }
);
//asyncParallelBailHook1 1
// error
// asyncParallelBailHook2 1
// asyncParallelBailHook3 1
複製代碼

源碼分析

打開 node_modules/lib/index.js 文件,能夠看到 Tapable 向外界暴露的不止 9 個鉤子,還有 TapableHookMapMultiHook。這三個不是重點,咱們忽略它。

全部鉤子都繼承 Hook 類,每一個鉤子的方法基本上徹底同樣,都有 compile 方法,只有兩點比較例外。

  1. AsyncSeriesWaterfallHookSyncWaterfallHook 重寫了 Hook 的構造函數
  2. 同步鉤子全都重寫了 tapAsynctapPromise 那當咱們綁定一個鉤子時候,Tapable 作了哪些呢?Tapable 會根據傳入的參數生成 options,並把生成的 options 對象插入到 taps 屬性上。

當經過 callcallAsyncpromise 調用鉤子時候,最終會調用各自類上重寫後的 compile 方法。

compile(options) {
	factory.setup(this, options);
	return factory.create(options);
}
複製代碼

factory 值從何而來,它是每個鉤子對應的 xxxHookCodeFactory 類的實例,全部的 xxxHookCodeFactory 類均繼承 HookCodeFactory。當咱們調用的時候,HookCodeFactory 內會組裝執行代碼,組裝完成後便執行此段代碼。

具體每個鉤子的原理,我以爲webpack4.0 源碼分析之 Tapable就寫得十分不錯,這裏就不贅述了。

總結

參考文獻

最後

筆者建議按照順序食用,效果更佳哦。若是本文對您有所幫助,還請不要吝嗇您的點贊,每個點贊都是對筆者最大的鼓勵。

相關文章
相關標籤/搜索