1 Tapable简介

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapablewebpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。本文主要介绍一下Tapable中的钩子函数。

tapable包暴露出很多钩子类,这些类可以用来为插件创建钩子函数,主要包含以下几种:

const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
复制代码

所有钩子类的构造函数都接收一个可选的参数,这个参数是一个由字符串参数组成的数组,如下:

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
复制代码

下面我们就详细介绍一下钩子的用法,以及一些钩子类实现的原理。

2 hooks概览

常用的钩子主要包含以下几种,分为同步和异步,异步又分为并发执行和串行执行,如下图:

首先,整体感受下钩子的用法,如下

序号 钩子名称 执行方式 使用要点
1 SyncHook 同步串行 不关心监听函数的返回值
2 SyncBailHook 同步串行 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑
3 SyncWaterfallHook 同步串行 上一个监听函数的返回值可以传给下一个监听函数
4 SyncLoopHook 同步循环 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
5 AsyncParallelHook 异步并发 不关心监听函数的返回值
6 AsyncParallelBailHook 异步并发 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
7 AsyncSeriesHook 异步串行 不关系callback()的参数
8 AsyncSeriesBailHook 异步串行 callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数
9 AsyncSeriesWaterfallHook 异步串行 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

3 钩子

sync* 钩子

同步串行

(1) SyncHook

不关心监听函数的返回值

  • usage
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。 // 订阅
queue.tap('1', function (name, name2) {// tap 的第一个参数是用来标识订阅的函数的
console.log(name, name2, 1);
return '1'
});
queue.tap('2', function (name) {
console.log(name, 2);
});
queue.tap('3', function (name) {
console.log(name, 3);
}); // 发布
queue.call('webpack', 'webpack-cli');// 发布的时候触发订阅的函数 同时传入参数 // 执行结果:
/*
webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数
webpack 2
webpack 3
*/
复制代码
  • 原理
class SyncHook_MY{
constructor(){
this.hooks = [];
} // 订阅
tap(name, fn){
this.hooks.push(fn);
} // 发布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
复制代码

(2) SyncBailHook

只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑

  • usage
const {
SyncBailHook
} = require("tapable"); let queue = new SyncBailHook(['name']); queue.tap('1', function (name) {
console.log(name, 1);
});
queue.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue.tap('3', function (name) {
console.log(name, 3);
}); queue.call('webpack'); // 执行结果:
/*
webpack 1
webpack 2
*/
复制代码
  • 原理
class SyncBailHook_MY {
constructor() {
this.hooks = [];
} // 订阅
tap(name, fn) {
this.hooks.push(fn);
} // 发布
call() {
for (let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
复制代码

(3) SyncWaterfallHook

上一个监听函数的返回值可以传给下一个监听函数

  • usage
const {
SyncWaterfallHook
} = require("tapable"); let queue = new SyncWaterfallHook(['name']); // 上一个函数的返回值可以传给下一个函数
queue.tap('1', function (name) {
console.log(name, 1);
return 1;
});
queue.tap('2', function (data) {
console.log(data, 2);
return 2;
});
queue.tap('3', function (data) {
console.log(data, 3);
}); queue.call('webpack'); // 执行结果:
/*
webpack 1
1 2
2 3
*/
复制代码
  • 原理
class SyncWaterfallHook_MY{
constructor(){
this.hooks = [];
} // 订阅
tap(name, fn){
this.hooks.push(fn);
} // 发布
call(){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...arguments): hook(result);
}
}
}
复制代码

(4) SyncLoopHook

当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环

  • usage
const {
SyncLoopHook
} = require("tapable"); let queue = new SyncLoopHook(['name']); let count = 3;
queue.tap('1', function (name) {
console.log('count: ', count--);
if (count > 0) {
return true;
}
return;
}); queue.call('webpack'); // 执行结果:
/*
count: 3
count: 2
count: 1
*/
复制代码
  • 原理
class SyncLoopHook_MY {
constructor() {
this.hook = null;
} // 订阅
tap(name, fn) {
this.hook = fn;
} // 发布
call() {
let result;
do {
result = this.hook(...arguments);
} while (result)
}
}
复制代码

async* 钩子

异步并行

(1) AsyncParallelHook

不关心监听函数的返回值。

有三种注册/发布的模式,如下:

异步订阅 调用方法
tap callAsync
tapAsync callAsync
tapPromise promise
  • usage - tap
const {
AsyncParallelHook
} = require("tapable"); let queue1 = new AsyncParallelHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
}); // 执行结果
/*
webpack 1
webpack 2
webpack 3
cost: 4.520ms
*/
复制代码
  • usage - tapAsync
let queue2 = new AsyncParallelHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
}); queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
}); // 执行结果
/*
webpack 1
webpack 2
webpack 3
over
time: 3004.411ms
*/
复制代码
  • usage - promise
let queue3 = new AsyncParallelHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
}); queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
resolve();
}, 2000);
});
}); queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
}); queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
/*
webpack 1
webpack 2
webpack 3
over
cost3: 3007.925ms
*/
复制代码

(2) AsyncParallelBailHook

只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数。

  • usage - tap
let queue1 = new AsyncParallelBailHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// 执行结果:
/*
webpack 1
webpack 2
cost: 4.975ms
*/ 复制代码
  • usage - tapAsync
let queue2 = new AsyncParallelBailHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
return 'wrong';// 最后的回调就不会调用了
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
}); queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
}); // 执行结果:
/*
webpack 1
webpack 2
webpack 3
*/
复制代码
  • usage - promise
let queue3 = new AsyncParallelBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
}); queue3.tapPromise('2', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
reject('wrong');// reject()的参数是一个不为null的参数时,最后的回调就不会再调用了
}, 2000);
});
}); queue3.tapPromise('3', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
}); queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
}); // 执行结果:
/*
webpack 1
webpack 2
error
cost3: 2009.970ms
webpack 3
*/
复制代码

异步串行

(1) AsyncSeriesHook

不关系callback()的参数

  • usage - tap
const {
AsyncSeriesHook
} = require("tapable"); // tap
let queue1 = new AsyncSeriesHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('zfpx', err => {
console.log(err);
console.timeEnd('cost1');
});
// 执行结果
/*
1
2
3
undefined
cost1: 3.933ms
*/
复制代码
  • usage - tapAsync
let queue2 = new AsyncSeriesHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
}); queue2.callAsync('webpack', (err) => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果
/*
webpack 1
webpack 2
webpack 3
undefined
over
cost2: 6019.621ms
*/
复制代码
  • usage - promise
let queue3 = new AsyncSeriesHook(['name']);
console.time('cost3');
queue3.tapPromise('1',function(name){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 1);
resolve();
},1000)
});
});
queue3.tapPromise('2',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 2);
resolve();
},2000)
});
});
queue3.tapPromise('3',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 3);
resolve();
},3000)
});
});
queue3.promise('webapck').then(err=>{
console.log(err);
console.timeEnd('cost3');
}); // 执行结果
/*
webapck 1
webapck 2
webapck 3
undefined
cost3: 6021.817ms
*/
复制代码
  • 原理
class AsyncSeriesHook_MY {
constructor() {
this.hooks = [];
} tapAsync(name, fn) {
this.hooks.push(fn);
} callAsync() {
var slef = this;
var args = Array.from(arguments);
let done = args.pop();
let idx = 0; function next(err) {
// 如果next的参数有值,就直接跳跃到 执行callAsync的回调函数
if (err) return done(err);
let fn = slef.hooks[idx++];
fn ? fn(...args, next) : done();
}
next();
}
}
复制代码

(2) AsyncSeriesBailHook

callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数

  • usage - tap
const {
AsyncSeriesBailHook
} = require("tapable"); // tap
let queue1 = new AsyncSeriesBailHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.timeEnd('cost1');
}); // 执行结果:
/*
1
null
cost1: 3.979ms
*/
复制代码
  • usage - tapAsync
let queue2 = new AsyncSeriesBailHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log(name, 1);
callback();
}, 1000)
});
queue2.tapAsync('2', function (name, callback) {
setTimeout(function () {
console.log(name, 2);
callback('wrong');
}, 2000)
});
queue2.tapAsync('3', function (name, callback) {
setTimeout(function () {
console.log(name, 3);
callback();
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果 /*
webpack 1
webpack 2
wrong
over
cost2: 3014.616ms
*/
复制代码
  • usage - promise
let queue3 = new AsyncSeriesBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 1);
resolve();
}, 1000)
});
});
queue3.tapPromise('2', function (name, callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 2);
reject();
}, 2000)
});
});
queue3.tapPromise('3', function (name, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(name, 3);
resolve();
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.log('over');
console.timeEnd('cost3');
}, err => {
console.log(err);
console.log('error');
console.timeEnd('cost3');
});
// 执行结果:
/*
webpack 1
webpack 2
undefined
error
cost3: 3017.608ms
*/
复制代码

(3) AsyncSeriesWaterfallHook

上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

  • usage - tap
const {
AsyncSeriesWaterfallHook
} = require("tapable"); // tap
let queue1 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(name, 1);
return 'lily'
});
queue1.tap('2', function (data) {
console.log(2, data);
return 'Tom';
});
queue1.tap('3', function (data) {
console.log(3, data);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost1');
}); // 执行结果:
/*
webpack 1
2 'lily'
3 'Tom'
null
over
cost1: 5.525ms
*/
复制代码
  • usage - tapAsync
let queue2 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log('1: ', name);
callback(null, 2);
}, 1000)
});
queue2.tapAsync('2', function (data, callback) {
setTimeout(function () {
console.log('2: ', data);
callback(null, 3);
}, 2000)
});
queue2.tapAsync('3', function (data, callback) {
setTimeout(function () {
console.log('3: ', data);
callback(null, 3);
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果:
/*
1: webpack
2: 2
3: 3
null
over
cost2: 6016.889ms
*/
复制代码
  • usage - promise
let queue3 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1:', name);
resolve('1');
}, 1000)
});
});
queue3.tapPromise('2', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('2:', data);
resolve('2');
}, 2000)
});
});
queue3.tapPromise('3', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('3:', data);
resolve('over');
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.timeEnd('cost3');
}, err => {
console.log(err);
console.timeEnd('cost3');
});
// 执行结果:
/*
1: webpack
2: 1
3: 2
over
cost3: 6016.703ms
*/
复制代码
  • 原理
class AsyncSeriesWaterfallHook_MY {
constructor() {
this.hooks = [];
} tapAsync(name, fn) {
this.hooks.push(fn);
} callAsync() {
let self = this;
var args = Array.from(arguments); let done = args.pop();
console.log(args);
let idx = 0;
let result = null; function next(err, data) {
if (idx >= self.hooks.length) return done();
if (err) {
return done(err);
}
let fn = self.hooks[idx++];
if (idx == 1) { fn(...args, next);
} else {
fn(data, next);
}
}
next();
}
}

链接:https://juejin.im/post/5abf33f16fb9a028e46ec352

webpack4.0源码分析之Tapable的更多相关文章

  1. AFNetWorking3.0源码分析

    分析: AFNetWorking(3.0)源码分析(一)——基本框架 AFNetworking源码解析 AFNetworking2.0源码解析<一> end

  2. Solr5.0源码分析-SolrDispatchFilter

    年初,公司开发法律行业的搜索引擎.当时,我作为整个系统的核心成员,选择solr,并在solr根据我们的要求做了相应的二次开发.但是,对solr的还没有进行认真仔细的研究.最近,事情比较清闲,翻翻sol ...

  3. Solr4.8.0源码分析(25)之SolrCloud的Split流程

    Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...

  4. Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五)

    Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五) 题记:关于SolrCloud的Recovery策略已经写了四篇了,这篇应该是系统介绍Recovery策略的最后一篇了 ...

  5. Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四)

    Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四) 题记:本来计划的SolrCloud的Recovery策略的文章是3篇的,但是没想到Recovery的内容蛮多的,前面 ...

  6. Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)

    Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及P ...

  7. Solr4.8.0源码分析(21)之SolrCloud的Recovery策略(二)

    Solr4.8.0源码分析(21)之SolrCloud的Recovery策略(二) 题记:  前文<Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一)>中提 ...

  8. Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一)

    Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一) 题记: 我们在使用SolrCloud中会经常发现会有备份的shard出现状态Recoverying,这就表明Solr ...

  9. Solr4.8.0源码分析(14)之SolrCloud索引深入(1)

    Solr4.8.0源码分析(14) 之 SolrCloud索引深入(1) 上一章节<Solr In Action 笔记(4) 之 SolrCloud分布式索引基础>简要学习了SolrClo ...

随机推荐

  1. 记https在Android浏览器无法访问

    问题描述 M站静态资源单独配置的https域名,在Android原生浏览器里面打开之后提示证书不安全,在chrome.UC之类的浏览器之下,静态资源都能够正常访问 问题原因 CA证书链不完整 http ...

  2. nginx热加载、热升级、回滚

    修改完配置文件后使用 nginx -s reload 命令进行热加载 编译好新的 nginx 二进制文件后,运行nginx 开启nginx服务,然后使用 kill -USR2 新的nginx_mast ...

  3. Questasim10.6c下载安装教程

    questasim作为modelsim的高级版,用着速度还是比modelsim爽很多,基本上所有操作指令都是和modelsim兼容的. 不同版本的vivado兼容的modelsim版本是不一样的,如果 ...

  4. python使用Pyinstaller打包

    一.前言 python文件打包,将.py文件转化成.exe文件(windows平台),可以使用Pyinstaller来打包 Pyinstaller可以在全平台下使用,但是请注意打包生成的文件不能在全平 ...

  5. Nginx跨域访问场景配置和防盗链

    跨域访问控制 跨域访问 为什么浏览器禁止跨域访问 不安全,容易出现CSRF攻击! 如果黑客控制的网站B在响应头里添加了让客户端去访问网站A的恶意信息,就会出现CSRF攻击 Nginx如何配置跨域访问 ...

  6. python txt文件读写(追加、覆盖)

    (1)在lucky.txt中新增内容(覆盖:每次运行都会重新写入内容) f = "lucky.txt" a =8 with open(f,"w") as fil ...

  7. jenkins中的流水线( pipeline)的理解(未完)

    目录 一.理论概述 Jenkins流水线的发展历程 什么是Jenkins流水线 一.理论概述 pipeline是流水线的英文释义,文档中统一称为流水线 Jenkins流水线的发展历程 ​ 在Jenki ...

  8. 【DRF框架】视图组件

    基于mixins视图类 from rest_framework import mixins # 创建视图 class CreateModelMixin(object) def create(self, ...

  9. 二进制部署kubernetes集群(下篇)

    1.k8S核心资源管理方法 1.1.陈述式资源管理方法 1.1.1.管理名称空间资源 1.1.1.1.查看名称空间 [root@hdss7-21 ~]# kubectl get namespace N ...

  10. 2019招商银行M-Geeker线上比赛题解析

    目录 1. 最大子序和(变体) 2. 矩阵求乘积最大 3. 逐渐平均--值最大 目前已更新:第一题,第二题,第四题 1. 最大子序和(变体) 题目描述: 首先考虑常规的最大子序和的问题,即不能去掉中间 ...