Tapable学习笔记

772 阅读4分钟

简介

webpack事件机制是引用tapable库实现

tapable提供了同步和异步的钩子,通过tap(同步)、tapAsync(done回调式异步)、tapPromise(Promise式异步)注册事件,call,callAsync,promise执行事件

类图

116219383-c55ae0371583b515_fix732.png

Hook数据结构

class Hook {
  constructor(args) {
    if(!Array.isArray(args)) args = [];
    // 把数组args赋值给 _args的内部属性
    this._args = args;
    // 保存所有的tap事件
    this.taps = [];
    // 拦截器数组: 用于修改监听函数的内容
    this.interceptors = [];
    // 调用 内部方法 _createCompileDelegate 然后把返回值赋值给内部属性 _call, 并且暴露给外部属性 call
    this.call = this._call = this._createCompileDelegate("call", "sync");
    /* 调用 内部方法 _createCompileDelegate ,然后把返回值赋值给内部属性 _promise,并且暴露外部属性 promise */
    this.promise = this._promise = this._createCompileDelegate("promise", "promise"); 
    /* 调用 内部方法 _createCompileDelegate,然后把返回值赋值给内部属性 _callAsync, 并且暴露外部属性 callAsync */
    this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async");
    // 用于调用函数的时候,保存钩子数组的变量
    this._x = undefined;
  }
}

钩子定义

钩子即注册的监听函数 tapable事件.png

(1)Sync* :钩子为同步函数,即串行执行同步函数

1.SyncHook : 不关心监听函数的返回值
2.SyncBailHook :  有一个函数的返回值不为 `undefined`,则跳过剩下所有的逻辑
3.SyncWaterfallHook  :  上一个函数的返回值可以传给下一个监听函数
4.SyncLoopHook :  如果函数返回`true`时则该监听函数会反复执行,如果返回 `undefined` 则表示退出循环

(2)Async* : 钩子为异步函数,即串行/并行执行异步函数

1.AsyncSeriesHook : 不关心`callback()`的参数
2.AsyncSeriesBailHook : 钩子函数返回非`undefined`,就会跳过后续函数,直接执行`callAsync`等触发函数绑定的回调函数
3.AsyncSeriesWaterfallHook : 上一个监听函数的中的`callback(err, data)`的第二个参数可以作为下一个监听函数的参数
4.AsyncSeriesLoopHook :  某一步钩子函数会循环执行到返回非undefined,才会开始下一个钩子。Hook回调会在所有钩子回调完成后执行
5.AsyncParallelHook: 不关心监听函数的返回值
6.AsyncParallelBailHook :  只要监听的返回值不为 `undefined`,就会忽略后续的函数,直接执行到`callAsync`绑定的回调函数

自实现tapable简易事件机制

class Hook {
    // 扩展
    constructor(args) {
        this.syncTasks = []
        this.asyncTasks = []
        this.promiseTasks = []
    }
    tap(name, task) {
        this.syncTasks.push(task)
    }
    tapAsync(name, task) {
      this.asyncTasks.push(task)
    }
    tapPromise(name, task) {
        this.promiseTasks.push(task)
    }
}

(1)Sync*(同步钩子)

SyncHook
// 不关心监听函数的返回值
class SyncHook extends Hook {
    call(...args) {
        this.syncTasks.forEach((task) => task(...args))
    }
}
SyncBailHook
// 有一个函数的返回值不为 `undefined`,则跳过剩下所有的逻辑
class SyncBailHook extends Hook {
    call(...args) {
        let ret, index = 0
        do {
            ret = this.syncTasks[index++](...args)
        } while(ret === undefined && index < this.tasks.length)
    }
}
SyncWaterfallHook
// 上一个函数的返回值可以传给下一个监听函数
class SyncWaterfallHook extends Hook {
    call(...args) {
        let [first, ...others] = this.syncTasks
        others.reduce((ret, next) => {
            return next(ret)
        }, first(...args))
    }
}
SyncLoopHook
// 直至返回undefined同步才执行下一个函数,否则重头开始执行
class SyncLoopHook extends Hook {
    call(...args) {
        let len = this.syncTasks.length
		for (let i = 0; i < len; i++) {
            let task = this.syncTasks[i]
            if (task(...args) !== undefined) i = 0
        }
    }
}

(2)Async*

异步钩子; 简易执行方式区分了callback 和 promsie形式的注册和执行

  • callback :注册的function参数为 (...args, cb)
  • promsie :注册的function参数为 (...args)
AsyncSeriesHook
// 不关心`callback()`的参数
class AsyncSeriesHook extends Hook {
    callAsync(...args) {
        const len = this.asyncTasks.length
        let callback = args.pop()
        let index = 0
        let next = async () => {
            if (len === index) return callback()
            let task = this.asyncTasks[index++]
            task(...args, next)
        }
        next() // express
    }
    
    promise(...args) {
        let [first, ...others] = this.promiseTasks
        return others.reduce((p, n) => {     // redux
            return p.then(() => n(...args)) 
        }, first(...args))
        
        // 实现二:
        // this.promiseTasks.reduce((p, n) => p.then(() => n(...args)), Promise.resolve()) 
    }
}
AsyncSeriesBailHook
//  串行执行的钩子函数返回非`undefined`,就会跳过后续函数,直接执行`callAsync`等触发函数绑定的回调函数
class AsyncSeriesBailHook extends Hook {
    callAsync(...args) {
        const len = this.asyncTasks.length
        let callback = args.pop()
        let index = 0
        let next = (val) => {
            if (len === index || !!val) return callback()
            let task = this.asyncTasks[index++]
            task(...args, next)
        }
        next() 
    }
    
    promise(...args) {
        const [first, ...others] = this.promiseTasks
        return new Promise((resolve, reject) => {
            others.reduce((p, n) => p.then(() => n(...args)), first(...args))
        }) 
    }
}
AsyncSeriesWaterfallHook
// 上一个监听函数的中的`callback(err, data)`的第二个参数可以作为下一个监听函数的参数, 前钩子函数不会阻止后续函数执行
class AsyncSeriesWaterfallHook extends Hook {
   callAsync(...args) {
        let callback = args.pop() 
        let index = 0
        let next = (err, ...data) => {
            let task = this.asyncTasks[index++]
            if (!task) return callback(err, ...data) // 不阻止后续执行
            task(...data, next)                      // 即 注册的callback需要传递参数(err, data)
        }
        next(null, ...args)
    }
    
    promise(...args) {
        let [first, ...others] = this.promiseTasks
        return others.reduce((p, n) => {     
            return p.then((v) => n(v)) 
        }, first(...args))

        // 实现二:
        // this.tasks.reduce((p, n) => p.then((v) => n(v)), Promise.resolve(...args)) 
    }
}
AsyncSeriesLoopHook
// 如果函数返回非undefined,执行下一个钩子,否则从第一个函数开始执行
class AsyncSeriesLoopHook extends Hook {
   callAsync(...args) {
        let callback = args.pop() 
        let index = 0
        
        let next = async (err, done) => {
            if (done === undefined) index++
            else index = 0

            let task = this.asyncTasks[index]
            if (!task) return callback(err)
            task(...args, next)
        }
        next(null, true)
    }
    
    promise(...args) {
        let [first, ...others] = this.promiseTasks
        const fn = () => {
            return others.reduce((p, n) => {     
                return p.then((v) => {
                  if (v === undefined) return n(v)
                  else return fn()
                }) 
            }, first(...args))
        }
        
        return fn()
    }
}
AsyncParallelHook
// 同时执行异步函数, 不关心监听函数的返回值, 等所有函数执行完再执行一次callAsync注册的回调函数
class AsyncParallelHook extends Hook {
    callAsync(...args) {
        let callback = args.pop()
        let index = 0, len = this.asyncTasks.length
        let done = () => {
            index++
            if (index === len) callback()
        }
        this.asyncTasks.forEach(task => {
            task(...args, done)
        })
    }
    
    promise(...args) {
        let callback = args.pop()
        let tasks = this.promiseTasks.map(task => task(...args))
        return await Promise.allSettled(tasks)
    }
}
AsyncParallelBailHook
// callback执行顺序不确定?!
class AsyncParallelBailHook extends Hook {
    // 所有done执行才执行callback
    callAsync(...args) {
        const callback = args.pop()
		
        let allRes
        let index = 0, len = this.asyncTasks.length
        let done = () => {
            index++
            allRes && index === len && callback(allRes)
        }
        
        this.asyncTasks.forEach(task => {
            let res = task(...args, done)
            if (!allRes && res !== undefined) {
                allRes = res
            }
        })
    }
    
    // 返回第一个(非undefined的resolve或reject返回)函数的处理结果
    async promise(...args) {
        return new Promsie((resolve, reject) => {
            this.promiseTasks.forEach(task => {
                task(...args).then(val => {
                    if (val !== undefined) {
                        resolve(val)
                    }
                }).catch(errMessage => {
                    if (errMessage !== undefined) {
                        reject(emerrMessage)
                    }
                })
        	})
        })
    }
}

【注】tapable中同步/异步监听函数为同一队列,即hook可按继承关系调用call, callAsync和promsie

源码阅读待写