乐趣区

关于webpack:聊聊-Webpack-插件系统的关键实现-Tapable

丹尼尔:蛋兄,咱们明天聊些什么呢?

蛋学生:明天就来聊下 webpack 中插件零碎实现的要害 – Tapable

丹尼尔:Tapable?

蛋学生:没错,咱们明天换种形式来聊吧,就聊你的一天

丹尼尔:我的一天?

蛋学生:首先,每个人的一天都有这么几个阶段:早上,中午,下午,早晨。用 Tapable 的形式形容是以下这个样子:

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

class Man {constructor() {
    this.hooks = {morningHook: new SyncHook(),
      noonHook: new SyncHook(),
      afternoonHook: new SyncHook(),
      nightHook: new SyncHook(),};
  }

  startNewDay() {this.hooks.morningHook.call();
    this.hooks.noonHook.call();
    this.hooks.afternoonHook.call();
    this.hooks.nightHook.call();}
}

丹尼尔:SyncHook 是啥?

蛋学生:先不焦急,等会你就会明确的。首先你是一个人。

丹尼尔:不然呢?难道还会是禽兽吗?(`へ´)

蛋学生:(lll¬ω¬) 误会误会,看看代码吧

const daniel = new Man();
daniel.startNewDay();

丹尼尔:哦,懂了。那我的一天都筹备干些啥呢?

蛋学生:首先是早上,早上你就做了三件事:起床,刷牙,吃早餐

丹尼尔:就这?还认为有什么惊喜呢

蛋学生:我又不是讲段子的 ╮(╯▽╰)╭,我只会讲代码,来

const daniel = new Man();

// Morning
getUpAction(daniel);
brushTeethAction(daniel);
eatBreakfastAction(daniel);

daniel.startNewDay();

function getUpAction(manInst) {manInst.hooks.morningHook.tap("getUp", () => console.log("Get up"));
}
function brushTeethAction(manInst) {manInst.hooks.morningHook.tap("brushTeeth", () => console.log("Brush Teeth"));
}
function eatBreakfastAction(manInst) {manInst.hooks.morningHook.tap("eatBreakfast", () =>
    console.log("Eat breakfast")
  );
}

输入后果:

Get up
Brush Teeth
Eat breakfast

丹尼尔:我如同看出点什么了,Man 只是定义了生命周期钩子,但每个阶段做什么,都是通过减少行为来灵便扩大(PS:这里的行为你能够了解为插件,只是为了配合这个剧本而已)

蛋学生:是的没错。这里的起床,刷牙等行为,彼此间独立,且都是同步程序执行的,所以咱们只用一般的同步 Hook 就行了,即 SyncHook。

class Man {constructor() {
    this.hooks = {morningHook: new SyncHook(),
      ...
    };
  }

  startNewDay() {this.hooks.morningHook.call();
    ...
  }
}

丹尼尔:这里的 this.hooks.morningHook.call() 就是告诉早上这个周期阶段开始了,而后后面各个 Action 通过 manInst.hooks.morningHook.tap 曾经提前注册好要在这个周期做些什么,所以此时各个 Action 也就繁忙起来了是吧

蛋学生:Yes。后面你不是问了 SyncHook 吗?因为行为有同步和异步,所以 Sync 结尾的 Hook 就是同步执行的,而 Async 结尾的就是异步执行的

丹尼尔:原来如此,那一个周期阶段上挂这么多行为,是不是要期待所有行为完结才进到下个周期阶段

蛋学生:是的没错。一个周期阶段上能够挂多个行为,个别先挂先执行(SyncXXX 和 AsyncSeriesXXX),还有一种是并发执行,当然也只有异步行为能力并发。接下来咱们持续通过你的一天来理解 Tapable 的各种 Hook 及其它信息吧


丹尼尔:好的,早上聊完了,中午干啥呢?

蛋学生:不不,还是早上。咱们略微调整下早上做的事,换成起床,做早餐,吃早餐

丹尼尔:额,还是一样平平无奇啊

蛋学生:你在做早餐时搞砸了

丹尼尔:啊,这么晦气?那我岂不是要饿肚子了 X﹏X

蛋学生:做早餐实现不了,意味着吃早餐须要中断,这个时候就须要 SyncBailHook

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

class Man {constructor() {
    this.hooks = {morningHook: new SyncBailHook(),
      ...
    };
  }
  
  ...
}

const daniel = new Man();

// Morning
getUpAction(daniel);
makeBreakfastAction(daniel);
eatBreakfastAction(daniel);

daniel.startNewDay();

function getUpAction(manInst) {manInst.hooks.morningHook.tap("getUp", () => console.log("Get up"));
}
function makeBreakfastAction(manInst) {manInst.hooks.morningHook.tap("makeBreakfast", () => {console.log("Make breakfast, but failed");
    return false;
  });
}
function eatBreakfastAction(manInst) {manInst.hooks.morningHook.tap("eatBreakfast", () =>
    console.log("Eat breakfast")
  );
}

输入后果

Get up
Make breakfast, but failed

丹尼尔:好吧,吃不了就算了,只能挨饿到中午了

蛋学生:早餐不吃对身材不好,我改下剧情。你胜利地做完早餐,做了牛奶,鸡蛋和面包。但咱们须要把做早餐的成绩给到吃早餐,这样吃早餐才有货色能够吃,这时就能够用 SyncWaterfallHook

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

class Man {constructor() {
    this.hooks = {morningHook: new SyncWaterfallHook(["breakfast"]),
      ...
    };
  }

  ...
}


function makeBreakfastAction(manInst) {manInst.hooks.morningHook.tap("makeBreakfast", () => {console.log("Make breakfast");
    return "milk, bread, eggs";
  });
}
function eatBreakfastAction(manInst) {manInst.hooks.morningHook.tap("eatBreakfast", (breakfast) =>
    console.log("Eat breakfast:", breakfast)
  );
}

输入后果:

Get up
Make breakfast
Eat breakfast:  milk, bread, eggs

丹尼尔:谢了蛋兄,对我真不错。早餐也吃完了,要到中午了吗?

蛋学生:是的,中午到了,你又开始做饭了

丹尼尔:啊,我就是个吃货啊,煮啥呢?

蛋学生:你一边煮饭一边煲汤。

丹尼尔:一边 … 一边 …,那就是同时做两件事啊

蛋学生:是的,什么行为能够同时做,当然是异步行为啦,这时就能够用 AsyncParallelHook 了

const {AsyncParallelHook} = require("tapable");

class Man {constructor() {
    this.hooks = {
      ...
      noonHook: new AsyncParallelHook(),
      ...
    };
  }

  async startNewDay() {
    ...
    await this.hooks.noonHook.promise();
    ...
  }
}

const daniel = new Man();
// Morning
...
// Noon
soupAction(daniel);
cookRiceAction(daniel);

daniel.startNewDay();

...
function cookRiceAction(manInst) {manInst.hooks.noonHook.tapPromise("cookRice", () => {console.log("cookRice starting...");
    return new Promise((resolve) => {setTimeout(() => {console.log("cookRice finishing...");
        resolve();}, 800);
    });
  });
}
function soupAction(manInst) {manInst.hooks.noonHook.tapPromise("soup", () => {console.log("soup starting...");
    return new Promise((resolve) => {setTimeout(() => {console.log("soup finishing...");
        resolve();}, 1000);
    });
  });
}

输入如下:

soup starting...
cookRice starting...
cookRice finishing...
soup finishing...

丹尼尔:好吧,中午看上去比早上顺利多了

蛋学生:接下来到下午了,下午你开始用番茄工作法学习四个小时

丹尼尔:恩,你又晓得我这么好学,真是太理解我了

蛋学生:因为一个番茄钟一直地循环,直到 4 小时过来才完结,所以能够用到 SyncLoopHook

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

class Man {constructor() {
    this.hooks = {
      ...
      afternoonHook: new SyncLoopHook(),
      ...
    };
  }

  async startNewDay() {
    ...
    this.hooks.afternoonHook.call();
    ...
  }
}

const daniel = new Man();
// Morning
...
// Noon
...
// Afternoon
studyAction(daniel);
restAction(daniel)

daniel.startNewDay();

...
let leftTime = 4 * 60;
function studyAction(manInst) {manInst.hooks.afternoonHook.tap("study", () => {console.log("study 25 minutes");
    leftTime -= 25;
  });
}
function restAction(manInst) {manInst.hooks.afternoonHook.tap("study", () => {console.log("rest 5 minutes");
    leftTime -= 5;
    if (leftTime <= 0) {console.log("tomatoStudy: finish");
      return;
    }
    return true;
  });
}

输入后果:

study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
study 25 minutes
rest 5 minutes
tomatoStudy: finish

丹尼尔:学到头昏脑涨的,早晨该放松放松了

蛋学生:恩,到了早晨了,你可能玩游戏,也可能看电影,这取决于有没敌人找你上分

丹尼尔:哦,就是看状况而定,不是所有行为都执行是吧

蛋学生:是的,这就须要用到 HookMap

const {SyncHook, HookMap} = require("tapable");

class Man {constructor() {
    this.hooks = {
      ...
      nightHook: new HookMap(() => new SyncHook()),
    };
  }

  async startNewDay() {
    ...
    this.hooks.nightHook.for("no friend invitation").call();}
}

const daniel = new Man();

// Morning
...
// Noon
...
// Afternoon
...
// Night
playGameAction(daniel);
watchMovieAction(daniel);

daniel.startNewDay();

...
function playGameAction(manInst) {manInst.hooks.nightHook.for("friend invitation").tap("playGame", () => {console.log("play game");
  });
}
function watchMovieAction(manInst) {manInst.hooks.nightHook.for("no friend invitation").tap("watchMovie", () => {console.log("watch movie");
  });
}

输入后果:

watch movie

丹尼尔:一天就这么过完了,咱们该说再见了

蛋学生:还没完,你有写日记的好习惯,而且是每做一件事就记

丹尼尔:每一件都记?这是记流水账吧

蛋学生:差不多吧,你感觉怎么记最好呢

丹尼尔:做每件事之前进行拦挡咯

蛋学生:真聪慧,这里能够用 Interception

...

const daniel = new Man();

writeDiary(daniel);

...

daniel.startNewDay();

...

function writeDiary(manInst) {const interceptFn = (hookName) => {
    return {tap: (tapInfo) => {console.log(`write diary:`, tapInfo)
      }
    };
  };
  Object.keys(manInst.hooks).forEach((hookName) => {if (manInst.hooks[hookName] instanceof HookMap) {manInst.hooks[hookName].intercept({factory: (key, hook) => {hook.intercept(interceptFn(hookName));
          return hook
        },
      });
    } else {manInst.hooks[hookName].intercept(interceptFn(hookName));
    }
  });
}

输入后果:

write diary: {type: 'sync', fn: [Function], name: 'getUp' }
write diary: {type: 'sync', fn: [Function], name: 'makeBreakfast' }
write diary: {type: 'sync', fn: [Function], name: 'eatBreakfast' }
write diary: {type: 'promise', fn: [Function], name: 'soup' }
write diary: {type: 'promise', fn: [Function], name: 'cookRice' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'study' }
write diary: {type: 'sync', fn: [Function], name: 'watchMovie' }

丹尼尔:日记也写完了,没啥其它事了吧

蛋学生:最初的最初,聊一下 context 吧。因为每个行为都可能由不同的开发者提供,行为之间独立,但有时又想共享一些数据,比方这里须要共享下你的个人信息,再看最初的一段代码,而后就能够散了,再保持一小会

...

const daniel = new Man();

writeDiary(daniel);

...

daniel.startNewDay();

function getUpAction(manInst) {
  manInst.hooks.morningHook.tap(
    {
      name: "getUp",
      context: true,
    },
    (context) => {console.log("Get up", context);
    }
  );
}
...

function writeDiary(manInst) {const interceptFn = (hookName) => {
    return {
      context: true,
      tap: (context, tapInfo) => {context = context || {};
        context.userInfo = {name: "daniel",};
      }
    };
  };
  Object.keys(manInst.hooks).forEach((hookName) => {if (manInst.hooks[hookName] instanceof HookMap) {manInst.hooks[hookName].intercept({factory: (key, hook) => {console.log(`[${hookName}][${key}]`);
          hook.intercept(interceptFn(hookName));
          return hook;
        },
      });
    } else {manInst.hooks[hookName].intercept(interceptFn(hookName));
    }
  });
}

输入后果:

Get up {userInfo: { name: 'daniel'} }

丹尼尔:好困,眼睛快要睁不开了

蛋学生:好了,你的一天就聊完了,再见

丹尼尔:告辞

保持读到这里的小伙伴们,你们通过 Tapable 会怎么定制你的一天的呢?

退出移动版