本文主要講解
webpack
與tapable
node
上一篇文章咱們分析當執行 webpack
命令的時候,會從 webpack
目錄執行到 webpack-cli
目錄,最終又回到 webpack
目錄,這一篇文章就主要講解回到 webpack
目錄中又作了哪些事情?webpack
首先打開 webpack/lib/webpack.js
文件,找到 webpack
方法。方法內部首先會驗證 options
是否合法,而後會根據傳入的 options
是否爲數組,生成不一樣的 compiler
對象。web
若是爲非數組則實例化 Compiler
,Compiler
對象繼承 Tapable
,若是是數組則實例化 MultiCompiler
,數組每一項均實例化 Compiler
。最終給生成 compiler
對象注入 plugins
,再根據 target
,選擇性注入插件。好比 target
爲 web
時,注入 xxx 插件,最後視參數調用 run
方法仍是 watch
方法。數組
因爲筆者未用過
Tapable
,因此先講解用法,對使用已掌握的童鞋能夠直接忽略,對tapable
源碼不熟的童鞋能夠直接拉到最後。promise
Tapable
是一個相似於 Node.js
中 EventEmitter
的庫,專一於事件的觸發與處理,同時它也是 webpack
出的一個小型的庫,它容許你建立鉤子,爲鉤子掛載函數,最後調用掛載函數,其更像一個發佈-訂閱系統。異步
Tapable
共有 9 種鉤子,共分爲 2 大類,一類爲 sync
同步鉤子,一類爲 async
異步鉤子。sync
同步鉤子分爲 SyncHook
、SyncBailHook
、SyncWaterfallHook
和 SyncLoopHook
4 種。async
異步鉤子分爲 AsyncParallelHook
、AsyncParallelBailHook
、AsyncSeriesHook
、AsyncSeriesBailHook
和 Async SeriesWaterfallHook
5 種。同步的鉤子,只能經過 tap
調用,而異步的鉤子則能夠經過 tapPromise
、tapAsync
和 tap
來調用。async
const { SyncHook } = require("tapable");
const hook = new SyncHook();
hook.tap("subscribe", () => {
console.log("subscribe");
});
hook.call(); // subscribe
複製代碼
SyncHook
是最基礎的鉤子,它能夠掛載多個函數,掛載的函數依次執行,沒有返回值。函數
const syncHook = new SyncHook();
syncHook.tap("syncHook1",() => {
console.log("syncHook1");
});
syncHook.tap("syncHook2",() => {
console.log("syncHook2");
});
syncHook.call();
// syncHook1
// syncHook2
複製代碼
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
能夠掛載多個函數,函數依次執行,當有一個函數有返回值,則接下來的函數都不會執行。源碼分析
const syncBailHook = new SyncBailHook();
syncBailHook.tap("syncBailHook1", () => {
console.log("syncBailHook1");
return false;
});
syncBailHook.tap("syncBailHook2", () => {
console.log("syncBailHook2");
});
syncBailHook.call();
// syncBailHook1
複製代碼
當監聽的函數返回 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
異步鉤子,能夠掛載多個函數,每一個函數都會等待上一個函數執行完,再執行。
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
異步鉤子,能夠掛載多個函數,只要一個異步函數 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
異步鉤子,能夠掛載多個函數,只要有一個異步函數 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
異步鉤子,能夠掛載多個函數,同時觸發異步函數執行,和 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
和 AsyncParallelHook
特性同樣。惟一不同的就是,若是綁定的第一個異步函數出錯,那總體狀態就出錯,成功則成功,並立馬執行回調函數。不以其他綁定的異步函數成功與否做爲依據。
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 個鉤子,還有 Tapable
、HookMap
和 MultiHook
。這三個不是重點,咱們忽略它。
全部鉤子都繼承 Hook
類,每一個鉤子的方法基本上徹底同樣,都有 compile
方法,只有兩點比較例外。
AsyncSeriesWaterfallHook
和 SyncWaterfallHook
重寫了 Hook
的構造函數tapAsync
和 tapPromise
那當咱們綁定一個鉤子時候,Tapable
作了哪些呢?Tapable
會根據傳入的參數生成 options
,並把生成的 options
對象插入到 taps
屬性上。當經過 call
、callAsync
或 promise
調用鉤子時候,最終會調用各自類上重寫後的 compile
方法。
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
複製代碼
factory
值從何而來,它是每個鉤子對應的 xxxHookCodeFactory
類的實例,全部的 xxxHookCodeFactory
類均繼承 HookCodeFactory
。當咱們調用的時候,HookCodeFactory
內會組裝執行代碼,組裝完成後便執行此段代碼。
具體每個鉤子的原理,我以爲webpack4.0 源碼分析之 Tapable就寫得十分不錯,這裏就不贅述了。
筆者建議按照順序食用,效果更佳哦。若是本文對您有所幫助,還請不要吝嗇您的點贊,每個點贊都是對筆者最大的鼓勵。