介绍
tapable 是一个类似于nodejs 的EventEmitter 的库, 主要是控制钩子函数的发布与订阅,控制着webpack的插件系.webpack的本质就是一系列的插件运行.
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
SyncHook
所有tap注册的函数都会执行SyncBailHook
某一个tap注册的函数有返回值,则后面注册的都不会执行SyncWaterfallHook
瀑布的意思就是上一个监听函数的结果是下一个人的输入SyncLoopHook
所有tap注册的函数都会执行,只是某个监听事件 如果返回了值 这个方法会再次执行,只有返回undefined这个方法才会停止执行AsyncParallelHook
AsyncParallelBailHook
AsyncSeriesHook
AsyncSeriesBailHook
AsyncSeriesWaterfallHook
1 SyncHook
1.1 使用
// 同步的方法
let {SyncHook} = require('tapable');
// 核心就是发布订阅
class Lesson {
constructor(){
this.hooks = {
arch: new SyncHook(['name','age']) // 限制绑定函数的参数
}
}
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('jw');
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
react jw
*/
1.2 手写
class SyncHook{ // 同步的钩子
constructor(args) { // args 起一个限制作用
this.tasks = [];
}
tap(name,task){
this.tasks.push(task);
}
call(...args){
this.tasks.forEach(task=>task(...args));
}
}
let hook = new SyncHook(['name']);
hook.tap('node',function (name) {
console.log('node',name);
}); // tap用来注册事件的 events.on
hook.tap('react', function (name) {
console.log('react', name);
});
hook.call('jw');
2 SyncBailHook
2.1 使用
// 同步的方法
let {SyncBailHook} = require('tapable');
// Bail保险 熔断型的
// 核心就是发布订阅
class Lesson {
constructor(){
this.hooks = {
arch: new SyncBailHook(['name','age']) // 限制绑定函数的参数
}
}
tap(){ // 希望调用这个方法来在钩子上注册事件
this.hooks.arch.tap('node', function (name) { // 第一个参数是注释作用 没有实际意义
console.log('node', name);
return false; // 如果当前函数有返回值则不会继续执行
});
this.hooks.arch.tap('react', function (name) {
console.log('react', name);
});
}
start(){
this.hooks.arch.call('jw');
}
}
let l = new Lesson();
l.tap();
l.start();
// node jw
2.2 手写
class SyncBailHook{ // 同步的钩子
constructor(args) { // args 起一个限制作用
this.tasks = [];
}
tap(name,task){
this.tasks.push(task);
}
call(...args){
let index = 0; // 取任务队列中的第一个
let ret;
do{ // 至少做一次
ret = this.tasks[index++](...args);
}while(ret === undefined && index < this.tasks.length)
}
}
let hook = new SyncBailHook(['name']);
hook.tap('node',function (name) {
console.log('node',name);
// return '停一停有事'
}); // tap用来注册事件的 events.on
hook.tap('react', function (name) {
console.log('react', name);
});
hook.call('jw');
3 SyncWaterfallHook
3.1 使用
// 同步的方法
let {SyncWaterfallHook} = require('tapable');
// 瀑布的意思就是上一个监听函数的结果是下一个人的输入
class Lesson {
constructor(){
this.hooks = {
arch: new SyncWaterfallHook(['name','age'])
}
}
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);
return 'react 更好'
});
this.hooks.arch.tap('webpack', function (data) {
console.log('webpack', data);
});
}
start(){
this.hooks.arch.call('jw');
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
react node学的不错
webpack react 更好
*/
3.2 手写
class SyncWaterfallHook{
constructor(args) {
this.tasks = [];
}
tap(name,task){
this.tasks.push(task);
}
call(...args){
let [first,...others] = this.tasks;
// first就是第一个任务
// reduce
others.reduce((prev,next)=>{
return next(prev);
}, first(...args))
}
}
let hook = new SyncWaterfallHook(['name']);
hook.tap('node',function (name) {
console.log('node',name);
return 'node还不错'
});
hook.tap('react', function (data) {
console.log('react', data);
return 'react ok'
});
hook.tap('webpack', function (data) {
console.log('webpack', data);
});
hook.call('jw');
4 SyncLoopHook
所有tap注册的函数都会执行,只是某个监听事件 如果返回了值 这个方法会再次执行,只有返回undefined这个方法才会停止执行
4.1 使用
let {SyncLoopHook} = require('tapable');
// 某个监听事件 如果返回了值 这个方法会再次执行,只有返回undefined这个方法才会停止执行
class Lesson {
constructor(){
this.index = 0;
this.hooks = {
arch: new SyncLoopHook(['name','age'])
}
}
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);
});
this.hooks.arch.tap('webpack', function (data) {
console.log('webpack', data);
});
}
start(){
this.hooks.arch.call('jw');
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
node jw
node jw
react jw
webpack jw
*/
4.2 手写
class SyncLoopHook{
constructor(args) {
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 total = 0;
let hook = new SyncLoopHook(['name']);
hook.tap('node',function (name) {
console.log('node',name);
return ++total == 3?undefined :'1'
});
hook.tap('react', function (name) {
console.log('react', name);
});
hook.call('jw');
5 AsyncParallelHook
使用
有点像promise.all那种感觉
let { AsyncParallelHook } = require('tapable');
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelHook(['name'])
}
}
tap() {
this.hooks.arch.tapAsync('react', (name, cb) => {
setTimeout(() => {
console.log('react', name);
cb();
}, 2000);
});
this.hooks.arch.tapAsync('react', (name, cb) => {
setTimeout(() => {
console.log('react', name);
cb();
}, 2000);
});
}
start() {
this.hooks.arch.callAsync('jw', function () {
console.log('end');
});
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
react jw
end
*/
某一个tap有返回值时,则结束
let { AsyncParallelHook } = require('tapable');
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelHook(['name'])
}
}
tap() {
this.hooks.arch.tapAsync('react', (name, cb) => {
setTimeout(() => {
console.log('react', name);
cb('出错了'); // BailHook
}, 2000);
});
this.hooks.arch.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name);
cb();
}, 1000);
});
this.hooks.arch.tapAsync('vue', (name, cb) => {
setTimeout(() => {
console.log('vue', name);
cb();
}, 3000);
});
}
start() {
this.hooks.arch.callAsync('jw', function () {
console.log('end');
});
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
react jw
end
vue jw
*/
用promise
let { AsyncParallelBailHook } = require('tapable'); // 实现AsyncParallelBailHook
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelBailHook(['name'])
}
}
tap() {
this.hooks.arch.tapPromise('react', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 2000);
})
});
this.hooks.arch.tapPromise('node', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
})
});
}
start() {
this.hooks.arch.promise('jw').then(function () {
console.log('end');
});
}
}
let l = new Lesson();
l.tap();
l.start();
/*
* node jw
* react jw
end
* */
promise有返回值
let { AsyncParallelBailHook } = require('tapable'); // 实现AsyncParallelBailHook
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelBailHook(['name'])
}
}
tap() {
this.hooks.arch.tapPromise('react', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve('出错了');
}, 2000);
})
});
this.hooks.arch.tapPromise('node', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
})
});
this.hooks.arch.tapPromise('vue', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('vue', name);
resolve();
}, 3000);
})
});
}
start() {
this.hooks.arch.promise('jw').then(function () {
console.log('end');
});
}
}
let l = new Lesson();
l.tap();
l.start();
/*
node jw
react jw
end
vue jw
*/
6 AsyncSeriesHook
使用
let { AsyncSeriesHook } = require('tapable');
// AsyncSeriesHook 串行执行 tapAsync + callAsync
// tapPromise + promise
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncSeriesHook(['name'])
}
}
tap() {
this.hooks.arch.tapPromise('react', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 2000);
})
});
this.hooks.arch.tapPromise('node', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
})
});
}
start() {
this.hooks.arch.promise('jw').then(function () {
console.log('end');
});
}
}
let l = new Lesson();
l.tap();
l.start();
/*
react jw
node jw
end
*/
手写
class AsyncSeriesHook {
constructor() {
this.tasks = [];
}
tapPromise(name, task) {
this.tasks.push(task);
}
promise(...args) {
let [first,...others] = this.tasks;
return others.reduce((p,n)=>{
return p.then(()=>n()); // 把所有的promise串连起来
}, first(...args));
}
}
let hook = new AsyncSeriesHook(['name']);
hook.tapPromise('node', function (name) {
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('node');
resolve();
}, 1000);
})
});
hook.tapPromise('react', function (name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react');
resolve();
}, 1000);
})
});
hook.promise('jw').then(function () {
console.log('all')
});
7 AsyncSeriesBailHook
7.1 使用
let {AsyncSeriesWaterfallHook} = require('tapable');
// AsyncSeriesBailHook
// tapable (compose + promise + asyncCallback)
// webpack
let h = new AsyncSeriesWaterfallHook(['name']);
// tapAsync tapPromise
h.tapAsync('node',function (name,cb) {
setTimeout(() => {
console.log('node');
cb(null,'我饿了');
}, 1000);
});
h.tapAsync('react', function (data, cb) {
setTimeout(() => {
console.log('react', data);
cb();
},2000);
});
h.callAsync('jw',function () {
console.log('end');
});
7.2 手写
// let {AsyncSerieslHook} = require('tapable');
class AsyncSerieslHook{
constructor(){
this.tasks = [];
}
tapAsync(name,task){
this.tasks.push(task);
}
callAsync(...args){ // express 中的中间件的原理
let finalCallback = args.pop();
let index = 0;
let next = ()=>{ // compose
if (index === this.tasks.length) return finalCallback();
let task = this.tasks[index++];
task(...args,next);
}
next();
}
}
let hook = new AsyncSerieslHook(['name']);
hook.tapAsync('node',function (name,cb) {
setTimeout(() => {
console.log('node');
cb('错误');
}, 1000);
});
hook.tapAsync('react', function (name,cb) {
setTimeout(() => {
console.log('react');
cb();
}, 1000);
});
hook.callAsync('jw',function () {
console.log('all')
});