tapable学习
webpack本质上是一种事件流的机制, 它的工作流程就是将各个插件串联起来,而实现这一切的核心就是tapable,tapab的核心原理是依赖发布订阅模式,webpack的中最核心的负责编译的Compiler和负责创建bundles的Compilation都是tapab的示例,所以在学习webpack的时候,有且必要先学习下tapable
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
tapable Hooks预览
tapable 提供了同步和异步钩子(异步又分为异步并行和异步串行),根据事件的终止条件不同,又分为Bail(保险)/waterfall(瀑布)/loop(循环,这里不展示)
每种类型的作用图示(滴滴webpack系列):
- BasicHook: 执行每一个,不关心函数的返回值, 有SyncHook, AsyncParallelHook, AsyncSeriesHook.
- BailHook: 保险hook,遇到第一个hook函数返回值 result !== undefined 则返回,不再继续执行,有 SyncBailHook,AsyncSeriesBailHook,AsyncParallelBailHook.
- WaterfallHook: 瀑布流hook,类似reduce,如果前一个hook执行函数的返回值 result !== undefined, 则result会作为后一个hooks函数的第一个参数,需要顺序执行,所以只有:SyncWaterfallHook, AsyncSeriesWaterfallHook
- LoopHook: 不停的循环执行 Hook,直到所有函数结果 result === undefined。同样的,由于对串行性有依赖,所以只有 SyncLoopHook 和 AsyncSeriseLoopHook
tapable hooks 使用及其实现
SyncHook
SyncHook使用
const { SyncHook } = require('tapable')
class Lesson {
constructor() {
this.hooks = {
arch: new SyncHook(['name'])
}
}
tap(name, cb) {
this.hooks.arch.tap(name, cb)
}
start(name) {
this.hooks.arch.call(name)
}
}
const l = new Lesson()
l.tap('webpack', (data) => {
console.log('webpack', data)
})
l.tap('react', (data) => {
console.log('react', data)
})
l.start('zh')
/*
打印结果
webpack zh
react zh
*/
SyncHook简单实现
class SyncHook {
constructor(args) {
this.tasks = [] // 事件队列
}
tap(eventname, taskCallback) {
// 用于事件注册,将所有注册的事件push进入事件队列数组
this.tasks.push(taskCallback)
}
call(...args) {
// 事件执行函数
this.tasks.forEach(task => task(...args))
}
}
let hook = new SyncHook()
hook.tap('react', (name) => {
console.log('react', name)
})
hook.tap('node', (name) => {
console.log('react', name)
})
hook.call('zh1')
SyncBailHook
SyncBailHook使用
const { SyncBailHook } = require('tapable')
class Lesson {
constructor() {
this.hooks = {
arch: new SyncBailHook(['name'])
}
}
tap(name, cb) {
this.hooks.arch.tap(name, cb)
}
start(name) {
this.hooks.arch.call(name)
}
}
const l = new Lesson()
l.tap('webpack', (data) => {
console.log('webpack', data)
return '这是一个非undefined的结果' // 遇到返回非undefined的hook函数,会终止
})
l.tap('react', (data) => {
console.log('react', data)
})
l.start('zh')
/*
打印结果
webpack zh
*/
SyncBailHook简单实现
class SyncBailHook {
constructor() {
this.tasks = []
}
tap = (n, f) => {
this.tasks.push(f)
}
call = (...args) => {
// hook函数返回值
let ret;
// hook函数索引
let idx = 0
do {
// 先执行
ret = this.tasks[idx++](...args)
} while (typeof ret === 'undefined' && idx >= this.tasks.length);
}
}
const hook = new SyncBailHook(['name'])
hook.tap('react', (name) => {
console.log('react', name)
return 'ret是一个非undefined的值'
})
hook.tap('node', (name) => {
console.log('node', name)
})
hook.call('zh')
SyncWaterfallHook
SyncWaterfallHook用法
const { SyncWaterfallHook } = require('tapable')
class Lesson {
constructor() {
this.hooks = {
arch: new SyncWaterfallHook(['name'])
}
}
tap(name, cb) {
this.hooks.arch.tap(name, cb)
}
start(name) {
this.hooks.arch.call(name)
}
}
const l = new Lesson()
l.tap('webpack', (data) => {
console.log('webpack', data)
return '这是一个非undefined的结果'
})
l.tap('react', (data) => {
console.log('react', data)
})
l.start('zh')
/*
打印结果
webpack zh
react 这是一个非undefined的结果
*/
SyncWaterfallHook简单实现(reduce)
class SyncWaterfallHook {
constructor() {
this.tasks = []
}
tap = (n, f) => this.tasks.push(f)
call = (args) => {
// 将每次hook函数执行的结果传递给下一个hook,如果是undefined 则依然是args
const [first, ...rest] = this.tasks
const ret = first(args)
const firstRet = ret || args
rest.reduce((acc, curHook) => {
return curHook(acc)
}, firstRet)
}
}
const hook = new SyncWaterfallHook()
hook.tap('react', (name) => {
console.log('react', name)
return 'ret是一个非undefined的值'
})
hook.tap('node', (name) => {
console.log('node', name)
})
hook.call('zh')
AsyncParallelHook
AsyncParallelHook的用法
// 使用tapAsync和callAsync
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelHook(['name'])
}
}
tap() {
// tapAsync 的第二个参数cb, 需要在执行事件结束后调用,告诉AsyncParallelHook,事件函数执行完毕
this.hooks.arch.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb()
}, 1000);
})
this.hooks.arch.tapAsync('react', (name, cb) => {
setTimeout(() => {
console.log('react', name)
cb()
}, 500);
})
}
start(name) {
// 执行结束后 会调用callAsync的回调
this.hooks.arch.callAsync(name, () => {
console.log('执行结束end')
})
}
}
const l = new Lesson()
l.tap()
l.start('zh')
AsyncParallelHook简单实现
class AsyncParallelHook {
constructor() {
this.tasks = [] // 事件队列
}
tapAsync = (name, taskFn) => {
this.tasks.push(taskFn)
}
callAsync = (...args) => {
// 取出结束回调函数
const finalCallback = args.pop()
// 何时结束?
let index = 0
// 定义结束函数
const done = () => {
index++
if (index === this.tasks.length) {
finalCallback()
}
}
// args已经去除了回调
this.tasks.forEach(task => task(args, done))
}
}
const hook = new AsyncParallelHook()
hook.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb()
}, 1000);
})
hook.tapAsync('webpack', (name, cb) => {
setTimeout(() => {
console.log('webpack', name)
cb()
}, 500);
})
AsyncParallelBailHook
AsyncParallelBailHook的使用(此次使用tapPromise和promise)
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncParallelBailHook(['name'])
}
}
tap() {
// tapPromise注册事件
this.hooks.arch.tapPromise('onde', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve('出错')
}, 1000);
})
})
this.hooks.arch.tapPromise('rect', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('rect', name)
resolve()
}, 1000);
})
})
}
start(name) {
// 执行结束后 会调用callAsync的回调
this.hooks.arch.promise('zh').then(() => {
console.log('end')
}).catch(err => {
console.log(err)
})
}
}
const l = new Lesson()
l.tap()
l.start('zh')
/*
打印结果:
node事件函数,resolve一个非undefined的值, 所以会直接执行结束,但是AsyncParallelBailHook 是异步并行, 所以依然会执行rect事件函数
node zh
end
rect zh
*/
AsyncSeriesHook
AsyncSeriesHook用法
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncSeriesHook(['name'])
}
}
tap() {
// tapPromise注册事件
this.hooks.arch.tapPromise('onde', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve()
}, 1000);
})
})
this.hooks.arch.tapPromise('rect', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('rect', name)
resolve('测试')
}, 1000);
})
})
}
start(name) {
// 执行结束后 会调用callAsync的回调
this.hooks.arch.promise('zh').then(() => {
console.log('end')
}).catch(err => {
console.log(err)
})
}
}
const l = new Lesson()
l.tap()
l.start('zh')
/*
打印结果 发现 是先打印ed node zh,然后在打印rect zh,验证了AsyncSeriesHook 是异步串行
*/
AsyncSeriesHook简单实现
// tapAsync版本
class AsyncSeriesHook {
constructor() {
this.tasks = []
}
tapAsync = (n, f) => {
// 这里应该没有实现异步注册
this.tasks.push(f)
}
callAsync = (...args) => {
// 取出结束函数
const finalCallback = args.pop()
// hook函数执行索引
let index = 0
// 定义next函数
const next = () => {
// 何时结束?
if (index === this.tasks.length) return finalCallback()
// 递归
this.tasks[index++](...args, next)
}
next()
}
}
const hook = new AsyncSeriesHook()
hook.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb()
}, 800);
})
hook.tapAsync('react', (name, cb) => {
setTimeout(() => {
console.log('react', name)
cb()
}, 800);
})
hook.callAsync('zh', () => {
console.log('end')
})
// tapPromise版本
class AsyncSeriesHook {
constructor() {
this.tasks = []
}
tapPromise = (n, f) => this.tasks.push(f)
promise = (...args) => {
const [first, ...rest] = this.tasks
const ret = first(...args)
return rest.reduce((acc, curP) => {
return acc.then(() => curP(...args))
}, ret)
}
}
const hook = new AsyncSeriesHook()
hook.tapPromise('node', (name, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve()
}, 800);
})
})
hook.tapPromise('react', (name, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name)
resolve()
}, 800);
})
})
hook.promise('zh').then(() => {
console.log('end')
})
AsyncSeriesWaterfallHook
AsyncSeriesWaterfallHook用法
class Lesson {
constructor() {
this.hooks = {
arch: new AsyncSeriesWaterfallHook(['name'])
}
}
tap() {
// tapPromise注册事件
this.hooks.arch.tapPromise('onde', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve('测试')
// reject('测试')
}, 1000);
})
})
this.hooks.arch.tapPromise('rect', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('rect', name)
resolve()
}, 1000);
})
})
}
start(name) {
// 执行结束后 会调用callAsync的回调
this.hooks.arch.promise('zh').then(() => {
console.log('end')
}).catch(err => {
console.log('error', err)
})
}
}
const l = new Lesson()
l.tap()
l.start('zh')
/*
node zh
rect 测试
end
*/
AsyncSeriesWaterfallHook简单实现
class AsyncSeriesWaterfallHook {
constructor() {
this.tasks = []
}
tapPromise = (n, f) => {
this.tasks.push(f)
}
promise = (...args) => {
const [first, ...rest] = this.tasks
let ret = first(...args)
return rest.reduce((acc, curP) => {
return acc.then(data => {
const arg = [data] || args
return curP(...arg)
})
}, ret)
}
}
const hook = new AsyncSeriesWaterfallHook()
hook.tapPromise('node', (name, cb) => {
return new Promise((res, rej) => {
setTimeout(() => {
console.log('node', name)
res('传递data')
// res()
// rej('err')
}, 800);
})
})
hook.tapPromise('react', (name, cb) => {
return new Promise((res, rej) => {
setTimeout(() => {
console.log('react', name)
res()
}, 800);
})
})
hook.promise('zh').then(res => {
console.log('end', res)
}).catch(err => {
console.log('catch', err)
})