15、webpack与tapable
事件驱动型事件流工作机制
负责创建bundles的compilation
tapable本身就是一个独立的库
实例化hook注册时间监听
通过hook出发事件监听
执行懒编译
hook本质就是tapable实例对象
hook执行机制分为同步和异步
Hook执行特点:
普通钩子:监听器之间互相不干扰
bailhook:熔断钩子,某个监听返回undefined时后续不执行
waterfallhook:瀑布钩子,上一个监听返回值可以传给下一个
loophook:循环钩子,当前监听未返回false则一直执行
上述都有同步钩子,例如SyncHook
AsyncSeriesHook
异步串行钩子
异步并行钩子
16、同步钩子的使用
const { SyncHook } = require('tapable')
let hook = new SyncHook(['name', 'age'])
hook.tap('fn1', function (name, age) {
console.log('fn1--->', name, age)
})
hook.tap('fn2', function (name, age) {
console.log('fn2--->', name, age)
})
hook.call('zoe', 18)//通过call方法调用
const { SyncBailHook } = require('tapable')
let hook = new SyncBailHook(['name', 'age'])
hook.tap('fn1', function (name, age) {
console.log('fn1--->', name, age)
})
hook.tap('fn2', function (name, age) {
console.log('fn2--->', name, age)
return undefined
})//返回undefined,fn3就不执行了
hook.tap('fn3', function (name, age) {
console.log('fn3--->', name, age)
})
hook.call('lg', 100)
const { SyncWaterfallHook } = require('tapable')
let hook = new SyncWaterfallHook(['name', 'age'])
hook.tap('fn1', function (name, age) {
console.log('fn1--->', name, age)
return 'ret1'
})
hook.tap('fn2', function (name, age) {
//这里的name拿到了ret1
console.log('fn2--->', name, age)
return 'ret2'
})
hook.tap('fn3', function (name, age) {
console.log('fn3--->', name, age)
return 'ret3'
})
hook.call('zce', 33)
const { SyncLoopHook } = require('tapable')
let hook = new SyncLoopHook(['name', 'age'])
let count1 = 0
let count2 = 0
let count3 = 0
hook.tap('fn1', function (name, age) {
console.log('fn1--->', name, age)
if (++count1 === 1) {
count1 = 0
return undefined//如果不返回undefined,就会从头再循环一次
}
return true
})
hook.tap('fn2', function (name, age) {
console.log('fn2--->', name, age)
// if (++count2 === 2) {
// count2 = 0
// return undefined
// }
// return true
})
hook.tap('fn3', function (name, age) {
console.log('fn3--->', name, age)
})
hook.call('foo', 100)
17、异步钩子
const { AsyncParallelHook } = require('tapable')
//并行是指 等待所有并发的异步事件执行之后再执行最终的异步回调
let hook = new AsyncParallelHook(['name'])
// 对于异步钩子的使用,在添加事件监听时会存在三种方式: tap tapAsync tapPromise
// hook.tap('fn1', function (name) {
// console.log('fn1--->', name)
// })
// hook.tap('fn2', function (name) {
// console.log('fn2--->', name)
// })
// hook.callAsync('zoe', function () {
// console.log('最后执行了回调操作')
// })
/* console.time('time')
hook.tapAsync('fn1', function (name, callback) {
setTimeout(() => {
console.log('fn1--->', name)
callback()
}, 1000)
})
hook.tapAsync('fn2', function (name, callback) {
setTimeout(() => {
console.log('fn2--->', name)
callback()
}, 2000)
})
hook.callAsync('lg', function () {
console.log('最后一个回调执行了')
console.timeEnd('time')
}) */
// 03 promise
console.time('time')
hook.tapPromise('fn1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('fn1--->', name)
resolve()
}, 1000)
})
})
hook.tapPromise('fn2', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('fn2--->', name)
resolve()
}, 2000)
})
})
hook.promise('foo').then(() => {
console.log('end执行了')
console.timeEnd('time')
})
//最终输出:
const { AsyncParallelBailHook } = require('tapable')
let hook = new AsyncParallelBailHook(['name'])
console.time('time')
hook.tapAsync('fn1', function (name, callback) {
setTimeout(() => {
console.log('fn1--->', name)
callback()
}, 1000)
})
hook.tapAsync('fn2', function (name, callback) {
setTimeout(() => {
console.log('fn2--->', name)
callback('err’)//这边就会提前结束hook.callAsync的执行
}, 2000)
})
hook.tapAsync('fn3', function (name, callback) {
setTimeout(() => {
console.log('fn3--->', name)
callback()
}, 3000)
})
hook.callAsync('zce', function () {
console.log('最后的回调执行了')
console.timeEnd('time')
})
const { AsyncSeriesHook } = require('tapable')
//异步的串行
let hook = new AsyncSeriesHook(['name'])
console.time('time')
hook.tapPromise('fn1', function (name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('fn1--->', name)
resolve()
}, 1000)
})
})
hook.tapPromise('fn2', function (name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('fn2--->', name)
resolve()
}, 2000)
})
})
hook.promise('foo').then(function () {
console.log('~~~~')
console.timeEnd('time')
})
19、手写异步钩子
const SyncHook = require('./SyncHook.js')
let hook = new SyncHook(['name', 'age'])
hook.tap('fn1', function (name, age) {
console.log('fn1-->', name, age)
})
hook.tap('fn2', function (name, age) {
console.log('fn2-->', name, age)
})
hook.call('zoe66', 18)
/**
- 01 实例化 hook , 定义 _x = [f1, f2, ...] taps = [{}, {}]
- 02 实例调用 tap taps = [{}, {}]
- 03 调用 call 方法, HookCodeFactory setup create
- 04 Hook SyncHook HookCodeFactory
*/
let Hook = require('./Hook.js')
class HookCodeFactory {
args() {
return this.options.args.join(',') // ["name", "age"]===> name, age
}
head() {
return `var _x = this._x;`
}
content() {
let code = ``
for (var i = 0; i < this.options.taps.length; i++) {
code += `var _fn${i} = _x[${i}];_fn${i}(${this.args()});`
}
return code
}
setup(instance, options) { // 先准备后续需要使用到的数据
this.options = options // 这里的操作在源码中是通过 init 方法实现,而我们当前是直接挂在了 this 身上
instance._x = options.taps.map(o => o.fn) // this._x = [f1, f2, ....]
}
create() { // 核心就是创建一段可执行的代码体然后返回
let fn
// fn = new Function("name, age", "var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);")
// 在content中拼接出对应的代码片段
fn = new Function(
this.args(),
this.head() + this.content()
)
return fn
}
}
let factory = new HookCodeFactory()
class SyncHook extends Hook {
constructor(args) {
super(args)
}
compile(options) { // {taps: [{}, {}], args: [name, age]}
factory.setup(this, options)
return factory.create(options)
}
}
module.exports = SyncHook
./Hook.js
class Hook {
constructor(args = []) {
this.args = args
this.taps = [] // 将来用于存放组装好的 {}对象信息
this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3....]
}
tap(options, fn) {
if (typeof options === 'string') {
options = { name: options }
}
options = Object.assign({ fn }, options) // { fn:... name:fn1 }
// 调用以下方法将组装好的 options 添加至this.taps
this._insert(options)
}
_insert(options) {
this.taps[this.taps.length] = options
}
call(...args) {
// 01 创建将来要具体执行的函数代码结构
let callFn = this._createCall()
// 02 调用上述的函数(args传入进去)
return callFn.apply(this, args)
}
_createCall() {
return this.compile({
taps: this.taps,
args: this.args
})
}
}
module.exports = Hook
20、手写同步钩子
class Hook {
constructor(args = []) {
this.args = args
this.taps = [] // 将来用于存放组装好的 {}
this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3....]
}
tap(options, fn) {
if (typeof options === 'string') {
options = { name: options }
}
options = Object.assign({ fn }, options) // { fn:... name:fn1 }
// 调用以下方法将组装好的 options 添加至 []
this._insert(options)
}
tapAsync(options, fn) {
if (typeof options === 'string') {
options = { name: options }
}
options = Object.assign({ fn }, options) // { fn:... name:fn1 }
// 调用以下方法将组装好的 options 添加至 []
this._insert(options)
}
_insert(options) {
this.taps[this.taps.length] = options
}
call(...args) {
// 01 创建将来要具体执行的函数代码结构
let callFn = this._createCall()
// 02 调用上述的函数(args传入进去)
return callFn.apply(this, args)
}
callAsync(...args) {
let callFn = this._createCall()
return callFn.apply(this, args)
}
_createCall() {
return this.compile({
taps: this.taps,
args: this.args
})
}
}
module.exports = Hook
let Hook = require('./Hook.js')
class HookCodeFactory {
args({ after, before } = {}) {
let allArgs = this.options.args
if (before) allArgs = [before].concat(allArgs)
if (after) allArgs = allArgs.concat(after)
return allArgs.join(',') // ["name", "age"]===> name, age
}
head() {
return `"use strict";var _context;var _x = this._x;`
}
content() {
let code = `var _counter = ${this.options.taps.length};var _done = (function () {
_callback();
});`
for (var i = 0; i < this.options.taps.length; i++) {
code += `var _fn${i} = _x[${i}];_fn${i}(name, age, (function () {
if (--_counter === 0) _done();
}));`
}
return code
}
setup(instance, options) { // 先准备后续需要使用到的数据
this.options = options // 这里的操作在源码中是通过 init 方法实现,而我们当前是直接挂在了 this 身上
instance._x = options.taps.map(o => o.fn) // this._x = [f1, f2, ....]
}
create() { // 核心就是创建一段可执行的代码体然后返回
let fn
// fn = new Function("name, age", "var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);")
fn = new Function(
this.args({ after: '_callback' }),
this.head() + this.content()
)
return fn
}
}
let factory = new HookCodeFactory()
class AsyncParallelHook extends Hook {
constructor(args) {
super(args)
}
compile(options) { // {taps: [{}, {}], args: [name, age]}
factory.setup(this, options)
return factory.create(options)
}
}
module.exports = AsyncParallelHook
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。