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 ...
随机推荐
- Android - Rxjava 使用和原理
用RxJava写的一个Android的小Demo 我所理解的RxJava——上手其实很简单 http://www.jianshu.com/p/5e93c9101dc5
- HashMap和Hashtable的比较
相同点 HashMap和Hashtable都是存储“键值对(key-value)”的散列表,而且都是采用拉链法解决hash冲突的.但是1.8中,hashmap引入了红黑树.Hashtable没有引入红 ...
- JS多级树结构写法
效果: 一.布局: <div class="three_tree"> <div class="tree_title_cut"> < ...
- Zepto结合Swiper的选项卡
我们昨天说了关于Angular的选项卡,那今天就说一下Swiper的选项卡吧! 今天的选项卡是Zepto结合Swiper的选项卡,咱么明天再说纯纯的Swiper的吧! 既然是关于Zepto和Swipe ...
- Oracle数据库错误消息
Oracle数据库错误消息 导出错误消息 l EXP-00000导出终止失败 原因:导出时产生Oracle错误. 操作:检查相应的Oracle错误消息. l EXP-00001数据域被截断 - 列长度 ...
- 使用CKRule实现促销管理系统
1, 常见的促销模型 促销管理系统在很多地方都有使用,大家去超市就经常体现到,感受到,不少中小型单位都其促销活动,要搞促销活动最好是有应用软件支持,这样就比较灵活管理也方便.而依靠手工处理的话效率会比 ...
- 【起航计划 033】2015 起航计划 Android APIDemo的魔鬼步伐 32 App->Service->Foreground Service Controller service使用,共享service,前台服务,onStartCommand
Android系统也提供了一种称为“Service”的组件通常在后台运行.Activity 可以用来启动一个Service,Service启动后可以保持在后台一直运行,即使启动它的Activity退出 ...
- java中什么是上下文(servletContext)
找了很多大佬的博客,看了之后还不是很清楚上下文到底是怎么回事,我个人理解 所谓上下文,它是用来存储系统的一些初始化信息,例如在jboss中通过配置文件指定了数据源,那么在jboss启动的时候就把这个文 ...
- Siebel escript学习笔记
Siebel(escript)的学习:1.Siebel的数据类型Primitive(原始的)---Number,Integer,Hexadecimal(十六进制),Octal(八进制),Floatin ...
- springMvc-入参对象
1.修改或者添加对象 2.多添件查询时候也会遇到 springMvc能够根据属性自动的封装pojo的对象并且支持关联的对象:大致的原理是在传入后台的时候把前台的属性和对象封装成json的形式传入后台, ...