文章首發於:github.com/USTB-musion…node
對於Webpack有一句話Everything is a plugin,Webpack本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable。Tapable有點相似nodejs的events庫,核心原理也是依賴與發佈訂閱模式。webpack中最核心的負責編譯的Compiler和負責建立bundles的Compilation都是Tapable的實例。下面介紹一下tapable的用法和原理。如下實例的代碼原文地址爲github.com/USTB-musion…react
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
複製代碼
下圖展現了每種類型的做用: webpack
Sync爲同步串行的執行關係,用法以下:git
let { SyncHook } = require("tapable");
class Lesson {
constructor() {
this.hooks = {
arch: new SyncHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tap("node", function(name) {
console.log("node", name);
});
this.hooks.arch.tap("react", function(name) {
console.log("react", name);
});
}
start() {
this.hooks.arch.call("musion");
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * react musion */
複製代碼
SyncHook是一個很典型的經過發佈訂閱方式實現的,實現方式以下:github
// 鉤子是同步的
class SyncHook {
// args => ["name"]
constructor() {
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach(task => task(...args));
}
}
let hook = new SyncHook(["name"]);
hook.tap("react", function(name) {
console.log("react", name);
});
hook.tap("node", function(name) {
console.log("node", name);
});
hook.call("musion");
/** * 打印出來的值爲: * node musion * react musion */
複製代碼
SyncBailHook爲同步串行的執行關係,只要監聽函數中有一個函數的返回值不爲 null,則跳過剩下全部的邏輯,用法以下:web
let { SyncBailHook } = require("tapable");
class Lesson {
constructor() {
this.hooks = {
arch: new SyncBailHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tap("node", function(name) {
console.log("node", name);
//return "stop";
return undefined;
});
this.hooks.arch.tap("react", function(name) {
console.log("react", name);
});
}
start() {
this.hooks.arch.call("musion");
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * react musion */
複製代碼
SyncBailHook的實現:併發
// 鉤子是同步的,bail -> 保險
class SyncBailHook {
// args => ["name"]
constructor() {
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
// 當前函數的返回值
let ret;
// 當前要先執行第一個
let index = 0;
do {
ret = this.tasks[index++](...args);
} while (ret === undefined && index < this.tasks.length);
}
}
let hook = new SyncBailHook(["name"]);
hook.tap("react", function(name) {
console.log("react", name);
return "stop";
});
hook.tap("node", function(name) {
console.log("node", name);
});
hook.call("musion");
/** * 打印出來的值爲: * node musion * react musion */
複製代碼
SyncWaterfallHook爲同步串行的執行關係,上一個監聽函數的返回值能夠傳給下一個監聽函數,用法以下:異步
let { SyncWaterfallHook } = require("tapable");
// waterfall 瀑布 上面會影響下面的
class Lesson {
constructor() {
this.hooks = {
arch: new SyncWaterfallHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tap("node", function(name) {
console.log("node", name);
return "node學得還不錯";
});
this.hooks.arch.tap("react", function(data) {
console.log("react", data);
});
}
start() {
this.hooks.arch.call("musion");
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * react node學得還不錯 */
複製代碼
SyncWaterfallHook的實現:函數
// 鉤子是同步的
class SyncWaterfallHook {
// args => ["name"]
constructor() {
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
let [first, ...others] = this.tasks;
let ret = first(...args);
others.reduce((a, b) => {
return b(a);
}, ret);
}
}
let hook = new SyncWaterfallHook(["name"]);
hook.tap("react", function(name) {
console.log("react", name);
return "react ok";
});
hook.tap("node", function(data) {
console.log("node", data);
return "node ok";
});
hook.tap("webpack", function(data) {
console.log("webpack", data);
});
hook.call("musion");
/** * 打印出來的值爲: * react musion * node react ok * webpack node ok */
複製代碼
SyncLoopHook爲同步循環的執行關係,當監聽函數被觸發的時候,若是該監聽函數返回true時則這個監聽函數會反覆執行,若是返回 undefined 則表示退出循環,用法以下:oop
let { SyncLoopHook } = require("tapable");
// 同步遇到某個不返回undefined的監聽函數會屢次執行
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
arch: new SyncLoopHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tap("node", name => {
console.log("node", name);
return ++this.index === 3 ? undefined : "繼續學";
});
this.hooks.arch.tap("react", data => {
console.log("react", data);
});
}
start() {
this.hooks.arch.call("musion");
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * node musion * node musion * react musion */
複製代碼
SyncLoopHook的實現:
// 鉤子是同步的
class SyncLoopHook {
// args => ["name"]
constructor() {
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach(task => {
let ret;
do {
ret = task(...args);
} while (ret != undefined);
});
}
}
let hook = new SyncLoopHook(["name"]);
let total = 0;
hook.tap("react", function(name) {
console.log("react", name);
return ++total === 3 ? undefined : "繼續學";
});
hook.tap("node", function(data) {
console.log("node", data);
});
hook.tap("webpack", function(data) {
console.log("webpack", data);
});
hook.call("musion");
/** * 打印出來的值爲: * react musion * react musion * react musion * node musion * webpack musion */
複製代碼
AsyncParallelHook爲異步併發的執行關係,用法以下:
let { AsyncParallelHook } = require("tapable");
// 異步的鉤子分爲串行和並行
// 串行:第一個異步執行完,纔會執行第二個
// 並行:須要等待全部併發的異步事件執行後再執行回調方法
// 註冊方法: tap註冊 tapAsync註冊
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tapAsync("node", (name, cb) => {
setTimeout(() => {
console.log("node", name);
cb();
}, 1000);
});
this.hooks.arch.tapAsync("react", (name, cb) => {
setTimeout(() => {
console.log("react", name);
cb();
}, 1000);
});
}
start() {
this.hooks.arch.callAsync("musion", function() {
console.log("end");
});
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * react musion * end */
複製代碼
AsyncParallelHook的實現:
class SyncParralleHook {
constructor() {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
// 拿出最終的函數
let finalCallBack = args.pop();
let index = 0;
// 相似Promise.all
let done = () => {
index++;
if (index === this.tasks.length) {
finalCallBack();
}
};
this.tasks.forEach(task => {
task(...args, done);
});
}
}
let hook = new SyncParralleHook(["name"]);
hook.tapAsync("react", function(name, cb) {
setTimeout(() => {
console.log("react", name);
cb();
}, 1000);
});
hook.tapAsync("node", function(name, cb) {
setTimeout(() => {
console.log("node", name);
cb();
}, 1000);
});
hook.callAsync("musion", function() {
console.log("end");
});
/** * 打印出來的值爲: * react musion * react musion * react musion * node musion * webpack musion */
複製代碼
AsyncSeriesHook爲異步串行的執行關係,用法以下:
// AsyncSeriesHook 異步串行
let { AsyncSeriesHook } = require("tapable");
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncSeriesHook(["name"])
};
}
// 註冊監聽函數
tap() {
this.hooks.arch.tapAsync("node", (name, cb) => {
setTimeout(() => {
console.log("node", name);
cb();
}, 4000);
});
this.hooks.arch.tapAsync("react", (name, cb) => {
setTimeout(() => {
console.log("react", name);
cb();
}, 1000);
});
}
start() {
this.hooks.arch.callAsync("musion", function() {
console.log("end");
});
}
}
let l = new Lesson();
// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();
/** * 打印出來的值爲: * node musion * react musion * end */
複製代碼
AsyncSeriesHook的實現:
class SyncSeriesHook {
constructor() {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
let finalCallback = args.pop();
let index = 0;
let next = () => {
if (this.tasks.length === index) return finalCallback();
let task = this.tasks[index++];
task(...args, next);
};
next();
}
}
複製代碼
AsyncSeriesWaterfallHook爲異步串行的執行關係,上一個監聽函數的中的callback(err, data)的第二個參數,能夠做爲下一個監聽函數的參數,用法以下:
class SyncSeriesWaterfallHook {
constructor() {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
let finalCallback = args.pop();
let index = 0;
let next = (err, data) => {
let task = this.tasks[index];
if (!task) return finalCallback();
// 執行的是第一個
if (index === 0) {
task(...args, next);
} else {
task(data, next);
}
index++;
};
next();
}
}
let hook = new SyncSeriesWaterfallHook(["name"]);
hook.tapAsync("react", function(name, cb) {
setTimeout(() => {
console.log("react", name);
cb(null, "musion");
}, 3000);
});
hook.tapAsync("node", function(name, cb) {
setTimeout(() => {
console.log("node", name);
cb(null);
}, 1000);
});
hook.callAsync("musion", function() {
console.log("end");
});
/** * 打印出來的值爲: * node musion * end */
複製代碼
AsyncSeriesWaterfallHook的實現:
class SyncSeriesWaterfallHook {
constructor() {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
let finalCallback = args.pop();
let index = 0;
let next = (err, data) => {
let task = this.tasks[index];
if (!task) return finalCallback();
// 執行的是第一個
if (index === 0) {
task(...args, next);
} else {
task(data, next);
}
index++;
};
next();
}
}
複製代碼