[源碼-webpack02-前置知識] Tapable

導航

[深刻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] Hookshtml

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI前端

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程vue

前置知識

一些單詞

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…node

相關文章
相關標籤/搜索