头图

image.png

Daniel: Brother egg, what are we talking about today?

Mr. Egg: Today, let's talk about the key to the implementation of the plugin system in webpack - Tapable

Daniel: Tapable?

Mr. Egg: Yes, let's talk about a different way today, just talk about your day

Daniel: My day?

Mr. Egg: First of all, everyone's day has several stages: morning, noon, afternoon, evening. The description in Tapable way is as follows:

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();
  }
}

Daniel: What is SyncHook?

Mr. Egg: Don't worry, you will understand later. First you are alone.

Daniel: Or else? Will it still be a beast? (`へ´)

Mr. Egg: (lll¬ω¬) Misunderstanding, look at the code

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

Daniel: Oh, I see. So what am I going to do all day?

Mr. Egg: The first is the morning. In the morning, you do three things: get up, brush your teeth, and eat breakfast

Daniel: That's it? I thought there was a surprise

Mr. Egg: I'm not talking about jokes ╮(╯▽╰)╭, I only talk about code, come

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")
  );
}

Output result:

Get up
Brush Teeth
Eat breakfast

Daniel: I seem to see something, Man just defines life cycle hooks, but what each stage does is flexibly extended by adding behaviors (PS: The behaviors here can be understood as plug-ins, just to match this script only)

Mr. Egg: Yes that's right. The behaviors such as getting up and brushing teeth here are independent of each other, and they are all executed in synchronous order, so we only use the ordinary synchronous Hook, that is, SyncHook.

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

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

Daniel: this.hooks.morningHook.call() here is to inform the morning that this cycle phase has started, and then the previous actions have been registered in advance through manInst.hooks.morningHook.tap to do something in this cycle, so each action is busy at this time, right?

Mr. Egg: Yes. Didn't you ask about SyncHook earlier? Because the behavior is synchronous and asynchronous, the Hook at the beginning of Sync is executed synchronously, and the one at the beginning of Async is executed asynchronously

Daniel: So that's the case, so many behaviors are hung in that cycle stage, do you have to wait for all behaviors to end before entering the next cycle stage?

Mr. Egg: Yes that's right. Multiple behaviors can be hung on a cycle stage. Generally, they are executed first (SyncXXX and AsyncSeriesXXX), and the other is concurrent execution. Of course, only asynchronous behaviors can be concurrent. Next, let's continue to learn about Tapable's various Hooks and other information through your day.


Daniel: Okay, we're done talking in the morning, what are we going to do at noon?

Mr. Egg: No, it's still morning. Let's tweak what we do in the morning a little bit, get up, make breakfast, eat breakfast

Daniel: Uh, it's still just as normal.

Mr. Egg: You screwed up making breakfast

Daniel: Ah, so unlucky? Then am I going to starve X﹏X

Mr. Egg: If you can't finish making breakfast, it means you need to interrupt breakfast. At this time, you need 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")
  );
}

output result

Get up
Make breakfast, but failed

Daniel: Well, it's fine if you can't eat, you'll have to starve until noon

Mr. Egg: Not eating breakfast is bad for your health. I'll change the plot. You made a successful breakfast with milk, eggs and bread. But we need to give the results of making breakfast to eating breakfast, so that there is something to eat for breakfast, then we can use 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)
  );
}

Output result:

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

Daniel: Thanks Eggman, it's been really nice to me. Breakfast is over, is it noon?

Mr. Egg: Yes, it's noon and you're cooking again

Daniel: Ah, I'm a foodie. What do I cook?

Mr. Egg: You cook rice and soup at the same time.

Daniel: On the one hand... on the other hand..., that is to do two things at the same time.

Mr. Egg: Yes, what behavior can be done at the same time, of course, asynchronous behavior, then you can use 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);
    });
  });
}

The output is as follows:

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

Daniel: Well, noon looks smoother than morning

Mr. Egg: It's the afternoon, and in the afternoon you start to study the Pomodoro Technique for four hours

Daniel: Well, you know I'm so eager to learn, you know me so well

Mr. Egg: Because a Pomodoro keeps looping until 4 hours have passed, you can use 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;
  });
}

Output result:

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

Daniel: I'm dizzy, it's time to relax at night

Mr. Egg: Well, at night, you may play games or watch movies, it depends on whether your friends are looking for you to score

Daniel: Oh, it depends on the situation, not all actions are performed, right?

Mr. Egg: Yes, this requires the use of 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");
  });
}

Output result:

watch movie

Daniel: That's the end of the day, it's time to say goodbye

Mr. Egg: It's not over yet. You have a good habit of keeping a diary, and you keep a diary every time you do something.

Daniel: Remember everything? Is this a running account?

Mr. Egg: About the same, what do you think is the best way to remember it?

Daniel: Intercept before everything

Mr. Egg: Very clever, Interception can be used here

...

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));
    }
  });
}

Output result:

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' }

Daniel: I've finished writing the diary, so there's nothing else to do

Mr. Egg: Finally, let's talk about context. Because each behavior may be provided by different developers, the behaviors are independent, but sometimes you want to share some data, for example, you need to share your personal information here, and then look at the last piece of code, then you can disperse, and then hold on for a while

...

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));
    }
  });
}

Output result:

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

Daniel: I'm so sleepy, I can barely open my eyes

Mr. Egg: Okay, your day is over, goodbye

Daniel: Farewell

Keep reading, guys, how would you customize your day with Tapable?

蛋先生DX
307 声望2 粉丝

ncform / ncgen / nice-hooks 作者