美文网首页程序员
聊聊 Webpack 插件系统的关键实现 Tapable

聊聊 Webpack 插件系统的关键实现 Tapable

作者: 蛋先生DX | 来源:发表于2022-03-27 18:50 被阅读0次

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

    蛋先生:今天就来聊下 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 会怎么定制你的一天的呢?

    相关文章

      网友评论

        本文标题:聊聊 Webpack 插件系统的关键实现 Tapable

        本文链接:https://www.haomeiwen.com/subject/hfmyjrtx.html