[原始碼-webpack02-前置知識] Tapable

woow_wu7發表於2020-04-05

導航

[深入01] 執行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件迴圈
[深入05] 柯里化 偏函式 函式記憶
[深入06] 隱式轉換 和 運算子
[深入07] 瀏覽器快取機制(http快取機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模組化
[深入13] 觀察者模式 釋出訂閱模式 雙向資料繫結
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue專案
[部署03] gitlab-CI

[原始碼-webpack01-前置知識] AST抽象語法樹
[原始碼-webpack02-前置知識] Tapable

[原始碼-webpack02-前置知識] Tapable

前置知識

一些單詞

bail:保險,保證
parallel:並行的
series:序列的,連續的

optional:可選的
// All Hook constructors take one optional argument, which is a list of argument names as strings.
// 所有的 Hook 建構函式都接收一個可選的引數,是一個引數名稱字串組成的陣列

practice:實踐
// The best practice is to expose all hooks of a class in a hooks property
// 最佳實踐是在hooks屬性中公開類的所有鉤子

accelerate:加速
brake:剎車
waterfall:瀑布
複製程式碼

釋出訂閱模式

  • 釋出者:publisher
  • 訂閱者:subscribe
  • 中介:topic/event channel
  • ( 中介 ) 既要接收 ( 釋出者 ) 釋出的訊息,又要將訊息派發給訂閱了該事件的 ( 訂閱者 )
    • ( 中介 ) 需要根據 ( 不同的事件 ),儲存相應的 ( 訂閱者 ) 資訊
    • 通過 ( 中介物件 ) 完全 ( 解耦 ) 了 ( 釋出者 ) 和 ( 訂閱者 )
  • 釋出訂閱模式程式碼實現 - es5
// 釋出訂閱模式

<script>
  const pubsub = {};
  // pubsub中介物件
  // pubsub中具有subscribe,unSubscribe,publish等方法

  (function(pubsub) {
    const topics = {};
    // topics是一個 ( 事件 ) 和訂閱該事件的 ( 訂閱者 ) 對應關係的一個物件
    // key:是不同的事件
    // value:是訂閱了該事件的訂閱者的陣列
    // event_name: [{fname: fn.name, fn}]
    
    // 訂閱
    pubsub.subscribe = function(eventName, fn) {
      // 不存在該事件對應的訂閱者物件陣列, 新建
      // 存在,向陣列中新增訂閱者物件
      if (!topics[eventName]) {
        topics[eventName] = []
      }
      topics[eventName].push({
        fname: fn.name,
        fn
      })
    }

    // 釋出
    pubsub.publish = function(eventName, params) {
      if (!topics[eventName].length) {
        return
      }
      // 取出所有訂閱者中的更新函式,並執行
      topics[eventName].forEach((item, index) => {
        item.fn(params)
      })
    }

    // 取消訂閱
    pubsub.unSubscribe = function(eventName, fname) {
      if (!topics[eventName]) {
        return
      }
      topics[eventName].forEach((item, index) => {
        if (item.fname === fname) {
          topics[eventName].splice(index, 1) // 刪除該事件對應的訂閱者物件的更新函式同名的訂閱者物件
        }
      })
    }
  })(pubsub)

  function goFn1(params) {
    console.log('goFn1', params)
  }
  function goFn2(params) {
    console.log('goFn2', params)
  }

  pubsub.subscribe('go', goFn1) // 訂閱
  pubsub.subscribe('go', goFn2)

  pubsub.publish('go', '1111111') // 釋出

  pubsub.unSubscribe('go', goFn2.name) // 取消訂閱
  pubsub.publish('go', '22222')
</script>
複製程式碼
  • 釋出訂閱模式程式碼實現 - es6
<script>
  class PubSub { // PubSub類
    constructor() {
      this.topics = {} 
      // 維護事件和訂閱者對應關係的物件
      // 事件 <-> 訂閱者陣列
    }
    subscribe(eventName, fn) { // 訂閱
      if (!this.topics[eventName]) {
        this.topics[eventName] = []
      }
      this.topics[eventName].push({
        fname: fn.name,
        fn
      })
    }
    publish(eventName, params) { // 釋出
      if (!this.topics[eventName].length) {
        return
      }
      this.topics[eventName].forEach((item, index) => {
        item.fn(params)
      })
    }
    unSubscribe(eventName, fname) { // 取消訂閱
      if (!this.topics[eventName]) {
        return
      }
      this.topics[eventName].forEach((item, index) => {
        if (item.fname === fname) {
          this.topics[eventName].splice(index, 1)
        }
      })
    }
  }

  const pubsub = new PubSub()

  function goFn1(params) {
    console.log('goFn1', params)
  }
  function goFn2(params) {
    console.log('goFn2', params)
  }

  pubsub.subscribe('go', goFn1)
  pubsub.subscribe('go', goFn2)

  pubsub.publish('go', '1') // 釋出

  pubsub.unSubscribe('go', goFn2.name) // 取消訂閱
  pubsub.publish('go', '2')
</script>
複製程式碼

Tapable

  • 核心就是釋出訂閱模式
  • 安裝:npm install tapable -S

SyncHook

  • 同步方式的釋出訂閱模式
  • const synchook = new SyncHook(['params'])
  • synchook.tap() // 訂閱,註冊
  • synchook.call() // 釋出,執行
const { SyncHook } = require('tapable')

class Work {
  constructor() {
    this.hooks = {
      city: new SyncHook(['who']) // 這裡要求在訂閱事件時的回撥中需要傳參
    }
  }
  // 訂閱,註冊
  tap(eventName) {
    this.hooks.city.tap(eventName, function(who){
      console.log(who, eventName)
    })
  }
  // 釋出,執行
  publish() {
    this.hooks.city.call('woow_wu7')
  }
}
const work = new Work()
work.tap('chongqing') // 訂閱
work.tap('hangzhou')
work.publish() // 釋出
複製程式碼

--------- 對比參考
const { SyncHook } = require('tapable')

class Lesson {
  constructor() {
    this.hook = {
      arch: new SyncHook(['name']) // 同步鉤子,在 .tap()函式的引數函式中接收一個引數
    }
  }
  tap() {
    this.hook.arch.tap('react', (name) => { // name引數是在 .call()時傳入的
      console.log('react', name)
    })
    this.hook.arch.tap('vue', (name) => {
      console.log('vue', name)
    })
  }
  publish() {
    this.hook.arch.call('woow_wu7')
  }
}

const lesson = new Lesson(['name'])
lesson.tap() // 註冊
lesson.publish() // 釋出
複製程式碼

SyncHook模擬實現

class SyncHook {
  constructor() {
    this.observers = []
    // observers 是觀察者物件組成的陣列,觀察者物件中包含event事件名稱, 和fn任務函式
    // [{event:eventName, fn: fn}]
  }

  // 註冊觀察者物件
  // 更簡單點可以直接註冊事件
  tap(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }

  // 執行註冊的觀察者物件中的fn函式
  call(...params) {
    this.observers.forEach(item => item.fn(item.event, ...params))
  }
}

const synchook = new SyncHook(['params'])

synchook.tap('react', function(eventName, name) {
  console.log(eventName, name)
})
synchook.tap('vue', function(eventName, name) {
  console.log(eventName, name)
})

synchook.call('woow_wu7')
複製程式碼

SyncBailHook

  • SyncBailHook提供了 ( 終止執行 ) 訂閱者陣列中監聽函式的機制
  • 當 .tap() 中的監聽函式返回值是 ( !undefined ) 時會 ( 終止執行 ) .call() 函式
let { SyncBailHook } = require('tapable');

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncBailHook(['name'])
    };
  }

  tap() {
    this.hooks.arch.tap('vue', function(name) {
      console.log('vue', name);
      return 'SyncBailHook當返回值是!undefined時,就會停止執行'; 
      // ---------------------------- SyncBailHook當.tap()的引數監聽函式返回值是 ( !undefined  ) 時就不再往下繼續執行
      // return undefined  ---------- 返回值是undefined則不受影響,因為函式預設的返回值就是undefined
    });

    this.hooks.arch.tap('react', function(name) {
      console.log('react', name);
    });
  }
  
  start() {
    this.hooks.arch.call('woow_wu7');
  }
}

let lesson = new Lesson();
lesson.tap();
lesson.start();

複製程式碼

SyncBailHook模擬實現

class SyncBailHook {
  constructor() {
    this.observers = []
    // observers 是觀察者物件組成的陣列,觀察者物件中包含event事件名稱, 和fn任務函式
    // [{event:eventName, fn: fn}]
  }

  // 註冊觀察者物件
  // 更簡單點可以直接註冊事件
  tap(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }

  // 執行註冊的觀察者物件中的fn函式
  call(...params) {
    // this.observers.forEach(item => item.fn(item.event, ...params))
    let res = undefined;
    for(let i = 0; i < this.observers.length; i++) {
      const currentObj = this.observers[i] // ---------------------------------- 多次用到,就快取提升效能
      res = currentObj.fn(currentObj.event, ...params)
      if (res !== undefined) { 
        // --------------------------- 迴圈陣列時做判斷,如果函式返回值是 (!undefined) 就跳出 .call() 函式
        return
      }
    }
  }
}

const syncBailHook = new SyncBailHook(['params'])

syncBailHook.tap('react', function(eventName, name) {
  console.log(eventName, name)
  return 'stop'
})
syncBailHook.tap('vue', function(eventName, name) {
  console.log(eventName, name)
})

syncBailHook.call('woow_wu7')
複製程式碼

SyncWaterfallHook

let { SyncWaterfallHook } = require('tapable');
// SyncWaterfallHook將上一個.tap() 的引數回撥函式的 ( 返回值 ) 作為 ( 引數 ) 傳給下一個 .tap() 的引數回撥函式

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncWaterfallHook(['name'])
    };
  }
  // 註冊監聽函式
  tap() {
    
    this.hooks.arch.tap('vue', function(name) {
      console.log('vue', name);
      return 'vue不錯'; 
      // ---------------------------- SyncBailHook當返回值是 !undefined 時就不再往下繼續執行
      // return undefined  ---------- 返回值是undefined則不受影響,因為函式預設的返回值就是undefined
    });

    this.hooks.arch.tap('react', function(name) {
      console.log('react', name); // 這裡的name就是上一個回撥的返回值,即vue不錯
      return 'react不錯'
    });

    this.hooks.arch.tap('node', function(name) {
      console.log('node', name); // 這裡的name是上一個回撥的返回值,即react不錯
    });
    
  }
  start() {
    this.hooks.arch.call('woow_wu7');
  }
}

let lesson = new Lesson();
lesson.tap();
lesson.start();

// vue woow_wu7
// react vue不錯
// node react不錯
複製程式碼

SyncWaterfallHook模擬實現

  • 主要利用陣列的 reduce() 方法迭代
class SyncWaterfallHook {
  constructor() {
    this.observers = []
    // observers 是觀察者物件組成的陣列,觀察者物件中包含event事件名稱, 和fn任務函式
    // [{event:eventName, fn: fn}]
  }

  // 註冊觀察者物件
  // 更簡單點可以直接註冊事件
  tap(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }

  // 執行註冊的觀察者物件中的fn函式
  call(...params) {
    // this.observers.forEach(item => item.fn(item.event, ...params))
    const [first, ...rest] = this.observers
    const fisrtRes = this.observers[0].fn(...params)

    rest.reduce((a, b) => {
      return b.fn(a)
    }, fisrtRes)
    // 第一次:當reduce存在第二個引數時,a = fisrtRes, b則就是陣列的第一個成員
    // 第二次:a = 第一次的返回值b.fn(a),b則是陣列的第二個成員
    // ...
  }
}

const syncWaterfallHook = new SyncWaterfallHook(['params'])

syncWaterfallHook.tap('react', function(name) {
  console.log('react', name)
  return 'react ok'
})
syncWaterfallHook.tap('vue', function(name) {
  console.log('vue', name)
  return 'vue ok'
})
syncWaterfallHook.tap('node', function(name) {
  console.log('node', name)
})

syncWaterfallHook.call('woow_wu7')

// react woow_wu7
// vue react ok
// node vue ok
複製程式碼

SyncLoopHook

  • 當 當前監聽函式返回 ( !undefined ) 時會多次執行,直到當前的監聽函式返回undefined時停止執行當前函式,繼續執行下一個函式
let { SyncLoopHook } = require('tapable');
// SyncLoopHook 
// 當 .tap()的回撥函式中返回值是 !undefined 時就會多次執行,知道返回值是undefined就會終止執行,則繼續執行下一個 .tap()

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncLoopHook(['name'])
    };
  }
  index = 0;
  // 這裡index是掛在Lesson.prototype上的
  // 如果在constructor中宣告 this.index = 0 則 this表示例項物件
  // 之所以在tap()中可以呼叫this.index是因為tap的呼叫實在less例項上呼叫的,所以this表示例項則可以獲取到this.index

  // 註冊監聽函式
  tap() {
    const that = this;
    this.hooks.arch.tap('react', function(name) {
      console.log('react', name);
      return ++that.index === 3 ? undefined : 'react不錯';
      // 返回值不為undefined就會一直執行該tap函式,直到為undefined
    });
    this.hooks.arch.tap('node', function(name) {
      console.log('node', name);
    });
  }
  start() {
    this.hooks.arch.call('woow_wu7');
  }
}

let lesson = new Lesson();
lesson.tap();
lesson.start();

複製程式碼

SyncLoopHook模擬實現

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
class SyncLoopHook {
  constructor() {
    this.observers = [];
    // observers 是觀察者物件組成的陣列,觀察者物件中包含event事件名稱, 和fn任務函式
    // [{event:eventName, fn: fn}]
  }

  // 註冊觀察者物件
  // 更簡單點可以直接註冊事件
  tap(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }

  // 執行註冊的觀察者物件中的fn函式
  call(...params) {
    this.observers.forEach(item => {
      let res;
      do {
        res = item.fn(...params)
      } while(res !== undefined);
    })
  }
}

const syncLoopHook = new SyncLoopHook(['params'])

let count = 0;

syncLoopHook.tap('react', function(name) {
  console.log('react', name)
  return ++count === 3 ? undefined : 'go on'
})
// 注意:函式的作用域是函式定義時所在的物件
// 當在 .call() 方法中多次呼叫時,count會增加,因為函式和變數的作用域都是在宣告時確認的

syncLoopHook.tap('vue', function(name) {
  console.log('vue', name)
})
syncLoopHook.tap('node', function(name) {
  console.log('node', name)
})

syncLoopHook.call('woow_wu7')
  </script>
</body>
</html>
複製程式碼

AsyncParallelHook - 通過 .tapAsync(_, (name, cb)=>{非同步程式碼}).callAsync(_, ()=>{})呼叫

  • 非同步並行的鉤子
  • parallel:是並行的意思
  • series:是序列的意思
let { AsyncParallelHook } = require('tapable');
// AsyncParallelHook是非同步並行的鉤子
// parallel:並行
// series:序列

class Lesson {
  constructor() {
    this.hooks = {
      arch: new AsyncParallelHook(['name'])
    };
  }

  // 註冊監聽函式
  tap() {
    this.hooks.arch.tapAsync('react', function (name, cb) { // ----------- tapAsync 非同步註冊
      setTimeout(function () {
        console.log('react', name);
        cb(); // --------------------------------------------------------- cb()表示執行完畢
      }, 1000)
    });
    this.hooks.arch.tapAsync('node', function (name, cb) {
      setTimeout(function () {
        console.log('node', name);
        cb(); // -----------------只有有一個cb()沒有執行,在callAsync()中的回撥函式就不會觸發
      }, 1000)
    });
  }
  start() {
    this.hooks.arch.callAsync('woow_wu7', function () { // --------------- callAsync非同步執行
      console.log('end')
    });
  }
}

let lesson = new Lesson();
lesson.tap();
lesson.start();


複製程式碼

AsyncParallelHook模擬實現

class AsyncParallelHook {
  constructor() {
    this.observers = [];
  }
  tapAsync(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }
  callAsync(...params) {
    const callbackFn = params.pop() 
    // 取出callAsync()的最後一個引數,這裡只有兩個引數,即取出 cb 回撥
    // 注意:pop(), shift() 返回被刪除的元素,改變原陣列
    // 注意:push(), unshift() 返回操作後的陣列長度,改變原陣列

    const that = this
    // 固定thiss

    let count = 0
    // 計數,用於統計 cb 回撥執行的次數

    function done() {
      count++;
      if (count === that.observers.length) { 
        // 保證每一個 .tap() 中都執行了 cb() 回撥
        // 相等說明每個 .tap()中都執行了 cb()
        // 說明:此條件就是執行 callAsync()中的引數回撥函式的條件
        callbackFn()
      }
    }
    this.observers.forEach((item, index) => {
      item.fn(...params, done)
      // done函式作為引數傳遞給 .tapAsync()在內部被呼叫
    })
  }
}

const asyncParallelHook = new AsyncParallelHook(['params'])

asyncParallelHook.tapAsync('react', function(name, cb) {
  setTimeout(() => {
    console.log('react', name)
    cb()
  }, 1000);
})
asyncParallelHook.tapAsync('vue', function(name, cb) {
  setTimeout(() => {
    console.log('vue', name)
    cb()
  }, 1000);
})
asyncParallelHook.tapAsync('node', function(name, cb) {
  setTimeout(() => {
    console.log('node', name)
    cb()
  }, 1000);
})

asyncParallelHook.callAsync('woow_wu7', function() {
  console.log('end')
})
複製程式碼

AsyncParallelHook - 通過 .tapPromise().promise().then()呼叫

const { AsyncParallelHook } = require('tapable')

class Lesson {
  constructor() {
    this.hook = {
      arch: new AsyncParallelHook(['name'])
    }
  }
  tap() {
    this.hook.arch.tapPromise('react', name => {
      return new Promise((resolove) => {
        setTimeout(() => {
          console.log('react', name)
          return resolove()
        }, 1000);
      })
    })
    this.hook.arch.tapPromise('vue', name => {
      return new Promise((resolove) => {
        setTimeout(() => {
          console.log('react', name)
          return resolove()
        }, 1000);
      })
    })
  }
  publish() {
    this.hook.arch.promise('woow_wu7').then(() => {
      console.log('end')
    })
  }
}

const lesson = new Lesson(['name'])
lesson.tap() // 註冊
lesson.publish() // 釋出
複製程式碼

AsyncParallelHook - 通過 .tapPromise().promise().then()呼叫 - 模擬實現

class AsyncParallelHook {
  constructor() {
    this.observers = [];
  }
  tapPromise(eventName, fn) {
    this.observers.push({
      event: eventName,
      fn
    })
  }
  promise(...params) {
    const promiseArr = this.observers.map(item => item.fn(...params))
    return Promise.all(promiseArr) 
    // 返回一個Promise.all()函式
    // 所有 resolve() 才 resolve()
    // 一個 rejectI() 就 reject()
  }
}

const asyncParallelHook = new AsyncParallelHook(['params'])

asyncParallelHook.tapPromise('react', function (name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('react', name)
      return resolve()
    }, 1000);
  })
})
asyncParallelHook.tapPromise('vue', function (name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('react', name)
      return resolve()
    }, 1000);
  })
})
asyncParallelHook.promise('node').then(function (name) {
  console.log('end')
})
複製程式碼

AsyncSeriesHook - 非同步序列鉤子

  • 在前一個.tap()執行完才會繼續執行下一個.tap(),所有的.tap()中的cb()都執行完,才執行.callAsync()中的回撥函式
const { AsyncSeriesHook } = require('tapable')

class Lesson {
  constructor() {
    this.hook = {
      arch: new AsyncSeriesHook(['name'])
    }
  }
  tap() {
    this.hook.arch.tapAsync('react', (name, cb) => {
      setTimeout(() => {
        console.log('react', name)
        cb()
      }, 1000);
    })
    this.hook.arch.tapAsync('vue', (name, cb) => {
      setTimeout(() => {
        console.log('vue', name)
        cb()
      }, 1000);
    })
  }
  publish() {
    this.hook.arch.callAsync('woow_wu7', () => {
      console.log('end')
    })
  }
}

const lesson = new Lesson(['name'])

lesson.tap()
lesson.publish()
複製程式碼

AsyncSeriesHook模擬實現

class AsyncSeriesHook {
  constructor() {
    this.observers = []
  }
  tapAsync(name, fn) {
    this.observers.push({
      event_name: name,
      fn
    })
  }
  callAsync(...params) {
    const lastCallback = params.pop()
    let index = 0;
    const that = this;
    function next() {
      if (index === that.observers.length) {
        return lastCallback()
        // 如果不等,就永遠不會執行 lastCallback()
      }
      that.observers[index++].fn(...params, next)
      // index++ 
      // 先整個賦值即 that.observes[0]
      // 再 index = index + 1 => index = 1

      // 遞迴next()方法,直到 index === that.observers.length 後則返回 lastCallbackk() 停止遞迴
    }
    next()
  }
}

const asyncSeriesHook = new AsyncSeriesHook(['name'])

asyncSeriesHook.tapAsync('react', function(name, cb) {
  setTimeout(() => {
    console.log('react', name)
    cb()
  }, 1000);
})
asyncSeriesHook.tapAsync('vue', function(name, cb) {
  setTimeout(() => {
    console.log('vue', name)
    cb()
  }, 1000);
})
asyncSeriesHook.callAsync('woow_wu7', function() {
  console.log('end')
})
複製程式碼

AsyncSeriesWaterfullHook

const { AsyncSeriesWaterfallHook } = require('tapable')

class Lesson {
  constructor() {
    this.hook = {
      arch: new AsyncSeriesWaterfallHook(['name'])
    }
  }
  tap() {
    this.hook.arch.tapAsync('react', (name, cb) => {
      setTimeout(() => {
        console.log('react', name)
        cb(null, 'next-react')
        // 當第一個引數布林值是false時, 將傳遞 'next-react' 作為下一個 .tapAsynce() 中引數回撥函式的引數
        // 當第一個引數布林值是true時,將中斷下一個 .tapAsynce() 的執行
           // 注意: 雖然會中斷下一個.tapAsynce()
           // 但是因為呼叫了cb()且為.tapAsynce()的總個數時,所以callAsync的第二個引數回撥會執行,即會列印'end'
      }, 1000);
    })
    this.hook.arch.tapAsync('vue', (name, cb) => {
      setTimeout(() => {
        console.log('vue', name)
        cb(null, 'next-vue')
        // 如果是cb('null', 'next-vue') 第一個引數布林值是true,則不會執行下一個.tapAsynce()
      }, 1000);
    })
  }
  publish() {
    this.hook.arch.callAsync('woow_wu7', () => {
      console.log('end')
    })
  }
}

const lesson = new Lesson(['name'])

lesson.tap()
lesson.publish()
複製程式碼

AsyncSeriesWaterfullHook模擬實現

class AsyncSeriesWaterfallHook {
  constructor() {
    this.observers = []
  }
  tapAsync(name, fn) {
    this.observers.push({
      event_name: name,
      fn
    })
  }
  callAsync(...params) {
    const lastCallback = params.pop()
    let index = 0;
    const that = this;
    function next(err, data) {
      let task = that.observers[index]
      if (!task || err || index === that.observers.length) {
        // 如果該task不存在
        // 或者 err存在
        // 或者 index達到最大長度,其實可以不判斷,因為index超出that.observers.length時,task將不存在了,滿足第一個條件
        // 滿足以上條件,都直接返回lastCallback
        return lastCallback()
      }
      if (index === 0) {
        task.fn(...params, next)
      } else {
        task.fn(data, next)
      }
      index++
    }
    next()
  }
}

const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(['name'])

asyncSeriesWaterfallHook.tapAsync('react', function(name, cb) {
  setTimeout(() => {
    console.log('react', name)
    cb(null, 'next-react')
  }, 1000);
})
asyncSeriesWaterfallHook.tapAsync('vue', function(name, cb) {
  setTimeout(() => {
    console.log('vue', name)
    cb(null, 'next-vue')
  }, 1000);
})
asyncSeriesWaterfallHook.callAsync('woow_wu7', function() {
  console.log('end')
})
複製程式碼

資料

tapable github.com/webpack/tap…
tapable juejin.im/post/5c5d96…
tapable juejin.im/post/5c25f9…

相關文章