Webpack Tapable原理详解
directory
- src
- sim ---- 简单的模拟实现
- /.js$/ ---- 使用
代码已上传github, 地址
Detailed
Webpack 就像一条生产线, 要经过一系列的处理流程才能将源文件转换成输出结果。这条生产线上的每个流程都是单一的, 多个流程之间存在依赖关系。只能完成当前处理后才会转交到下一个流程。
插件就像一个插入到生产线中的一个功能, 它会在特定的时机对生产线上的资源进行处理。
这条生产线很复杂, Webpack则是通过 tapable 核心库来组织这条生产线。
Webpack 在运行中会通过 tapable 提供的钩子进行广播事件, 插件只需要监听它关心的事件,就可以加入到这条生产线中,去改变生产线的运作。使得 Webpack整体扩展性很好。
Tapable Hook
Tapable 提供同步(Sync)和异步(Async)钩子类。而异步又分为 异步串行、异步并行钩子类。
右键图片,在新标签中查看完整图片

逐个分析每个钩子类的使用及其原理
同步钩子类
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
同步钩子类通过实例的 tap 方法监听函数, 通过 call发布事件
SyncHook
同步串行不关心订阅函数执行后的返回值是什么。其原理是将监听(订阅)的函数存放到一个数组中, 发布时遍历数组中的监听函数并且将发布时的 arguments传递给监听函数
class SyncHook {
constructor(options) {
this.options = options
this.hooks = [] //存放监听函数的数组
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
for (let i = 0; i < this.hooks.length; i++) {
this.hooks[i](...args)
}
}
}
const synchook = new SyncHook('name')
// 注册监听函数
synchook.tap('name', (data) => {
console.log('name', data)
})
synchook.tap('age', (data) => {
console.log('age', data)
})
// 发布事件
synchook.call('qiqingfu')
打印结果:
name qiqingfu
age qiqingfu
SyncBailHook
同步串行, 但是如果监听函数的返回值不为 null, 就终止后续的监听函数执行
class SyncBailHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
let ret, i = 0
do {
// 将第一个函数的返回结果赋值给ret, 在while中如果结果为 true就继续执行do代码块
ret = this.hooks[i++](...args)
} while(!ret)
}
}
const syncbailhook = new SyncBailHook('name')
syncbailhook.tap('name', (data) => {
console.log('name', data)
return '我的返回值不为null'
})
syncbailhook.tap('age', (data) => {
console.log('age', data)
})
syncbailhook.call('qiqingfu')
执行结果
name qiqingfu
SyncWaterfallHook
同步串行瀑布流, 瀑布流指的是第一个监听函数的返回值,做为第二个监听函数的参数。第二个函数的返回值作为第三个监听函数的参数,依次类推...
class SyncWaterfallHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
let [firstHook, ...otherHooks] = this.hooks
/**
* 通过解构赋值先取出第一个监听函数执行
* 并且将第一个函数的执行结果传递给第二个, 第二个传递给第三个,迭代的过程
*/
let ret = firstHook(...args)
otherHooks.reduce((f,n) => {
return n(f)
}, ret)
}
}
const syncWaterfallHook = new SyncWaterfallHook('name')
syncWaterfallHook.tap('name', data => {
console.log('name', data)
return 23
})
syncWaterfallHook.tap('age', data => {
console.log('age', data)
})
syncWaterfallHook.call('qiqingfu')
打印结果
name qiqingfu
age 23
SyncLoopHook
同步串行, 如果监听函数的返回值为 true, 则反复执行当前的监听函数,直到返回指为 undefind则继续执行下面的监听函数
class SyncLoopHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
for (let i = 0; i < this.hooks.length; i++) {
let hook = this.hooks[i], ret
do{
ret = hook(...args)
}while(ret === true && ret !== undefined)
}
}
}
const syncLoopHook = new SyncLoopHook('name')
let n1 = 0
syncLoopHook.tap('name', data => {
console.log('name', data)
return n1 < 2 ? true : undefined
})
syncLoopHook.tap('end', data => {
console.log('end', data)
})
syncLoopHook.call('qiqingfu')
执行结果
name qiqingfu
name qiqingfu
name qiqingfu 第三次打印的时候, n1的指为2, 返回值为 undefined则执行后面的监听函数
end qiqingfu
异步钩子
- 异步并行
(Parallel)- AsyncParallelHook
- AsyncParalleBailHook
- 异步串行
(Series)- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
凡有异步,必有回调
同步钩子是通过 tap来监听函数的, call来发布的。
异步钩子是通过 tapAsync 或 tapPromise 来监听函数,通过 callAsync 或 promise来发布订阅的。
AsyncParallelHook
异步并行, 监听的函数会一块执行, 哪个函数先执行完就先触发。不需要关心监听函数的返回值。
class AsyncParallelHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
// 订阅
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
// 发布
callAsync(...args) {
/**
* callAsync(arg1, arg2,..., cb)
* 发布的时候最后一个参数可以是回调函数
* 订阅的每一个函数的最后一个参数也是一个回调函数,所有的订阅函数执行完
* 且都调用了最后一个函数,才会执行cb
*/
const finalCallback = args.pop()
let i = 0
// 将这个作为最后一个参数传过去,使用的时候选择性调用
const done = () => {
++i === this.asyncHooks.length && finalCallback()
}
this.asyncHooks.forEach(hook => {
hook(...args, done)
})
}
}
const asyncParallelHook = new AsyncParallelHook('name')
asyncParallelHook.tapAsync('name', (data, done) => {
setTimeout(() => {
console.log('name', data)
done()
}, 2000)
})
asyncParallelHook.tapAsync('age', (data, done) => {
setTimeout(() => {
console.log('age', data)
done()
}, 3000)
})
console.time('time')
asyncParallelHook.callAsync('qiqingfu', () => {
console.log('监听函数都调用了 done')
console.timeEnd('time')
})
打印结果
name qiqingfu
age qiqingfu
监听函数都调用了 done
time: 3002.691ms
AsyncParalleBailHook
暂时不理解
AsyncSeriesHook
异步串行钩子类, 不关心 callback的参数。异步函数一个一个的执行,但是必须调用 done函数。
class AsyncSeriesHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0
const done = () => {
let task = this.asyncHooks[i++]
task ? task(...args, done) : finalCallback()
}
done()
}
}
const asyncSeriesHook = new AsyncSeriesHook('name')
asyncSeriesHook.tapAsync('name', (data, done) => {
setTimeout(() => {
console.log('name', data)
done()
}, 1000)
})
asyncSeriesHook.tapAsync('age', (data, done) => {
setTimeout(() => {
console.log('age', data)
done()
}, 2000)
})
console.time('time')
asyncSeriesHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('time')
})
执行结果
name qiqingfu
age qiqingfu
end
time: 3010.915ms
AsyncSeriesBailHook
同步串行钩子类, callback的参数如果不是 null, 后面所有的异步函数都不会执行,直接执行 callAsync方法的回调函数
class AsyncSeriesBailHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0
const done = data => {
if (data) return finalCallback()
let task = this.asyncHooks[i++]
task ? task(...args, done) : finalCallback()
}
done()
}
}
const asyncSeriesBailHook = new AsyncSeriesBailHook('name')
asyncSeriesBailHook.tapAsync('1', (data, done) => {
setTimeout(() => {
console.log('1', data)
done(null)
}, 1000)
})
asyncSeriesBailHook.tapAsync('2', (data, done) => {
setTimeout(() => {
console.log('2', data)
done(null)
}, 2000)
})
console.time('times')
asyncSeriesBailHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('times')
})
打印结果
1 qiqingfu
2 qiqingfu
end
times: 3012.060ms
AsyncSeriesWaterfallHook
同步串行钩子类, 上一个监听函数 callback(err, data)的第二个参数, 可以作为下一个监听函数的参数
class AsyncSeriesWaterfallHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0, once
const done = (err, data) => {
let task = this.asyncHooks[i++]
if (!task) return finalCallback()
if (!once) {
// 只执行一次
task(...args, done)
once = true
} else {
task(data, done)
}
}
done()
}
}
const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook('name')
asyncSeriesWaterfallHook.tapAsync('1', (data, done) => {
setTimeout(() => {
console.log('1', data)
done(null, '第一个callback传递的参数')
}, 1000)
})
asyncSeriesWaterfallHook.tapAsync('2', (data, done) => {
setTimeout(() => {
console.log('2', data)
done(null)
}, 1000)
})
console.time('timer')
asyncSeriesWaterfallHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('timer')
})
打印结果
1 qiqingfu
2 第一个callback传递的参数
end
timer: 2015.445ms
END
如果理解有误, 麻烦纠正!
参考文章
webpack 4.0 Tapable 类中的常用钩子函数源码分析
Webpack Tapable原理详解的更多相关文章
- I2C 基础原理详解
今天来学习下I2C通信~ I2C(Inter-Intergrated Circuit)指的是 IC(Intergrated Circuit)之间的(Inter) 通信方式.如上图所以有很多的周边设备都 ...
- Zigbee组网原理详解
Zigbee组网原理详解 来源:互联网 作者:佚名2015年08月13日 15:57 [导读] 组建一个完整的zigbee网状网络包括两个步骤:网络初始化.节点加入网络.其中节点加入网络又包括两个 ...
- 块级格式化上下文(block formatting context)、浮动和绝对定位的工作原理详解
CSS的可视化格式模型中具有一个非常重要地位的概念——定位方案.定位方案用以控制元素的布局,在CSS2.1中,有三种定位方案——普通流.浮动和绝对定位: 普通流:元素按照先后位置自上而下布局,inli ...
- SSL/TLS 原理详解
本文大部分整理自网络,相关文章请见文后参考. SSL/TLS作为一种互联网安全加密技术,原理较为复杂,枯燥而无味,我也是试图理解之后重新整理,尽量做到层次清晰.正文开始. 1. SSL/TLS概览 1 ...
- 锁之“轻量级锁”原理详解(Lightweight Locking)
大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意. 原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖 ...
- [转]js中几种实用的跨域方法原理详解
转自:js中几种实用的跨域方法原理详解 - 无双 - 博客园 // // 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同 ...
- 节点地址的函数list_entry()原理详解
本节中,我们继续讲解,在linux2.4内核下,如果通过一些列函数从路径名找到目标节点. 3.3.1)接下来查看chached_lookup()的代码(namei.c) [path_walk()> ...
- WebActivator的实现原理详解
WebActivator的实现原理详解 文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivato ...
- Influxdb原理详解
本文属于<InfluxDB系列教程>文章系列,该系列共包括以下 15 部分: InfluxDB学习之InfluxDB的安装和简介 InfluxDB学习之InfluxDB的基本概念 Infl ...
随机推荐
- Wp及Windows应用商店程序Logo生成器
在开发wp或windows应用商店程序时,需要制作不同分辨率下的logo,往往不同分辨率下的logo仅仅是图片尺寸或图片的内边距不同,为了快速生成不同分辨率下的图片,减少工作量,于是就自己动手开发了个 ...
- mysql主从复制报错 :Incorrect usage of DB GRANT and GLOBAL PRIVILEGES
在配置mysql主从复制时,想通过 grant replication slave on bbs.* to 'bbs'@'192.168.1.3' identified by '123456'; 来限 ...
- SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理
SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理... @RequestMapping(value = "/t ...
- Python爬虫之requests模块(2)
一.今日内容 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 二.回顾 xpath的解析流程 bs4的解析流程 常用xpath表达式 常用bs4解析方法 三. ...
- Linux虚拟系统安装——Ubuntu18.04 & CentOS6.5
Linux虚拟系统安装--Ubuntu18.04 & CentOS6.5 摘要:Linux简介.虚拟系统安装.系统备份与文件介绍 1. Linux简介 (1)1968年,MIT.Bell实验室 ...
- html跨域获取数据
a.com下的a.html,需要嵌入b.com下的b.html.这时建一个静态页面c.html将c.html放到a.com服务器中.b.html在嵌入c.html.这样,将参数值传输到c.html中, ...
- html 表格的一些属性设置
第一种:单元格跨行 第二种:单元格间距 第三种:带有标题的表格 第四种:带标题的表格
- java实现12306的45分钟内支付,45分钟后取消订单功能?
java实现12306的45分钟内支付,45分钟后取消订单功能? - 回答作者: 匿名用户 https://zhihu.com/question/27254071/answer/35948645
- 使用CTE公用表表达式的递归查询(WITH AS)
公用表表达式 (CTE) 具有一个重要的优点,那就是能够引用其自身,从而创建递归 CTE.递归 CTE 是一个重复执行初始 CTE 以返回数据子集直到获取完整结果集的公用表表达式. 当某个查询引用递归 ...
- Laravel 教程 - 实战 iBrand 开源电商 API 系统
iBrand 简介 IYOYO 公司于2011年在上海创立.经过8年行业积累,IYOYO 坚信技术驱动商业革新,通过提供产品和服务助力中小企业向智能商业转型升级. 基于社交店商的核心价值,在2016年 ...