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 ...
随机推荐
- 【学习笔记】2017年7月18日MySQL测试:模拟QQ数据库
模拟测试: QQ数据库管理 一.创建数据库并添加关系和测试数据 ##创建QQ数据库,完成简单的测试 #创建数据库 DROP DATABASE IF EXISTS MyQQ; CREATE DATABA ...
- Spring Data JPA简单使用
用Spring Data JPA操作数据库 这份教程教你用Spring Data JPA从关系数据库mysql中存储和提取数据.总结来自https://spring.io/guides/gs/acce ...
- Java读写锁
Java读写锁,ReadWriteLock.java接口, RentrantReadWriteLock.java实现.通过读写锁,可以实现读-读线程并发,读-写,写-读线程互斥进行.以前面试遇到一个问 ...
- jQuery三——筛选方法、事件
一.jquery常用筛选方法 以下为jquery的常用筛选方法: 代码示例如下: <!DOCTYPE html> <html lang="en"> < ...
- caffe-windows之网络描述文件和参数配置文件注释(mnist例程)
caffe-windows之网络描述文件和参数配置文件注释(mnist例程) lenet_solver.prototxt:在训练和测试时涉及到一些参数配置,训练超参数文件 <-----lenet ...
- HTML字符实体名称/实体编号
字符实体对英文的大小写敏感! 字符实体一: 显示结果 描述 实体名称 实体编号 空格 < 小于号 < < > 大于号 > > & 和号 & ...
- MCU & 51单片机
- 标头停止点不能位于宏或#if块中.
使用VS2010在项目中编写C++头文件**.h时提示: PCH 警告: 标头停止点不能位于宏或#if块中 原因:vs2010的智能感知要求.h必须以非#if系列的预编译指令打头 正确方法:将所有含有 ...
- 超简便的ListView中Adapter的写法
对于 ListView 的使用,他有两个重点的部分,一个是下拉刷新和加载更多,这个今天我们不讲,另外一个是 BaseAdapter 的使用,这个是今天的主角,BaseAdapter 中又有 ViewH ...
- Zygote和System进程的启动过程
##init脚本的启动 +------------+ +-------+ +-----------+ |Linux Kernel+--> |init.rc+-> |app_process| ...