<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從使用到原理,吃透Tapable

          共 15258字,需瀏覽 31分鐘

           ·

          2022-04-25 16:15

          76a5aac1b207a5ac32d335f3d4f3663e.webp

          胡寧:微醫(yī)前端技術(shù)部平臺(tái)支撐組,最近是一陣信奉快樂的風(fēng)~

          tapable 是一個(gè)類似于 Node.js 中的 EventEmitter 的庫,但更專注于自定義事件的觸發(fā)和處理。webpack 通過 tapable 將實(shí)現(xiàn)與流程解耦,所有具體實(shí)現(xiàn)通過插件的形式存在。

          Tapable 和 webpack 的關(guān)系
          1. webpack 是什么?

          本質(zhì)上,webpack 是一個(gè)用于現(xiàn)代 JavaScript 應(yīng)用程序的 靜態(tài)模塊打包工具。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)在內(nèi)部構(gòu)建一個(gè) 依賴圖(dependency graph),此依賴圖對應(yīng)映射到項(xiàng)目所需的每個(gè)模塊,并生成一個(gè)或多個(gè) bundle。

          1. webpack 的重要模塊
          • 入口(entry)
          • 輸出(output)
          • loader(對模塊的源代碼進(jìn)行轉(zhuǎn)換)
          • plugin(webpack 構(gòu)建流程中的特定時(shí)機(jī)注入擴(kuò)展邏輯來改變構(gòu)建結(jié)果或做你想要的事)

          插件(plugin)是 webpack 的支柱功能。webpack 自身也是構(gòu)建于你在 webpack 配置中用到的相同的插件系統(tǒng)之上。

          1. webpack 的構(gòu)建流程

          webpack 本質(zhì)上是一種事件流的機(jī)制,它的工作流程就是將各個(gè)插件串聯(lián)起來,而實(shí)現(xiàn)這一切的核心就是 Tapable。webpack 中最核心的負(fù)責(zé)編譯的 Compiler 和負(fù)責(zé)創(chuàng)建 bundle 的 Compilation 都是 Tapable 的實(shí)例(webpack5 前)。webpack5 之后是通過定義屬性名為 hooks 來調(diào)度觸發(fā)時(shí)機(jī)。Tapable 充當(dāng)?shù)木褪且粋€(gè)復(fù)雜的發(fā)布訂閱者模式

          以 Compiler 為例:

          //?webpack5?前,通過繼承
          ...
          const?{
          ?Tapable,
          ?SyncHook,
          ?SyncBailHook,
          ?AsyncParallelHook,
          ?AsyncSeriesHook
          }?=?require("tapable");
          ...
          class?Compiler?extends?Tapable?{
          ?constructor(context)?{
          ??super();
          ??...
          ?}
          }

          //?webpack5
          ...
          const?{
          ?SyncHook,
          ?SyncBailHook,
          ?AsyncParallelHook,
          ?AsyncSeriesHook
          }?=?require("tapable");
          ...
          class?Compiler?{
          ?constructor(context)?{
          ??this.hooks?=?Object.freeze({
          ???/**?@type?{SyncHook<[]>}?*/
          ???initialize:?new?SyncHook([]),

          ???/**?@type?{SyncBailHook<[Compilation],?boolean>}?*/
          ???shouldEmit:?new?SyncBailHook(["compilation"]),
          ???...
          ??})
          ?}
          ?...
          }
          Tapable 的使用姿勢

          tapable 對外暴露了 9 種 Hooks 類。這些 Hooks 類的作用就是通過實(shí)例化來創(chuàng)建一個(gè)執(zhí)行流程,并提供注冊和執(zhí)行方法,Hook 類的不同會(huì)導(dǎo)致執(zhí)行流程的不同。

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

          每個(gè) hook 都能被注冊多次,如何被觸發(fā)取決于 hook 的類型

          按同步、異步(串行、并行)分類

          • Sync:只能被同步函數(shù)注冊,如 myHook.tap()
          • AsyncSeries:可以被同步的,基于回調(diào)的,基于 promise 的函數(shù)注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序?yàn)榇?/li>
          • AsyncParallel:可以被同步的,基于回調(diào)的,基于 promise 的函數(shù)注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序?yàn)椴⑿?/li>
          2c257108825c4582136c4c0851c4c2b3.webpUntitled.png

          按執(zhí)行模式分類

          • Basic:執(zhí)行每一個(gè)事件函數(shù),不關(guān)心函數(shù)的返回值
          359cd8fe3ea33df05c5e11f297014723.webpTapable bda4604e3f27488082fd7a2820082dbc.png
          • Bail:執(zhí)行每一個(gè)事件函數(shù),遇到第一個(gè)結(jié)果 result !== undefined 則返回,不再繼續(xù)執(zhí)行
          fab3ce659606ad007ea41e4b9c39336e.webp_(1).png
          • Waterfall:如果前一個(gè)事件函數(shù)的結(jié)果 result !== undefined,則 result 會(huì)作為后一個(gè)事件函數(shù)的第一個(gè)參數(shù)
          ad0ee8c097277b4712db6e8650e583ea.webp_(2).png
          • Loop:不停的循環(huán)執(zhí)行事件函數(shù),直到所有函數(shù)結(jié)果 result === undefined
          5e3e44c27099b2930ad152136cf8e2f7.webp_(4).png8268ffcabd18e7c465bf18dc80bcf4bd.webpUntitled 1.png

          使用方式

          Hook 類使用

          簡單來說就是下面步驟

          1. 實(shí)例化構(gòu)造函數(shù) Hook
          2. 注冊(一次或者多次)
          3. 執(zhí)行(傳入?yún)?shù))
          4. 如果有需要還可以增加對整個(gè)流程(包括注冊和執(zhí)行)的監(jiān)聽-攔截器

          以最簡單的 SyncHook 為例:


          //?簡單來說就是實(shí)例化?Hooks?類
          //?接收一個(gè)可選參數(shù),參數(shù)是一個(gè)參數(shù)名的字符串?dāng)?shù)組
          const?hook?=?new?SyncHook(["arg1",?"arg2",?"arg3"]);
          //?注冊
          //?第一個(gè)入?yún)樽悦?/span>
          //?第二個(gè)為注冊回調(diào)方法
          hook.tap("1",?(arg1,?arg2,?arg3)?=>?{
          ??console.log(1,?arg1,?arg2,?arg3);
          ??return?1;
          });
          hook.tap("2",?(arg1,?arg2,?arg3)?=>?{
          ??console.log(2,?arg1,?arg2,?arg3);
          ??return?2;
          });
          hook.tap("3",?(arg1,?arg2,?arg3)?=>?{
          ??console.log(3,?arg1,?arg2,?arg3);
          ??return?3;
          });
          //?執(zhí)行
          //?執(zhí)行順序則是根據(jù)這個(gè)實(shí)例類型來決定的
          hook.call("a",?"b",?"c");

          //------輸出------
          //?先注冊先觸發(fā)
          1?a?b?c
          2?a?b?c
          3?a?b?c

          上面的例子為同步的情況,若注冊異步則:

          let?{?AsyncSeriesHook?}?=?require("tapable");
          let?queue?=?new?AsyncSeriesHook(["name"]);
          console.time("cost");
          queue.tapPromise("1",?function?(name)?{
          ??return?new?Promise(function?(resolve)?{
          ????setTimeout(function?()?{
          ??????console.log(1,?name);
          ??????resolve();
          ????},?1000);
          ??});
          });
          queue.tapPromise("2",?function?(name)?{
          ??return?new?Promise(function?(resolve)?{
          ????setTimeout(function?()?{
          ??????console.log(2,?name);
          ??????resolve();
          ????},?2000);
          ??});
          });
          queue.tapPromise("3",?function?(name)?{
          ??return?new?Promise(function?(resolve)?{
          ????setTimeout(function?()?{
          ??????console.log(3,?name);
          ??????resolve();
          ????},?3000);
          ??});
          });
          queue.promise("weiyi").then((data)?=>?{
          ??console.log(data);
          ??console.timeEnd("cost");
          });

          HookMap 類使用

          A HookMap is a helper class for a Map with Hooks

          官方推薦將所有的鉤子實(shí)例化在一個(gè)類的屬性 hooks 上,如:

          class?Car?{
          ?constructor()?{
          ??this.hooks?=?{
          ???accelerate:?new?SyncHook(["newSpeed"]),
          ???brake:?new?SyncHook(),
          ???calculateRoutes:?new?AsyncParallelHook(["source",?"target",?"routesList"])
          ??};
          ?}
          ?/*?...?*/
          ?setSpeed(newSpeed)?{
          ??//?following?call?returns?undefined?even?when?you?returned?values
          ??this.hooks.accelerate.call(newSpeed);
          ?}
          }

          注冊&執(zhí)行:

          const?myCar?=?new?Car();

          myCar.hooks.accelerate.tap("LoggerPlugin",?newSpeed?=>?console.log(`Accelerating?to?${newSpeed}`));

          myCar.setSpeed(1)

          而 HookMap 正是這種推薦寫法的一個(gè)輔助類。具體使用方法:

          const?keyedHook?=?new?HookMap(key?=>?new?SyncHook(["arg"]))

          keyedHook.for("some-key").tap("MyPlugin",?(arg)?=>?{?/*?...?*/?});
          keyedHook.for("some-key").tapAsync("MyPlugin",?(arg,?callback)?=>?{?/*?...?*/?});
          keyedHook.for("some-key").tapPromise("MyPlugin",?(arg)?=>?{?/*?...?*/?});

          const?hook?=?keyedHook.get("some-key");
          if(hook?!==?undefined)?{
          ?hook.callAsync("arg",?err?=>?{?/*?...?*/?});
          }

          MultiHook 類使用

          A helper Hook-like class to redirect taps to multiple other hooks

          相當(dāng)于提供一個(gè)存放一個(gè) hooks 列表的輔助類:

          const?{?MultiHook?}?=?require("tapable");

          this.hooks.allHooks?=?new?MultiHook([this.hooks.hookA,?this.hooks.hookB]);
          Tapable 的原理

          核心就是通過 Hook 來進(jìn)行注冊的回調(diào)存儲(chǔ)和觸發(fā),通過 HookCodeFactory 來控制注冊的執(zhí)行流程。

          首先來觀察一下 tapable 的 lib 文件結(jié)構(gòu),核心的代碼都是存放在 lib 文件夾中。其中 index.js 為所有可使用類的入口。Hook 和 HookCodeFactory 則是核心類,主要的作用就是注冊和觸發(fā)流程。還有兩個(gè)輔助類 HookMap 和 MultiHook 以及一個(gè)工具類 util-browser。其余均是以 Hook 和 HookCodeFactory 為基礎(chǔ)類衍生的以上分類所提及的 9 種 Hooks。整個(gè)結(jié)構(gòu)是非常簡單清楚的。如圖所示:

          a570dbdb543a53e86e61abbf27c26941.webpUntitled 2.png

          接下來講一下最重要的兩個(gè)類,也是 tapable 的源碼核心。

          Hook

          首先看 Hook 的屬性,可以看到屬性中有熟悉的注冊的方法:tap、tapAsync、tapPromise。執(zhí)行方法:call、promise、callAsync。以及存放所有的注冊項(xiàng) taps。constructor 的入?yún)⒕褪敲總€(gè)鉤子實(shí)例化時(shí)的入?yún)?。從屬性上就能夠知道?Hook 類為繼承它的子類提供了最基礎(chǔ)的注冊和執(zhí)行的方法

          class?Hook?{
          ?constructor(args?=?[],?name?=?undefined)?{
          ??this._args?=?args;
          ??this.name?=?name;
          ??this.taps?=?[];
          ??this.interceptors?=?[];
          ??this._call?=?CALL_DELEGATE;
          ??this.call?=?CALL_DELEGATE;
          ??this._callAsync?=?CALL_ASYNC_DELEGATE;
          ??this.callAsync?=?CALL_ASYNC_DELEGATE;
          ??this._promise?=?PROMISE_DELEGATE;
          ??this.promise?=?PROMISE_DELEGATE;
          ??this._x?=?undefined;

          ??this.compile?=?this.compile;
          ??this.tap?=?this.tap;
          ??this.tapAsync?=?this.tapAsync;
          ??this.tapPromise?=?this.tapPromise;
          ?}
          ?...
          }

          那么 Hook 類是如何收集注冊項(xiàng)的?如代碼所示:

          class?Hook?{
          ?...
          ?tap(options,?fn)?{
          ??this._tap("sync",?options,?fn);
          ?}

          ?tapAsync(options,?fn)?{
          ??this._tap("async",?options,?fn);
          ?}

          ?tapPromise(options,?fn)?{
          ??this._tap("promise",?options,?fn);
          ?}

          ?_tap(type,?options,?fn)?{
          ??if?(typeof?options?===?"string")?{
          ???options?=?{
          ????name:?options.trim()
          ???};
          ??}?else?if?(typeof?options?!==?"object"?||?options?===?null)?{
          ???throw?new?Error("Invalid?tap?options");
          ??}
          ??if?(typeof?options.name?!==?"string"?||?options.name?===?"")?{
          ???throw?new?Error("Missing?name?for?tap");
          ??}
          ??if?(typeof?options.context?!==?"undefined")?{
          ???deprecateContext();
          ??}
          ??//?合并參數(shù)
          ??options?=?Object.assign({?type,?fn?},?options);
          ??//?執(zhí)行注冊的?interceptors?的?register?監(jiān)聽,并返回執(zhí)行后的?options
          ??options?=?this._runRegisterInterceptors(options);
          ??//?收集到?taps?中
          ??this._insert(options);
          ?}
          ?_runRegisterInterceptors(options)?{
          ??for?(const?interceptor?of?this.interceptors)?{
          ???if?(interceptor.register)?{
          ????const?newOptions?=?interceptor.register(options);
          ????if?(newOptions?!==?undefined)?{
          ?????options?=?newOptions;
          ????}
          ???}
          ??}
          ??return?options;
          ?}
          ?...
          }

          可以看到三種注冊的方法都是通過_tap 來實(shí)現(xiàn)的,只是傳入的 type 不同。_tap 主要做了兩件事。

          1. 執(zhí)行 interceptor.register,并返回 options
          2. 收集注冊項(xiàng)到 this.taps 列表中,同時(shí)根據(jù) stage 和 before 排序。(stage 和 before 是注冊時(shí)的可選參數(shù))

          收集完注冊項(xiàng),接下來就是執(zhí)行這個(gè)流程:

          const?CALL_DELEGATE?=?function(...args)?{
          ?this.call?=?this._createCall("sync");
          ?return?this.call(...args);
          };
          const?CALL_ASYNC_DELEGATE?=?function(...args)?{
          ?this.callAsync?=?this._createCall("async");
          ?return?this.callAsync(...args);
          };
          const?PROMISE_DELEGATE?=?function(...args)?{
          ?this.promise?=?this._createCall("promise");
          ?return?this.promise(...args);
          };
          class?Hook?{
          ?constructor()?{
          ??...
          ??this._call?=?CALL_DELEGATE;
          ??this.call?=?CALL_DELEGATE;
          ??this._callAsync?=?CALL_ASYNC_DELEGATE;
          ??this.callAsync?=?CALL_ASYNC_DELEGATE;
          ??this._promise?=?PROMISE_DELEGATE;
          ??this.promise?=?PROMISE_DELEGATE;
          ??...
          ?}
          ?compile(options)?{
          ??throw?new?Error("Abstract:?should?be?overridden");
          ?}

          ?_createCall(type)?{
          ??return?this.compile({
          ???taps:?this.taps,
          ???interceptors:?this.interceptors,
          ???args:?this._args,
          ???type:?type
          ??});
          ?}
          }

          執(zhí)行流程可以說是殊途同歸,最后都是通過_createCall 來返回一個(gè) compile 執(zhí)行后的值。從上文可知,tapable 的執(zhí)行流程有同步,異步串行,異步并行、循環(huán)等,因此 Hook 類只提供了一個(gè)抽象方法 compile,那么 compile 具體是怎么樣的呢。這就引出了下一個(gè)核心類 HookCodeFactory。

          HookCodeFactory

          見名知意,該類是一個(gè)返回 hookCode 的工廠。首先來看下這個(gè)工廠是如何被使用的。這是其中一種 hook 類 AsyncSeriesHook 使用方式:

          const?HookCodeFactory?=?require("./HookCodeFactory");

          class?AsyncSeriesHookCodeFactory?extends?HookCodeFactory?{
          ?content({?onError,?onDone?})?{
          ??return?this.callTapsSeries({
          ???onError:?(i,?err,?next,?doneBreak)?=>?onError(err)?+?doneBreak(true),
          ???onDone
          ??});
          ?}
          }

          const?factory?=?new?AsyncSeriesHookCodeFactory();
          //?options?=?{
          //???taps:?this.taps,
          //???interceptors:?this.interceptors,
          //???args:?this._args,
          //???type:?type
          //?}
          const?COMPILE?=?function(options)?{
          ?factory.setup(this,?options);
          ?return?factory.create(options);
          };

          function?AsyncSeriesHook(args?=?[],?name?=?undefined)?{
          ?const?hook?=?new?Hook(args,?name);
          ?hook.constructor?=?AsyncSeriesHook;
          ?hook.compile?=?COMPILE;
          ?...
          ?return?hook;
          }

          HookCodeFactory 的職責(zé)就是將執(zhí)行代碼賦值給 hook.compile,從而使 hook 得到執(zhí)行能力。來看看該類內(nèi)部運(yùn)轉(zhuǎn)邏輯是這樣的:

          class?HookCodeFactory?{
          ?constructor(config)?{
          ??this.config?=?config;
          ??this.options?=?undefined;
          ??this._args?=?undefined;
          ?}
          ?...
          ?create(options)?{
          ??...
          ??this.init(options);
          ??//?type
          ??switch?(this.options.type)?{
          ???case?"sync":?fn?=?new?Function(省略...);break;
          ???case?"async":?fn?=?new?Function(省略...);break;
          ???case?"promise":?fn?=?new?Function(省略...);break;
          ??}
          ??this.deinit();
          ??return?fn;
          ?}
          ?init(options)?{
          ??this.options?=?options;
          ??this._args?=?options.args.slice();
          ?}

          ?deinit()?{
          ??this.options?=?undefined;
          ??this._args?=?undefined;
          ?}
          }

          最終返回給 compile 就是 create 返回的這個(gè) fn,fn 則是通過 new Function()進(jìn)行創(chuàng)建的。那么重點(diǎn)就是這個(gè) new Function 中了。

          先了解一下 new Function 的語法

          new Function ([arg1[, arg2[, ...argN]],] functionBody)

          • arg1, arg2, ... argN:被函數(shù)使用的參數(shù)的名稱必須是合法命名的。參數(shù)名稱是一個(gè)有效的 JavaScript 標(biāo)識(shí)符的字符串,或者一個(gè)用逗號(hào)分隔的有效字符串的列表;例如“×”,“theValue”,或“a,b”。
          • functionBody:一個(gè)含有包括函數(shù)定義的 JavaScript 語句的字符串。

          基本用法:

          const?sum?=?new?Function('a',?'b',?'return?a?+?b');
          console.log(sum(2,?6));
          //?expected?output:?8

          使用 Function 構(gòu)造函數(shù)的方法:

          class?HookCodeFactory?{
          ?create()?{
          ??...
          ??fn?=?new?Function(this.args({...}),?code)
          ??...
          ??return?fn
          ?}
          ?args({?before,?after?}?=?{})?{
          ??let?allArgs?=?this._args;
          ??if?(before)?allArgs?=?[before].concat(allArgs);
          ??if?(after)?allArgs?=?allArgs.concat(after);
          ??if?(allArgs.length?===?0)?{
          ???return?"";
          ??}?else?{
          ???return?allArgs.join(",?");
          ??}
          ?}
          }

          這個(gè) this.args()就是返回執(zhí)行時(shí)傳入?yún)?shù)名,為后面 code 提供了對應(yīng)參數(shù)值。

          fn?=?new?Function(
          ?this.args({...}),?
          ?'"use?strict";\n'?+
          ??this.header()?+
          ??this.contentWithInterceptors({
          ???onError:?err?=>?`throw?${err};\n`,
          ???onResult:?result?=>?`return?${result};\n`,
          ???resultReturns:?true,
          ???onDone:?()?=>?"",
          ???rethrowIfPossible:?true
          ??})
          )
          header()?{
          ?let?code?=?"";
          ?if?(this.needContext())?{
          ??code?+=?"var?_context?=?{};\n";
          ?}?else?{
          ??code?+=?"var?_context;\n";
          ?}
          ?code?+=?"var?_x?=?this._x;\n";
          ?if?(this.options.interceptors.length?>?0)?{
          ??code?+=?"var?_taps?=?this.taps;\n";
          ??code?+=?"var?_interceptors?=?this.interceptors;\n";
          ?}
          ?return?code;
          }

          contentWithInterceptors()?{
          ?//?由于代碼過多這邊描述一下過程
          ?// 1. 生成監(jiān)聽的回調(diào)對象如:
          ?//?{
          ?//??onError,
          ?//??onResult,
          ?//??resultReturns,
          ?//??onDone,
          ?//??rethrowIfPossible
          ?//?}
          ??//?2.?執(zhí)行?this.content({...}),入?yún)榈谝徊椒祷氐膶ο?/span>
          ?...
          }

          而對應(yīng)的 functionBody 則是通過 header 和 contentWithInterceptors 共同生成的。this.content 則是根據(jù)鉤子類型的不同調(diào)用不同的方法如下面代碼則調(diào)用的是 callTapsSeries:

          class?SyncHookCodeFactory?extends?HookCodeFactory?{
          ?content({?onError,?onDone,?rethrowIfPossible?})?{
          ??return?this.callTapsSeries({
          ???onError:?(i,?err)?=>?onError(err),
          ???onDone,
          ???rethrowIfPossible
          ??});
          ?}
          }

          HookCodeFactory 有三種生成 code 的方法:

          //?串行
          callTapsSeries()?{...}
          //?循環(huán)
          callTapsLooping()?{...}
          //?并行
          callTapsParallel()?{...}
          //?執(zhí)行單個(gè)注冊回調(diào),通過判斷?sync、async、promise?返回對應(yīng)?code
          callTap()?{...}
          1. 并行(Parallel)原理:并行的情況只有在異步的時(shí)候才發(fā)生,因此執(zhí)行所有的 taps 后,判斷計(jì)數(shù)器是否為 0,為 0 則執(zhí)行結(jié)束回調(diào)(計(jì)數(shù)器為 0 有可能是因?yàn)?taps 全部執(zhí)行完畢,有可能是因?yàn)榉祷刂挡粸?undefined,手動(dòng)設(shè)置為 0)
          2. 循環(huán)(Loop)原理:生成 do{}while(__loop)的代碼,將執(zhí)行后的值是否為 undefined 賦值給_loop,從而來控制循環(huán)
          3. 串行:就是按照 taps 的順序來生成執(zhí)行的代碼
          4. callTap:執(zhí)行單個(gè)注冊回調(diào)
          • sync:按照順序執(zhí)行
          var?_fn0?=?_x[0];
          _fn0(arg1,?arg2,?arg3);
          var?_fn1?=?_x[1];
          _fn1(arg1,?arg2,?arg3);
          var?_fn2?=?_x[2];
          _fn2(arg1,?arg2,?arg3);
          • async 原理:將單個(gè) tap 封裝成一個(gè)_next[index]函數(shù),當(dāng)前一個(gè)函數(shù)執(zhí)行完成即調(diào)用了 callback,則會(huì)繼續(xù)執(zhí)行下一個(gè)_next[index]函數(shù),如生成如下 code:
          function?_next1()?{
          ??var?_fn2?=?_x[2];
          ??_fn2(name,?(function?(_err2)?{
          ????if?(_err2)?{
          ??????_callback(_err2);
          ????}?else?{
          ??????_callback();
          ????}
          ??}));
          }

          function?_next0()?{
          ??var?_fn1?=?_x[1];
          ??_fn1(name,?(function?(_err1)?{
          ????if?(_err1)?{
          ??????_callback(_err1);
          ????}?else?{
          ??????_next1();
          ????}
          ??}));
          }
          var?_fn0?=?_x[0];
          _fn0(name,?(function?(_err0)?{
          ??if?(_err0)?{
          ????_callback(_err0);
          ??}?else?{
          ????_next0();
          ??}
          }));
          • promise:將單個(gè) tap 封裝成一個(gè)_next[index]函數(shù),當(dāng)前一個(gè)函數(shù)執(zhí)行完成即調(diào)用了 promise.then(),then 中則會(huì)繼續(xù)執(zhí)行下一個(gè)_next[index]函數(shù),如生成如下 code:
          function?_next1()?{
          ??var?_fn2?=?_x[2];
          ??var?_hasResult2?=?false;
          ??var?_promise2?=?_fn2(name);
          ??if?(!_promise2?||?!_promise2.then)
          ????throw?new?Error('Tap?function?(tapPromise)?did?not?return?promise?(returned?'?+?_promise2?+?')');
          ??_promise2.then((function?(_result2)?{
          ????_hasResult2?=?true;
          ????_resolve();
          ??}),?function?(_err2)?{
          ????if?(_hasResult2)?throw?_err2;
          ????_error(_err2);
          ??});
          }

          function?_next0()?{
          ??var?_fn1?=?_x[1];
          ??var?_hasResult1?=?false;
          ??var?_promise1?=?_fn1(name);
          ??if?(!_promise1?||?!_promise1.then)
          ????throw?new?Error('Tap?function?(tapPromise)?did?not?return?promise?(returned?'?+?_promise1?+?')');
          ??_promise1.then((function?(_result1)?{
          ????_hasResult1?=?true;
          ????_next1();
          ??}),?function?(_err1)?{
          ????if?(_hasResult1)?throw?_err1;
          ????_error(_err1);
          ??});
          }
          var?_fn0?=?_x[0];
          var?_hasResult0?=?false;
          var?_promise0?=?_fn0(name);
          if?(!_promise0?||?!_promise0.then)
          ??throw?new?Error('Tap?function?(tapPromise)?did?not?return?promise?(returned?'?+?_promise0?+?')');
          _promise0.then((function?(_result0)?{
          ??_hasResult0?=?true;
          ??_next0();
          }),?function?(_err0)?{
          ??if?(_hasResult0)?throw?_err0;
          ??_error(_err0);
          });

          將以上的執(zhí)行順序以及執(zhí)行方式來進(jìn)行組合,就得到了現(xiàn)在的 9 種 Hook 類。若后續(xù)需要更多的模式只需要增加執(zhí)行順序或者執(zhí)行方式就能夠完成拓展。

          如圖所示:

          a837bd657a95908fba0d5a44e468796a.webpTapable bda4604e3f27488082fd7a2820082dbc 1.png如何助力 webpack

          插件可以使用 tapable 對外暴露的方法向 webpack 中注入自定義構(gòu)建的步驟,這些步驟將在構(gòu)建過程中觸發(fā)。

          webpack 將整個(gè)構(gòu)建的步驟生成一個(gè)一個(gè) hook 鉤子(即 tapable 的 9 種 hook 類型的實(shí)例),存儲(chǔ)在 hooks 的對象里。插件可以通過 Compiler 或者 Compilation 訪問到對應(yīng)的 hook 鉤子的實(shí)例,進(jìn)行注冊(tap,tapAsync,tapPromise)。當(dāng) webpack 執(zhí)行到相應(yīng)步驟時(shí)就會(huì)通過 hook 來進(jìn)行執(zhí)行(call, callAsync,promise),從而執(zhí)行注冊的回調(diào)。以 ConsoleLogOnBuildWebpackPlugin 自定義插件為例:

          const?pluginName?=?'ConsoleLogOnBuildWebpackPlugin';

          class?ConsoleLogOnBuildWebpackPlugin?{
          ??apply(compiler)?{
          ????compiler.hooks.run.tap(pluginName,?(compilation)?=>?{
          ??????console.log('webpack 構(gòu)建過程開始!');
          ????});
          ??}
          }

          module.exports?=?ConsoleLogOnBuildWebpackPlugin;

          可以看到在 apply 中通過 compiler 的 hooks 注冊(tap)了在 run 階段時(shí)的回調(diào)。從 Compiler 類中可以了解到在 hooks 對象中對 run 屬性賦值 AsyncSeriesHook 的實(shí)例,并在執(zhí)行的時(shí)候通過 this.hooks.run.callAsync 觸發(fā)了已注冊的對應(yīng)回調(diào):

          class?Compiler?{
          ?constructor(context)?{
          ??this.hooks?=?Object.freeze({
          ????...
          ????run:?new?AsyncSeriesHook(["compiler"]),
          ????...
          ??})
          ?}
          ?run()?{
          ??...
          ??const?run?=?()?=>?{
          ???this.hooks.beforeRun.callAsync(this,?err?=>?{
          ????if?(err)?return?finalCallback(err);

          ????this.hooks.run.callAsync(this,?err?=>?{
          ?????if?(err)?return?finalCallback(err);

          ?????this.readRecords(err?=>?{
          ??????if?(err)?return?finalCallback(err);

          ??????this.compile(onCompiled);
          ?????});
          ????});
          ???});
          ??};
          ??...
          ?}
          }

          如圖所示,為該自定義插件的執(zhí)行過程:

          f7475f183a2c557cfc8523ebe8a07241.webp_(1) 1.png總結(jié)
          1. tapable 對外暴露 9 種 hook 鉤子,核心方法是注冊、執(zhí)行、攔截器
          2. tapable 實(shí)現(xiàn)方式就是根據(jù)鉤子類型以及注冊類型來拼接字符串傳入 Function 構(gòu)造函數(shù)創(chuàng)建一個(gè)新的 Function 對象
          3. webpack 通過 tapable 來對整個(gè)構(gòu)建步驟進(jìn)行了流程化的管理。實(shí)現(xiàn)了對每個(gè)構(gòu)建步驟都能進(jìn)行靈活定制化需求。

          如有意見,歡迎一鍵素質(zhì)三連,寶~。

          參考資料

          [1]webpack 官方文檔中對于 plugin 的介紹: https://webpack.docschina.org/concepts/plugins/

          [2]tapable 相關(guān)介紹:http://www.zhufengpeixun.com/grow/html/103.7.webpack-tapable.html

          [3]tabpable 源碼:https://github.com/webpack/tapable

          [4]webpack 源碼:https://github.com/webpack/webpack


          瀏覽 119
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美AⅤ在线 | 日韩一级爱爱 | 日韩成人电影在线免费 | 精品无码一区二区三区四区 | a黄色片网站 |