重学webpack- tapable学习

86 阅读5分钟

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)
})