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 ...
随机推荐
- 项目搭建系列之二:SpringMVC框架下配置MyBatis
1.什么是MyBatis? MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis ...
- c#学习基础(2)存储、值类型和引用类型、变量
程序运行时,它的数据必须存储在内存中,数据项需要多大的内存.存储在什么地方以及如何存储都依赖该数据项的类型 运行中的程序使用两个区域来存储数据:栈和堆 栈是一个内存数组,是一个LIFO(last in ...
- C# ADO.NET 面向对象
ADO.NET跟面向对象的结合 把面向对象跟数据库连接用 在项目里面创建一个新的文件夹 名字为App_Code 在这个App_Code里面创建几个类 主要为拆分问题,标上序号,先干什么在干什么 实 ...
- openlayers 聚合效果
//cyd var cydclusterSource = new ol.source.Cluster({ distance: 40, source: new ol.source.Vector({ fe ...
- 解决flashfxp连接虚拟机报错 530 permission denied
菜鸟使用flashfxp遇到连接报错. [21:36:19] [R] 530 Permission denied.[21:36:19] [R] 连接失败 (连接已被客户端关闭) 搜索后发现,是因为li ...
- JSON.stringify使用
基本使用 JSON.stringify(value[, replacer [, space]]) value 将要序列化成 一个JSON 字符串的值. replacer 可选 如果该参数是一个函数,则 ...
- webpack、babel模块、模块化
一.webpack介绍 webpack这个工具非常强大,解决了前端很繁琐的一些工具流程繁琐的事情.中文官网链接地址:https://www.webpackjs.com/ 1.为什么要使用webpack ...
- PHP连接MySQL数据库的几种方式
PHP 5 及以上版本建议使用以下方式连接 MySQL : MySQLi :MySQLi 只针对 MySQL 数据库,MySQLi 还提供了 API 接口. PDO (PHP Data Objects ...
- sketchup 与 ArcGIS 10 的交互(转)
来自:http://blog.csdn.net/kikitamoon/article/details/9036347 许多用户在 ArcGIS 9.2 时代习惯于使用 Sketchup 插件,但是,9 ...
- github代码上传教程
github 上传代码步骤 一.git以及Github Git是个正快速成长的版本控制系统,它由GitHub维护. 优势: 1.支持离线开发,离线Repository. 2.强大的分支功能,适合多个独 ...