webpack插件機制之Tapable

文章首發於:github.com/USTB-musion…node

Tapable

對於Webpack有一句話Everything is a plugin,Webpack本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable。Tapable有點相似nodejs的events庫,核心原理也是依賴與發佈訂閱模式。webpack中最核心的負責編譯的Compiler和負責建立bundles的Compilation都是Tapable的實例。下面介紹一下tapable的用法和原理。如下實例的代碼原文地址爲github.com/USTB-musion…react

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");
複製代碼

Tapable Hook概覽

Tapable提供了不少類型的hook,分爲同步和異步兩大類(異步中又區分異步並行和異步串行),而根據事件執行的終止條件的不一樣,由衍生出 Bail/Waterfall/Loop 類型。

下圖展現了每種類型的做用: webpack

  • BasicHook: 執行每個,不關心函數的返回值,有 SyncHook、AsyncParallelHook、AsyncSeriesHook。
  • BailHook: 順序執行 Hook,遇到第一個結果 result !== undefined 則返回,再也不繼續執行。有:SyncBailHook、AsyncSeriseBailHook, AsyncParallelBailHook。
  • WaterfallHook: 相似於 reduce,若是前一個 Hook 函數的結果 result !== undefined,則 result 會做爲後一個 Hook 函數的第一個參數。既然是順序執行,那麼就只有 Sync 和 AsyncSeries 類中提供這個Hook:SyncWaterfallHook,AsyncSeriesWaterfallHook
  • LoopHook: 不停的循環執行 Hook,直到全部函數結果 result === undefined。一樣的,因爲對串行性有依賴,因此只有 SyncLoopHook 和 AsyncSeriseLoopHook (PS:暫時沒看到具體使用 Case)

SyncHook的用法及實現

Sync爲同步串行的執行關係,用法以下:git

let { SyncHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
    });
    this.hooks.arch.tap("react", function(name) {
      console.log("react", name);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();

/** * 打印出來的值爲: * node musion * react musion */
複製代碼

SyncHook是一個很典型的經過發佈訂閱方式實現的,實現方式以下:github

// 鉤子是同步的
class SyncHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach(task => task(...args));
  }
}

let hook = new SyncHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
});
hook.tap("node", function(name) {
  console.log("node", name);
});
hook.call("musion");

/** * 打印出來的值爲: * node musion * react musion */
複製代碼

SyncBailHook的用法及實現

SyncBailHook爲同步串行的執行關係,只要監聽函數中有一個函數的返回值不爲 null,則跳過剩下全部的邏輯,用法以下:web

let { SyncBailHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncBailHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
      //return "stop";
      return undefined;
    });
    this.hooks.arch.tap("react", function(name) {
      console.log("react", name);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();


/** * 打印出來的值爲: * node musion * react musion */
複製代碼

SyncBailHook的實現:併發

// 鉤子是同步的,bail -> 保險
class SyncBailHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    // 當前函數的返回值
    let ret;
    // 當前要先執行第一個
    let index = 0;
    do {
      ret = this.tasks[index++](...args);
    } while (ret === undefined && index < this.tasks.length);
  }
}

let hook = new SyncBailHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
  return "stop";
});
hook.tap("node", function(name) {
  console.log("node", name);
});
hook.call("musion");


/** * 打印出來的值爲: * node musion * react musion */
複製代碼

SyncWaterfallHook的用法及實現

SyncWaterfallHook爲同步串行的執行關係,上一個監聽函數的返回值能夠傳給下一個監聽函數,用法以下:異步

let { SyncWaterfallHook } = require("tapable");

// waterfall 瀑布 上面會影響下面的

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncWaterfallHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
      return "node學得還不錯";
    });
    this.hooks.arch.tap("react", function(data) {
      console.log("react", data);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();

/** * 打印出來的值爲: * node musion * react node學得還不錯 */
複製代碼

SyncWaterfallHook的實現:函數

// 鉤子是同步的
class SyncWaterfallHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    let [first, ...others] = this.tasks;
    let ret = first(...args);
    others.reduce((a, b) => {
      return b(a);
    }, ret);
  }
}

let hook = new SyncWaterfallHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
  return "react ok";
});
hook.tap("node", function(data) {
  console.log("node", data);
  return "node ok";
});
hook.tap("webpack", function(data) {
  console.log("webpack", data);
});
hook.call("musion");

/** * 打印出來的值爲: * react musion * node react ok * webpack node ok */
複製代碼

SyncLoopHook的用法及實現

SyncLoopHook爲同步循環的執行關係,當監聽函數被觸發的時候,若是該監聽函數返回true時則這個監聽函數會反覆執行,若是返回 undefined 則表示退出循環,用法以下:oop

let { SyncLoopHook } = require("tapable");

// 同步遇到某個不返回undefined的監聽函數會屢次執行

class Lesson {
  constructor() {
    this.index = 0;
    this.hooks = {
      arch: new SyncLoopHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tap("node", name => {
      console.log("node", name);
      return ++this.index === 3 ? undefined : "繼續學";
    });
    this.hooks.arch.tap("react", data => {
      console.log("react", data);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();

/** * 打印出來的值爲: * node musion * node musion * node musion * react musion */

複製代碼

SyncLoopHook的實現:

// 鉤子是同步的
class SyncLoopHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach(task => {
      let ret;
      do {
        ret = task(...args);
      } while (ret != undefined);
    });
  }
}

let hook = new SyncLoopHook(["name"]);

let total = 0;
hook.tap("react", function(name) {
  console.log("react", name);
  return ++total === 3 ? undefined : "繼續學";
});
hook.tap("node", function(data) {
  console.log("node", data);
});
hook.tap("webpack", function(data) {
  console.log("webpack", data);
});
hook.call("musion");

/** * 打印出來的值爲: * react musion * react musion * react musion * node musion * webpack musion */
複製代碼

AsyncParallelHook的用法及實現

AsyncParallelHook爲異步併發的執行關係,用法以下:

let { AsyncParallelHook } = require("tapable");
// 異步的鉤子分爲串行和並行
// 串行:第一個異步執行完,纔會執行第二個
// 並行:須要等待全部併發的異步事件執行後再執行回調方法

// 註冊方法: tap註冊 tapAsync註冊

class Lesson {
  constructor() {
    this.hooks = {
      arch: new AsyncParallelHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tapAsync("node", (name, cb) => {
      setTimeout(() => {
        console.log("node", name);
        cb();
      }, 1000);
    });
    this.hooks.arch.tapAsync("react", (name, cb) => {
      setTimeout(() => {
        console.log("react", name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync("musion", function() {
      console.log("end");
    });
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();

/** * 打印出來的值爲: * node musion * react musion * end */


複製代碼

AsyncParallelHook的實現:

class SyncParralleHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    // 拿出最終的函數
    let finalCallBack = args.pop();
    let index = 0;
    // 相似Promise.all
    let done = () => {
      index++;
      if (index === this.tasks.length) {
        finalCallBack();
      }
    };
    this.tasks.forEach(task => {
      task(...args, done);
    });
  }
}

let hook = new SyncParralleHook(["name"]);

hook.tapAsync("react", function(name, cb) {
  setTimeout(() => {
    console.log("react", name);
    cb();
  }, 1000);
});
hook.tapAsync("node", function(name, cb) {
  setTimeout(() => {
    console.log("node", name);
    cb();
  }, 1000);
});
hook.callAsync("musion", function() {
  console.log("end");
});


/** * 打印出來的值爲: * react musion * react musion * react musion * node musion * webpack musion */
複製代碼

AsyncSeriesHook的用法及實現

AsyncSeriesHook爲異步串行的執行關係,用法以下:

// AsyncSeriesHook 異步串行
let { AsyncSeriesHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new AsyncSeriesHook(["name"])
    };
  }
  // 註冊監聽函數
  tap() {
    this.hooks.arch.tapAsync("node", (name, cb) => {
      setTimeout(() => {
        console.log("node", name);
        cb();
      }, 4000);
    });
    this.hooks.arch.tapAsync("react", (name, cb) => {
      setTimeout(() => {
        console.log("react", name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync("musion", function() {
      console.log("end");
    });
  }
}

let l = new Lesson();

// 註冊這兩個事件
l.tap();
// 啓動鉤子
l.start();

/** * 打印出來的值爲: * node musion * react musion * end */


複製代碼

AsyncSeriesHook的實現:

class SyncSeriesHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = () => {
      if (this.tasks.length === index) return finalCallback();
      let task = this.tasks[index++];
      task(...args, next);
    };
    next();
  }
}
複製代碼

AsyncSeriesWaterfallHook的用法及實現

AsyncSeriesWaterfallHook爲異步串行的執行關係,上一個監聽函數的中的callback(err, data)的第二個參數,能夠做爲下一個監聽函數的參數,用法以下:

class SyncSeriesWaterfallHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = (err, data) => {
      let task = this.tasks[index];
      if (!task) return finalCallback();
      // 執行的是第一個
      if (index === 0) {
        task(...args, next);
      } else {
        task(data, next);
      }
      index++;
    };
    next();
  }
}

let hook = new SyncSeriesWaterfallHook(["name"]);

hook.tapAsync("react", function(name, cb) {
  setTimeout(() => {
    console.log("react", name);
    cb(null, "musion");
  }, 3000);
});
hook.tapAsync("node", function(name, cb) {
  setTimeout(() => {
    console.log("node", name);
    cb(null);
  }, 1000);
});
hook.callAsync("musion", function() {
  console.log("end");
});

/** * 打印出來的值爲: * node musion * end */


複製代碼

AsyncSeriesWaterfallHook的實現:

class SyncSeriesWaterfallHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = (err, data) => {
      let task = this.tasks[index];
      if (!task) return finalCallback();
      // 執行的是第一個
      if (index === 0) {
        task(...args, next);
      } else {
        task(data, next);
      }
      index++;
    };
    next();
  }
}
複製代碼

參考文章

webpack系列之二Tapable

tapable

相關文章
相關標籤/搜索