Webpack tapable学习并尝试手动实现(仅流程处理,无法替代源码)

164 阅读3分钟

Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable。

tapable处理任务需要经过以下步骤:

  1. 声明一个钩子

    this.hooks = {
      study: new SyncHook(['name']) //synchook里面传的参数即为事件触发时传入的参数
    }
    
  2. 使用tap方法注册事件

    this.hooks.study.tap('node',(name)=>{
      console.log('node',name)
    })
    this.hooks.study.tap('react',(name)=>{
      console.log('react',name)
    })
    
  3. 触发事件

    this.hooks.study.call('zc');
    

    注册在这个钩子下的事件列表中传入初始值

tapable可以处理同步事件和异步事件:

  1. 处理同步任务分为三种情况:

    • 所有的同步事件依次执行,不care任何一个任务的返回结果
    • 根据上一个事件的返回结果觉得下一个事件是否执行
    • 上一个事件的执行结果作为下一个事件的输入值

    处理第一种情况:

    class SyncHook {
      constructor(args){
        this.tasks = []
      }
      tap(name, task){
        this.tasks.push(task)
      }
      call(...args){
        tasks.forEach((task)=>{
          task(...args)
        })
      }
    }
    
    let hook = new SyncHook(['name'])
    hook.tap('node', (name)=>{
      console.log('node', name)
    })
    hook.tap('react', (name)=>{
      console.log('react', name)
    })
    
    hook.call('zc')
    

    SyncHook类很好解释,包含一个事件存储列表,tap注册函数和call执行函数。

    调用时首先确定最终事件需要传入的参数个数,tap函数中第一个name参数无实际意义。

    处理第二种情况:

    第二种情况相较于第一种情况仅需要判断返回值是否存在而决定是否需要继续执行。

    class SyncHook {
      constructor(args){
        this.tasks = []
      }
      tap(name, task){
        this.tasks.push(task)
      }
      call(...args){
        let i =0;
        let ret;
        while(i<this.tasks.length && !ret) {
        	ret = this.tasks[i](...args)
        }
      }
    }
    
    let hook = new SyncHook(['name'])
    hook.tap('node', (name)=>{
      console.log('node', name)
    })
    hook.tap('react', (name)=>{
      console.log('react', name)
    })
    
    hook.call('zc')
    

    处理第三种情况:

    class SyncHook {
      constructor(args){
        this.tasks = []
      }
      tap(name, task){
        this.tasks.push(task)
      }
      call(...args){
        let i =0;
        let ret;
        while(i<this.tasks.length && !ret) {
        	ret = this.tasks[i]
        }
      }
    }
    
    let hook = new SyncHook(['name'])
    hook.tap('node', (name)=>{
      console.log('node', name)
    })
    hook.tap('react', (name)=>{
      console.log('react', name)
    })
    
    hook.call('zc')
    

    处理第三种情况:

    第三种情况仅需获得返回值传入下一次事件

    class SyncHook {
      constructor(args){
        this.tasks = []
      }
      tap(name, task){
        this.tasks.push(task)
      }
      call(...args){
        let [first, ...others] = this.tasks;
        let ret = first(...args);
    
        others.forEach((task)=>{
          ret = task(ret)
        })
      }
    
    }
    
    let hook = new SyncHook(['name'])
    hook.tap('node', (name)=>{
      console.log('node', name)
      return '学的不错'
    })
    hook.tap('react', (name)=>{
      console.log('react', name)
    })
    
    hook.call('zc')
    

    以上为同步情况处理较为方便。

  2. 处理异步任务

    • 异步并发任务,所有任务依次触发,当获取所有任务的返回结果后再进行下一步。
    • 异步任务同步执行,只有当上一个异步任务触发后才执行下一个任务
    • 上一个异步任务的输出作为下一个的输入(waterfall)

    三种情况均可以通过两种不同的办法实现,异步迭代中间函数next或者promise

    处理第一种情况:

    • 使用异步迭代中间函数next 每次任务结束后出发一次callback,当所有任务结束后触发最终的finalTask 解决思路为设置一个计数器,当callback触发时判断计数器是否达到任务列表的长度,达到则触发finalcallbak

       class AsyncHook {
          constructor(args) {
            this.tasks = []
            this.promiseTasks = [];
      
          }
          tapAsync(name, task) {
            this.tasks.push(task)
          }
          callAsync(...args) {
            // 事件完成后需要触发的函数,此处均不考虑边界情况
            let finalTask = args.pop()
            let index = 0;
            const cb = () => {
              index++;
              (index >= this.tasks.length) && finalTask()
            }
            this.tasks.forEach((task) => {
              task(...args, cb)
            })
          }
        }
      
        let hook = new AsyncHook(['name'])
      
        hook.tapAsync('node', (name, cb) => {
          setTimeout(() => {
            console.log('node', name)
            cb()
          }, 1000)
      
        })
        hook.tapAsync('react', (name, cb) => {
          setTimeout(() => {
            console.log('react', name)
            cb()
          }, 400)
        })
        hook.callAsync('zc',
          () => {
            console.log('finished')
          }
        )
      
    • 使用promise完成

      解决思路为利用Promise.all

      // 添加promise 注册和执行函数
        tapPromise(name, task) {
          this.promiseTasks.push(task)
        }
        callPromise(...args) {
          var promiseArr = this.promiseTasks.map(task => {
            return task(...args)
          })
          return Promise.all(promiseArr)
        }
        
        // 调用方式为
       hook.tapPromise('node', (name) => {
        return new Promise((resolved, rejected) => {
          setTimeout(() => {
            console.log('node', name)
            resolved()
          }, 1000)
        })
      
      })
      hook.tapPromise('react', (name) => {
        return new Promise((resolved, rejected) => {
          setTimeout(() => {
            console.log('react', name)
            resolved()
          }, 400)
        })
      })
      
      hook.callPromise('zc').then(
        () => {
          console.log('finished')
        }
      )
      

    处理第二种情况:

    任务按顺序执行,使用中间函数next,每次异步任务完成后触发next或者使用promise

    class AsyncHook {
        constructor(args) {
          this.tasks = [];
          this.promiseTasks = []
          this.waterTasks = []
          this.waterPromiseTasks = []
        }
        tapAsync(name, task) {
          this.tasks.push(task)
        }
    
        callAsync(...args) {
          let i = 0;
          const lastarg = args.pop()
          let cb = ()=>{
            i++;
            let task = this.tasks[i];
            if (i < this.tasks.length) {
              task(...args, cb)
            } else {
              lastarg()
            }
          }
          this.tasks[0](...args, cb)
        }
        
        hook.tapPromise('node', (name) => {
          return new Promise(
            (resolve, reject) => {
              setTimeout(() => {
                console.log('node', name)
                resolve()
              }, 1000)
            }
          )
    
        })
        
        // promise实现方法
        hook.tapPromise('react', (name) => {
          return new Promise(
            (resolve, reject) => {
              setTimeout(() => {
                console.log('react', name)
                resolve()
              }, 400)
            }
          )
        })
        hook.callPromise('zc').then(()=>{
          console.log('finished')
        })
      }
    

    处理第三种情况:

    上一个的输入作为下一个的输出

    // waterfall 执行的结果传给下一个
    hook.tapWater('node', (name,cb) => {
      setTimeout(() => {
        console.log('node', name)
        cb('err', 'result')
      }, 1000)
    
    })
    hook.tapWater('react', (name, cb) => {
      setTimeout(() => {
        console.log('react', name)
        cb()
      }, 400)
    })
    
    hook.callWater('zc',() => {
      console.log('finished')
    })
    
    // WaterPromise 传入promise
    hook.tapWaterPromise('node', (name) => {
      return new Promise(
        (resolve, reject) => {
          setTimeout(() => {
            console.log('node', name)
            resolve('node')
          }, 1000)
        }
      )
    
    })
    hook.tapWaterPromise('react', (name) => {
      return new Promise(
        (resolve, reject) => {
          setTimeout(() => {
            console.log('react', name)
            resolve('react')
          }, 400)
        }
      )
    })
    
    hook.callWaterPromise('zc').then(()=>{
      console.log('finished')
    })