1、从0开始
- 假如我们现在有一个 Runner类如下,外界可以通过调用exec方法来执行内部逻辑进行build的操作
class Runner {
exec() {
build()
}
}
function build() {
console.log('build')
}
- 但是,我们想要在进行buildSomething之前来进行一些前期的准备工作,如准备node环境或者yarn。更改代码如下:
class Runner {
exec() {
prepareNode()
prepareYarn()
build()
}
}
function prepareNode() {
console.log('prepareNode')
}
function prepareYarn() {
console.log('prepareYarn')
}
function build() {
console.log('build')
}
- 下一步,容易想到可以抽离出来一个prepare函数,将所有的准备工作都丢在prepare函数中,来保证Runner类的纯粹
class Runner {
exec() {
prepare()
buildSomething()
}
}
function prepare() {
prepareNode()
prepareYarn()
}
...
- 下一步,在build之前,不仅想执行prepare,还想执行一些其他的预置操作,比如beforeBuild等,代码就会变成这样
- 可以预想到,配置越多,增加的类似prepare方法就越多
class Runner {
exec() {
prepare()
beforeBuild()
...
build()
}
}
function prepare() {
prepareNode()
prepareYarn()
}
function beforeBuild(){
...
}
...
2、环境准备,依赖注入
- 下一步,通过配置参数的形式传入,再使用EventEmitter来注册任务
const EventEmitter = require("events").EventEmitter;
class Runner {
eventTriger;
constructor(options) {
this.eventTriger = new EventEmitter();
if (Array.isArray(options.prepare)) {
for (const listener of options.prepare) {
this.eventTriger.on("prepare", listener);
}
} else {
this.eventTriger.on("prepare", options.prepare);
}
}
exec() {
this.eventTriger.emit("prepare");
build();
}
}
function build() {
console.log("build something");
}
const runner = new Runner({
prepare: [prepareNode, prepareYarn],
});
runner.exec();
function prepare() {
prepareNode();
prepareYarn();
}
3、控制任务时序
- 支持异步函数调用
- 需要在异步函数 prepare执行完毕之后才能够调用
build
的操作
const EventEmitter = require("events").EventEmitter;
class Runner {
eventTriger;
eventHandlerContext = new Map()
constructor(options) {
this.eventTriger = new EventEmitter();
if (Array.isArray(options.prepare)) {
for (const listener of options.prepare) {
this._on('prepare', listener)
}
} else {
this._on('prepare', options.prepare)
}
}
_on(eventName, listener) {
const wrapperListener = async (...args) => {
const ctx = this.eventHandlerContext.get(eventName);
const ret = await listener(...args)
this.eventHandlerContext.set(eventName, { ...ctx, count: ctx.count++ })
if (ctx.count === this.eventTriger.listenerCount(eventName)) {
ctx.resolve(true)
}
return ret
}
this.eventTriger.on(eventName, wrapperListener);
}
async _emit(eventName, ...args) {
const promise = new Promise((resolve, reject) => {
this.eventHandlerContext.set(eventName, { reject, resolve, count: 0 })
})
this.eventTriger.emit(eventName)
await promise
}
async exec() {
await this._emit("prepare");
build();
}
}
function build() {
console.log("build something");
}
const runner = new Runner({
prepare: [prepareNode, prepareYarn],
});
runner.exec();
function sleep(wait) {
return new Promise(resolve => {
setTimeout(() => {
resolve(wait)
}, wait);
})
}
async function prepareNode() {
await sleep(500)
console.log('prepareNode')
}
async function prepareYarn() {
await sleep(300)
console.log('prepareYarn')
}
function build() {
console.log('build')
}
- 支持异步并行和异步串行
- 在上一步中,实现了支持异步函数调用,但prepareNode的调用会晚于prepareYarn
- 继续改造实现异步串行调用,这里需要注意异步await 不能使用forEach这样的通过回掉的方式实现,普通的for…of…等即可
- 在异步串行中,支持传递上一个函数调用的结果
async function timer3() {
await new Promise((resolve) => {
setTimeout(() => {
console.log('3')
resolve()
}, 3000)
})
}
async function timer2() {
await new Promise((resolve) => {
setTimeout(() => {
console.log('2')
resolve()
}, 2000)
})
}
async function timer1() {
await new Promise((resolve) => {
setTimeout(() => {
console.log('1')
resolve()
}, 1000)
})
}
let arr = [timer3, timer2, timer1];
arr.forEach(async (fn) => {
await fn()
})
for (const listener of listeners:Function[]) {
await listener
}
const EventEmitter = require("events").EventEmitter;
class Runner {
eventTriger;
eventHandlerContext = new Map()
eventNames = []
eventTypeStore = new Map()
seriesListenersStore = new Map()
constructor(options) {
this.eventTriger = new EventEmitter();
this.eventNames = Object.keys(options);
for (const eventName of this.eventNames) {
const { type = 'parallel', listeners } = options[eventName]
this.eventTypeStore.set(eventName, type)
const listenersArr = Array.isArray(listeners) ? listeners : [listeners]
for (const listener of listenersArr) {
this._on(eventName, listener)
}
}
}
_on(eventName, listener) {
const eventType = this.eventTypeStore.get(eventName)
switch (eventType) {
case 'parallel': {
const wrapperListener = async (...args) => {
const ctx = this.eventHandlerContext.get(eventName)
await listener(...args)
this.eventHandlerContext.set(eventName, { ...ctx, count: ctx.count++ })
if (ctx.count === this.eventTriger.listenerCount(eventName)) {
ctx.resolve(true)
}
}
this.eventTriger.on(eventName, wrapperListener)
break;
}
case 'series': {
this.eventTriger.removeAllListeners(eventName)
const listeners = (this.seriesListenersStore.get(eventName) || [])
listeners.push(listener)
this.seriesListenersStore.set(eventName, listeners)
let wrapperListener = async (...args) => {
const ctx = this.eventHandlerContext.get(eventName)
const firstFn = listeners.shift()
let result = await firstFn(...args)
for (const fn of listeners) {
result = await fn(result)
}
ctx?.resolve(true)
}
this.eventTriger.on(eventName, wrapperListener)
break
}
default:
throw new Error(`unknown event type ${this.eventTypeStore.get(eventName)}`)
}
}
async _emit(eventName, ...args) {
const promise = new Promise((resolve, reject) => {
this.eventHandlerContext.set(eventName, { reject, resolve, count: 0 })
})
this.eventTriger.emit(eventName, ...args)
await promise
}
async exec(...args) {
for (const eventName of this.eventNames) {
console.log(`-----------${eventName}生命周期执行-----------`)
await this._emit(eventName, ...args)
}
build();
}
}
function build() {
console.log("build something");
}
const runner = new Runner({
prepare: {
listeners: [prepareNode, prepareYarn],
type: 'parallel'
},
start: {
listeners: [prepareNode, prepareYarn],
type: 'series'
},
end: {
listeners: [pipe1, pipe2],
type: 'series'
}
})
runner.exec(1, 2);
function sleep(wait) {
return new Promise(resolve => {
setTimeout(() => {
resolve(wait)
}, wait);
})
}
async function prepareNode() {
await sleep(500)
console.log('prepareNode')
}
async function prepareYarn() {
await sleep(300)
console.log('prepareYarn')
}
function pipe1(a, b) {
console.log(a, b, 'pipe1执行')
return a + b
}
function pipe2(a) {
console.log(a, 'pipe2执行')
return a * 2
}
function build() {
console.log('build')
}
- 至此一个支持同步、异步、并行、串行的插件系统已经完成啦
-----------prepare生命周期执行-----------
prepareYarn
prepareNode
-----------start生命周期执行-----------
prepareNode
prepareYarn
-----------end生命周期执行-----------
1 2 pipe1执行
3 pipe2执行
-----------生命周期执行结束-----------
build
4、终极大招
- 终极大招— Tapable
Tapable
是 Webpack
团队开发的基于事件驱动的插件模块, Webpack
的 Plugins
插件管理机制就是基于 Tapable
实现。下面我们用 Tapable
来实现一下上面的例子
const {
AsyncParallelHook,
AsyncSeriesHook,
AsyncSeriesWaterfallHook
} = require("tapable");
class TapableRunner {
hooks: { prepare: AsyncParallelHook, start: AsyncSeriesHook, end: AsyncSeriesWaterfallHook}
constructor() {
this.hooks = {
prepare: new AsyncParallelHook(),
start: new AsyncSeriesHook(),
end: new AsyncSeriesWaterfallHook(['a', 'b'])
}
}
async exec() {
await this.hooks.prepare.promise()
await this.hooks.start.promise()
await this.hooks.end.promise(1, 2)
}
}
const tapableRunner = new TapableRunner()
tapableRunner.hooks.prepare.tapPromise('prepareNode', prepareNode)
tapableRunner.hooks.prepare.tapPromise('prepareYarn', prepareYarn)
tapableRunner.hooks.start.tapPromise('prepareNode', prepareNode)
tapableRunner.hooks.start.tapPromise('prepareYarn', prepareYarn)
tapableRunner.hooks.end.tapPromise('pipe1', pipe1)
tapableRunner.hooks.end.tapPromise('pipe2', pipe2)
tapableRunner.exec()
- 总结:对于一些较复杂的项目,插件化的开发方式,可以让我们的项目更加灵活,同时极高的增加项目的可维护性。我们常用的
Webpack
、 Rollup
等都存在 Plugins
机制的设计思想。通过定义好生命周期事件,之后暴露给外部介入,从而实现了不同的功能通过不同的插件来实现的行为