插件系统设计

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;
    //处理event上下文
    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++ })
            // 类似Promise.all的实现,当已经执行完毕的方法和注册的方法一致时,resolve掉exec的Promise
            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) => {
		        //存储当前event对应的状态
            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')
}

//代码执行结果如下
//prepareYarn
//prepareNode
//build something
  • 支持异步并行和异步串行
    • 在上一步中,实现了支持异步函数调用,但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()
})

//输出结果:1、2、3;
//forEach对回掉的处理,只是简单的调用,不能实现阻塞
//while (index < arr.length) {
   //callback(item, index)   //也就是我们传入的回调函数
//}
  • 所以实现异步串行需要类似这样的操作

for (const listener of listeners:Function[]) {
  await listener
}
  • 最终实现

const EventEmitter = require("events").EventEmitter;

class Runner {
    eventTriger;
    //处理event上下文
    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++ })
                    // 如果listener 全部执行完毕 代表 on 事件执行完毕
                    if (ctx.count === this.eventTriger.listenerCount(eventName)) {
                        ctx.resolve(true)
                    }
                }
                this.eventTriger.on(eventName, wrapperListener)
                break;
            }
            //异步串行
            case 'series': {
                //异步串行需要收集虽有方法,然后只注册一次即可,所以这里每次注销上次注册的回掉
                this.eventTriger.removeAllListeners(eventName)
                //从seriesListenersStore中获取已存储的方法
                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 ... of ...异步串行调用
        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
  • TapableWebpack 团队开发的基于事件驱动的插件模块, WebpackPlugins 插件管理机制就是基于 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()

//输出
//prepareYarn
//prepareNode
//prepareNode
//prepareYarn
//1 2 pipe1执行
//3 pipe2执行
  • 总结:对于一些较复杂的项目,插件化的开发方式,可以让我们的项目更加灵活,同时极高的增加项目的可维护性。我们常用的 WebpackRollup 等都存在 Plugins 机制的设计思想。通过定义好生命周期事件,之后暴露给外部介入,从而实现了不同的功能通过不同的插件来实现的行为
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值