webpack4.0源码分析之Tapable
1 Tapable简介
webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的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的更多相关文章
- AFNetWorking3.0源码分析
分析: AFNetWorking(3.0)源码分析(一)——基本框架 AFNetworking源码解析 AFNetworking2.0源码解析<一> end
- Solr5.0源码分析-SolrDispatchFilter
年初,公司开发法律行业的搜索引擎.当时,我作为整个系统的核心成员,选择solr,并在solr根据我们的要求做了相应的二次开发.但是,对solr的还没有进行认真仔细的研究.最近,事情比较清闲,翻翻sol ...
- Solr4.8.0源码分析(25)之SolrCloud的Split流程
Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...
- Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五)
Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五) 题记:关于SolrCloud的Recovery策略已经写了四篇了,这篇应该是系统介绍Recovery策略的最后一篇了 ...
- Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四)
Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四) 题记:本来计划的SolrCloud的Recovery策略的文章是3篇的,但是没想到Recovery的内容蛮多的,前面 ...
- Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)
Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及P ...
- Solr4.8.0源码分析(21)之SolrCloud的Recovery策略(二)
Solr4.8.0源码分析(21)之SolrCloud的Recovery策略(二) 题记: 前文<Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一)>中提 ...
- Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一)
Solr4.8.0源码分析(20)之SolrCloud的Recovery策略(一) 题记: 我们在使用SolrCloud中会经常发现会有备份的shard出现状态Recoverying,这就表明Solr ...
- Solr4.8.0源码分析(14)之SolrCloud索引深入(1)
Solr4.8.0源码分析(14) 之 SolrCloud索引深入(1) 上一章节<Solr In Action 笔记(4) 之 SolrCloud分布式索引基础>简要学习了SolrClo ...
随机推荐
- 自己用ansible加shell 写的自动安装kubernetes的脚本
脚本地址:https://github.com/shatianxiaozi/auto_install_k8s.git 1. 下载 git clone https://github.com/shatia ...
- 【转载】Sqlserver使用Right函数从最右边向前截取固定长度字符串
在SQL语句查询过程中,Sqlserver支持使用LEFT().RIGHT().SUBSTRING()等几个函数对字符串进行截取操作,其中Left函数表示从开始字符向后截取多少个字符,Right函数表 ...
- python火爆背后
Python是一种非常好的编程语言,也是目前非常有前途的一门学科.有很多工作要做,而且薪水也很高,这已经成为每个人进入IT行业的首选.那么Python能做什么呢?为什么这么热? 那么Python能做什 ...
- Java 之 注解
一.注解介绍 注解概念:注解是说明程序的,给计算机看的. 注释概念:用文字描述程序的,给程序员看的. 注解定义:注解(Annotation),也叫元数据.一种代码级别的说明.它是 JDK1.5 及以后 ...
- visualSVN server 安装成功,但是无法连接,url打不开
转自:https://www.oschina.net/question/878142_91825 点击开始–>程序->VisualSVN–>VisuaSVN Server Manag ...
- Win10开启蓝屏信息记录及文件查看位置的方法
蓝屏,是电脑最常见的故障,一般出现蓝屏时都会显示详细的蓝屏错误信息,方便用户排查故障.但是如果系统未开启蓝屏记录,下文介绍蓝屏日志开启及蓝屏日志文件存放位置.我用的是win10系统 蓝屏日志开启方法步 ...
- WMware Workstation——网络类型:NAT、bridge、host-only
bridge NAT host-only 总结: NAT和host-only都需要虚拟网卡(WMware Adapter Card)实现物理机和虚拟机互访. NAT和bridge都可以访问外网 NAT ...
- mybatis3.1-[topic-18-20]-_映射文件_参数处理_单个参数&多个参数&命名参数 _POJO&Map&TO 三种方式及举例
笔记要点出错分析与总结 /**MyBatis_映射文件_参数处理_单个参数&多个参数&命名参数 * _POJO&Map&TO 三种方式及举例 _ * 单个参数 : #{ ...
- 织梦阿里云OSS解决方案
准备工作 申请OSS账号,并且创建一个public-read的bucket.这里需要权限为public-read是因为后面需要匿名访问. 详细步骤 1.开启织梦远程附件功能2.现在织梦还有远程附件还有 ...
- 14 webpack中url-loader的使用
默认情况下,webpack无法处理css文件中的url地址,不管是图片还是字体库,只要是URL地址,都处理不了,需要第三方loader 1.安装loader cnpm i url-loader fil ...