自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

談談Tapable的前世今生

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

[[406057]]

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

Tapable 和 webpack 的關(guān)系

webpack 是什么?

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

webpack 的重要模塊

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

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

webpack 的構(gòu)建流程

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

以 Compiler 為例:

  1. // webpack5 前,通過繼承 
  2. ... 
  3. const { 
  4.  Tapable, 
  5.  SyncHook, 
  6.  SyncBailHook, 
  7.  AsyncParallelHook, 
  8.  AsyncSeriesHook 
  9. } = require("tapable"); 
  10. ... 
  11. class Compiler extends Tapable { 
  12.  constructor(context) { 
  13.   super(); 
  14.   ... 
  15.  } 
  16.  
  17. // webpack5 
  18. ... 
  19. const { 
  20.  SyncHook, 
  21.  SyncBailHook, 
  22.  AsyncParallelHook, 
  23.  AsyncSeriesHook 
  24. } = require("tapable"); 
  25. ... 
  26. class Compiler { 
  27.  constructor(context) { 
  28.   this.hooks = Object.freeze({ 
  29.    /** @type {SyncHook<[]>} */ 
  30.    initialize: new SyncHook([]), 
  31.  
  32.    /** @type {SyncBailHook<[Compilation], boolean>} */ 
  33.    shouldEmit: new SyncBailHook(["compilation"]), 
  34.    ... 
  35.   }) 
  36.  } 
  37.  ... 

Tapable 的使用姿勢

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

  1. const { 
  2.  SyncHook, 
  3.  SyncBailHook, 
  4.  SyncWaterfallHook, 
  5.  SyncLoopHook, 
  6.  AsyncParallelHook, 
  7.  AsyncParallelBailHook, 
  8.  AsyncSeriesHook, 
  9.  AsyncSeriesBailHook, 
  10.  AsyncSeriesWaterfallHook 
  11.  } = require("tapable"); 

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

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

  • Sync:只能被同步函數(shù)注冊,如 myHook.tap()
  • AsyncSeries:可以被同步的,基于回調(diào)的,基于 promise 的函數(shù)注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序為串行
  • AsyncParallel:可以被同步的,基于回調(diào)的,基于 promise 的函數(shù)注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序為并行

按執(zhí)行模式分類

  • Basic:執(zhí)行每一個事件函數(shù),不關(guān)心函數(shù)的返回值

  • Bail:執(zhí)行每一個事件函數(shù),遇到第一個結(jié)果 result !== undefined 則返回,不再繼續(xù)執(zhí)行

  • Waterfall:如果前一個事件函數(shù)的結(jié)果 result !== undefined,則 result 會作為后一個事件函數(shù)的第一個參數(shù)

  • Loop:不停的循環(huán)執(zhí)行事件函數(shù),直到所有函數(shù)結(jié)果 result === undefined

使用方式

Hook 類

使用簡單來說就是下面步驟

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

以最簡單的 SyncHook 為例:

  1. // 簡單來說就是實例化 Hooks 類 
  2. // 接收一個可選參數(shù),參數(shù)是一個參數(shù)名的字符串數(shù)組 
  3. const hook = new SyncHook(["arg1""arg2""arg3"]); 
  4. // 注冊 
  5. // 第一個入?yún)樽悦?nbsp;
  6. // 第二個為注冊回調(diào)方法 
  7. hook.tap("1", (arg1, arg2, arg3) => { 
  8.   console.log(1, arg1, arg2, arg3); 
  9.   return 1; 
  10. }); 
  11. hook.tap("2", (arg1, arg2, arg3) => { 
  12.   console.log(2, arg1, arg2, arg3); 
  13.   return 2; 
  14. }); 
  15. hook.tap("3", (arg1, arg2, arg3) => { 
  16.   console.log(3, arg1, arg2, arg3); 
  17.   return 3; 
  18. }); 
  19. // 執(zhí)行 
  20. // 執(zhí)行順序則是根據(jù)這個實例類型來決定的 
  21. hook.call("a""b""c"); 
  22.  
  23. //------輸出------ 
  24. // 先注冊先觸發(fā) 
  25. 1 a b c 
  26. 2 a b c 
  27. 3 a b c 

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

  1. let { AsyncSeriesHook } = require("tapable"); 
  2. let queue = new AsyncSeriesHook(["name"]); 
  3. console.time("cost"); 
  4. queue.tapPromise("1"function (name) { 
  5.   return new Promise(function (resolve) { 
  6.     setTimeout(function () { 
  7.       console.log(1, name); 
  8.       resolve(); 
  9.     }, 1000); 
  10.   }); 
  11. }); 
  12. queue.tapPromise("2"function (name) { 
  13.   return new Promise(function (resolve) { 
  14.     setTimeout(function () { 
  15.       console.log(2, name); 
  16.       resolve(); 
  17.     }, 2000); 
  18.   }); 
  19. }); 
  20. queue.tapPromise("3"function (name) { 
  21.   return new Promise(function (resolve) { 
  22.     setTimeout(function () { 
  23.       console.log(3, name); 
  24.       resolve(); 
  25.     }, 3000); 
  26.   }); 
  27. }); 
  28. queue.promise("weiyi").then((data) => { 
  29.   console.log(data); 
  30.   console.timeEnd("cost"); 
  31. }); 

HookMap 類使用

A HookMap is a helper class for a Map with Hooks

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

  1. class Car { 
  2.  constructor() { 
  3.   this.hooks = { 
  4.    accelerate: new SyncHook(["newSpeed"]), 
  5.    brake: new SyncHook(), 
  6.    calculateRoutes: new AsyncParallelHook(["source""target""routesList"]) 
  7.   }; 
  8.  } 
  9.  /* ... */ 
  10.  setSpeed(newSpeed) { 
  11.   // following call returns undefined even when you returned values 
  12.   this.hooks.accelerate.call(newSpeed); 
  13.  } 

注冊&執(zhí)行:

  1. const myCar = new Car(); 
  2.  
  3. myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`)); 
  4.  
  5. myCar.setSpeed(1) 

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

  1. const keyedHook = new HookMap(key => new SyncHook(["arg"])) 
  2.  
  3. keyedHook.for("some-key").tap("MyPlugin", (arg) => { /* ... */ }); 
  4. keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => { /* ... */ }); 
  5. keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => { /* ... */ }); 
  6.  
  7. const hook = keyedHook.get("some-key"); 
  8. if(hook !== undefined) { 
  9.  hook.callAsync("arg", err => { /* ... */ }); 

MultiHook 類使用

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

相當于提供一個存放一個 hooks 列表的輔助類:

  1. const { MultiHook } = require("tapable"); 
  2.  
  3. this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]); 

Tapable 的原理

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

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

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

Hook

首先看 Hook 的屬性,可以看到屬性中有熟悉的注冊的方法:tap、tapAsync、tapPromise。執(zhí)行方法:call、promise、callAsync。以及存放所有的注冊項 taps。constructor 的入?yún)⒕褪敲總€鉤子實例化時的入?yún)ⅰ膶傩陨暇湍軌蛑朗?Hook 類為繼承它的子類提供了最基礎(chǔ)的注冊和執(zhí)行的方法

  1. class Hook { 
  2.  constructor(args = [], name = undefined) { 
  3.   this._args = args; 
  4.   this.name = name
  5.   this.taps = []; 
  6.   this.interceptors = []; 
  7.   this._call = CALL_DELEGATE; 
  8.   this.call = CALL_DELEGATE; 
  9.   this._callAsync = CALL_ASYNC_DELEGATE; 
  10.   this.callAsync = CALL_ASYNC_DELEGATE; 
  11.   this._promise = PROMISE_DELEGATE; 
  12.   this.promise = PROMISE_DELEGATE; 
  13.   this._x = undefined; 
  14.  
  15.   this.compile = this.compile; 
  16.   this.tap = this.tap; 
  17.   this.tapAsync = this.tapAsync; 
  18.   this.tapPromise = this.tapPromise; 
  19.  } 
  20.  ... 

那么 Hook 類是如何收集注冊項的?如代碼所示:

  1. class Hook { 
  2.  ... 
  3.  tap(options, fn) { 
  4.   this._tap("sync", options, fn); 
  5.  } 
  6.  
  7.  tapAsync(options, fn) { 
  8.   this._tap("async", options, fn); 
  9.  } 
  10.  
  11.  tapPromise(options, fn) { 
  12.   this._tap("promise", options, fn); 
  13.  } 
  14.  
  15.  _tap(type, options, fn) { 
  16.   if (typeof options === "string") { 
  17.    options = { 
  18.     name: options.trim() 
  19.    }; 
  20.   } else if (typeof options !== "object" || options === null) { 
  21.    throw new Error("Invalid tap options"); 
  22.   } 
  23.   if (typeof options.name !== "string" || options.name === "") { 
  24.    throw new Error("Missing name for tap"); 
  25.   } 
  26.   if (typeof options.context !== "undefined") { 
  27.    deprecateContext(); 
  28.   } 
  29.   // 合并參數(shù) 
  30.   options = Object.assign({ type, fn }, options); 
  31.   // 執(zhí)行注冊的 interceptors 的 register 監(jiān)聽,并返回執(zhí)行后的 options 
  32.   options = this._runRegisterInterceptors(options); 
  33.   // 收集到 taps 中 
  34.   this._insert(options); 
  35.  } 
  36.  _runRegisterInterceptors(options) { 
  37.   for (const interceptor of this.interceptors) { 
  38.    if (interceptor.register) { 
  39.     const newOptions = interceptor.register(options); 
  40.     if (newOptions !== undefined) { 
  41.      options = newOptions; 
  42.     } 
  43.    } 
  44.   } 
  45.   return options; 
  46.  } 
  47.  ... 

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

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

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

  1. const CALL_DELEGATE = function(...args) { 
  2.  this.call = this._createCall("sync"); 
  3.  return this.call(...args); 
  4. }; 
  5. const CALL_ASYNC_DELEGATE = function(...args) { 
  6.  this.callAsync = this._createCall("async"); 
  7.  return this.callAsync(...args); 
  8. }; 
  9. const PROMISE_DELEGATE = function(...args) { 
  10.  this.promise = this._createCall("promise"); 
  11.  return this.promise(...args); 
  12. }; 
  13. class Hook { 
  14.  constructor() { 
  15.   ... 
  16.   this._call = CALL_DELEGATE; 
  17.   this.call = CALL_DELEGATE; 
  18.   this._callAsync = CALL_ASYNC_DELEGATE; 
  19.   this.callAsync = CALL_ASYNC_DELEGATE; 
  20.   this._promise = PROMISE_DELEGATE; 
  21.   this.promise = PROMISE_DELEGATE; 
  22.   ... 
  23.  } 
  24.  compile(options) { 
  25.   throw new Error("Abstract: should be overridden"); 
  26.  } 
  27.  
  28.  _createCall(type) { 
  29.   return this.compile({ 
  30.    taps: this.taps, 
  31.    interceptors: this.interceptors, 
  32.    args: this._args, 
  33.    type: type 
  34.   }); 
  35.  } 

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

HookCodeFactory

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

  1. const HookCodeFactory = require("./HookCodeFactory"); 
  2.  
  3. class AsyncSeriesHookCodeFactory extends HookCodeFactory { 
  4.  content({ onError, onDone }) { 
  5.   return this.callTapsSeries({ 
  6.    onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true), 
  7.    onDone 
  8.   }); 
  9.  } 
  10.  
  11. const factory = new AsyncSeriesHookCodeFactory(); 
  12. // options = { 
  13. //   taps: this.taps, 
  14. //   interceptors: this.interceptors, 
  15. //   args: this._args, 
  16. //   type: type 
  17. // } 
  18. const COMPILE = function(options) { 
  19.  factory.setup(this, options); 
  20.  return factory.create(options); 
  21. }; 
  22.  
  23. function AsyncSeriesHook(args = [], name = undefined) { 
  24.  const hook = new Hook(args, name); 
  25.  hook.constructor = AsyncSeriesHook; 
  26.  hook.compile = COMPILE; 
  27.  ... 
  28.  return hook; 

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

  1. class HookCodeFactory { 
  2.  constructor(config) { 
  3.   this.config = config; 
  4.   this.options = undefined; 
  5.   this._args = undefined; 
  6.  } 
  7.  ... 
  8.  create(options) { 
  9.   ... 
  10.   this.init(options); 
  11.   // type 
  12.   switch (this.options.type) { 
  13.    case "sync": fn = new Function(省略...);break; 
  14.    case "async": fn = new Function(省略...);break; 
  15.    case "promise": fn = new Function(省略...);break; 
  16.   } 
  17.   this.deinit(); 
  18.   return fn; 
  19.  } 
  20.  init(options) { 
  21.   this.options = options; 
  22.   this._args = options.args.slice(); 
  23.  } 
  24.  
  25.  deinit() { 
  26.   this.options = undefined; 
  27.   this._args = undefined; 
  28.  } 

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

先了解一下 new Function 的語法

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

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

基本用法:

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

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

  1. class HookCodeFactory { 
  2.  create() { 
  3.   ... 
  4.   fn = new Function(this.args({...}), code) 
  5.   ... 
  6.   return fn 
  7.  } 
  8.  args({ before, after } = {}) { 
  9.   let allArgs = this._args; 
  10.   if (before) allArgs = [before].concat(allArgs); 
  11.   if (after) allArgs = allArgs.concat(after); 
  12.   if (allArgs.length === 0) { 
  13.    return ""
  14.   } else { 
  15.    return allArgs.join(", "); 
  16.   } 
  17.  } 

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

  1. fn = new Function
  2.  this.args({...}),  
  3.  '"use strict";\n' + 
  4.   this.header() + 
  5.   this.contentWithInterceptors({ 
  6.    onError: err => `throw ${err};\n`, 
  7.    onResult: result => `return ${result};\n`, 
  8.    resultReturns: true
  9.    onDone: () => ""
  10.    rethrowIfPossible: true 
  11.   }) 
  12. header() { 
  13.  let code = ""
  14.  if (this.needContext()) { 
  15.   code += "var _context = {};\n"
  16.  } else { 
  17.   code += "var _context;\n"
  18.  } 
  19.  code += "var _x = this._x;\n"
  20.  if (this.options.interceptors.length > 0) { 
  21.   code += "var _taps = this.taps;\n"
  22.   code += "var _interceptors = this.interceptors;\n"
  23.  } 
  24.  return code; 
  25.  
  26. contentWithInterceptors() { 
  27.  // 由于代碼過多這邊描述一下過程 
  28.  // 1. 生成監(jiān)聽的回調(diào)對象如: 
  29.  // { 
  30.  //  onError, 
  31.  //  onResult, 
  32.  //  resultReturns, 
  33.  //  onDone, 
  34.  //  rethrowIfPossible 
  35.  // } 
  36.   // 2. 執(zhí)行 this.content({...}),入?yún)榈谝徊椒祷氐膶ο?nbsp;
  37.  ... 

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

  1. class SyncHookCodeFactory extends HookCodeFactory { 
  2.  content({ onError, onDone, rethrowIfPossible }) { 
  3.   return this.callTapsSeries({ 
  4.    onError: (i, err) => onError(err), 
  5.    onDone, 
  6.    rethrowIfPossible 
  7.   }); 
  8.  } 

HookCodeFactory 有三種生成 code 的方法:

  1. // 串行 
  2. callTapsSeries() {...} 
  3. // 循環(huán) 
  4. callTapsLooping() {...} 
  5. // 并行 
  6. callTapsParallel() {...} 
  7. // 執(zhí)行單個注冊回調(diào),通過判斷 sync、async、promise 返回對應 code 
  8. callTap() {...} 
  1. 并行(Parallel)原理:并行的情況只有在異步的時候才發(fā)生,因此執(zhí)行所有的 taps 后,判斷計數(shù)器是否為 0,為 0 則執(zhí)行結(jié)束回調(diào)(計數(shù)器為 0 有可能是因為 taps 全部執(zhí)行完畢,有可能是因為返回值不為 undefined,手動設(shè)置為 0)
  2. 循環(huán)(Loop)原理:生成 do{}while(__loop)的代碼,將執(zhí)行后的值是否為 undefined 賦值給_loop,從而來控制循環(huán)
  3. 串行:就是按照 taps 的順序來生成執(zhí)行的代碼
  4. callTap:執(zhí)行單個注冊回調(diào)
  • sync:按照順序執(zhí)行
  1. var _fn0 = _x[0]; 
  2. _fn0(arg1, arg2, arg3); 
  3. var _fn1 = _x[1]; 
  4. _fn1(arg1, arg2, arg3); 
  5. var _fn2 = _x[2]; 
  6. _fn2(arg1, arg2, arg3); 
  • async 原理:將單個 tap 封裝成一個_next[index]函數(shù),當前一個函數(shù)執(zhí)行完成即調(diào)用了 callback,則會繼續(xù)執(zhí)行下一個_next[index]函數(shù),如生成如下 code:
  1. function _next1() { 
  2.   var _fn2 = _x[2]; 
  3.   _fn2(name, (function (_err2) { 
  4.     if (_err2) { 
  5.       _callback(_err2); 
  6.     } else { 
  7.       _callback(); 
  8.     } 
  9.   })); 
  10.  
  11. function _next0() { 
  12.   var _fn1 = _x[1]; 
  13.   _fn1(name, (function (_err1) { 
  14.     if (_err1) { 
  15.       _callback(_err1); 
  16.     } else { 
  17.       _next1(); 
  18.     } 
  19.   })); 
  20. var _fn0 = _x[0]; 
  21. _fn0(name, (function (_err0) { 
  22.   if (_err0) { 
  23.     _callback(_err0); 
  24.   } else { 
  25.     _next0(); 
  26.   } 
  27. })); 
  • promise:將單個 tap 封裝成一個_next[index]函數(shù),當前一個函數(shù)執(zhí)行完成即調(diào)用了 promise.then(),then 中則會繼續(xù)執(zhí)行下一個_next[index]函數(shù),如生成如下 code:
  1. function _next1() { 
  2.   var _fn2 = _x[2]; 
  3.   var _hasResult2 = false
  4.   var _promise2 = _fn2(name); 
  5.   if (!_promise2 || !_promise2.then
  6.     throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')'); 
  7.   _promise2.then((function (_result2) { 
  8.     _hasResult2 = true
  9.     _resolve(); 
  10.   }), function (_err2) { 
  11.     if (_hasResult2) throw _err2; 
  12.     _error(_err2); 
  13.   }); 
  14.  
  15. function _next0() { 
  16.   var _fn1 = _x[1]; 
  17.   var _hasResult1 = false
  18.   var _promise1 = _fn1(name); 
  19.   if (!_promise1 || !_promise1.then
  20.     throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')'); 
  21.   _promise1.then((function (_result1) { 
  22.     _hasResult1 = true
  23.     _next1(); 
  24.   }), function (_err1) { 
  25.     if (_hasResult1) throw _err1; 
  26.     _error(_err1); 
  27.   }); 
  28. var _fn0 = _x[0]; 
  29. var _hasResult0 = false
  30. var _promise0 = _fn0(name); 
  31. if (!_promise0 || !_promise0.then
  32.   throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')'); 
  33. _promise0.then((function (_result0) { 
  34.   _hasResult0 = true
  35.   _next0(); 
  36. }), function (_err0) { 
  37.   if (_hasResult0) throw _err0; 
  38.   _error(_err0); 
  39. }); 

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

如圖所示:

如何助力 webpack

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

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

  1. const pluginName = 'ConsoleLogOnBuildWebpackPlugin'
  2.  
  3. class ConsoleLogOnBuildWebpackPlugin { 
  4.   apply(compiler) { 
  5.     compiler.hooks.run.tap(pluginName, (compilation) => { 
  6.       console.log('webpack 構(gòu)建過程開始!'); 
  7.     }); 
  8.   } 
  9.  
  10. module.exports = ConsoleLogOnBuildWebpackPlugin; 

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

  1. class Compiler { 
  2.  constructor(context) { 
  3.   this.hooks = Object.freeze({ 
  4.     ... 
  5.     run: new AsyncSeriesHook(["compiler"]), 
  6.     ... 
  7.   }) 
  8.  } 
  9.  run() { 
  10.   ... 
  11.   const run = () => { 
  12.    this.hooks.beforeRun.callAsync(this, err => { 
  13.     if (err) return finalCallback(err); 
  14.  
  15.     this.hooks.run.callAsync(this, err => { 
  16.      if (err) return finalCallback(err); 
  17.  
  18.      this.readRecords(err => { 
  19.       if (err) return finalCallback(err); 
  20.  
  21.       this.compile(onCompiled); 
  22.      }); 
  23.     }); 
  24.    }); 
  25.   }; 
  26.   ... 
  27.  } 

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

總結(jié)

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

參考資料

[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

 

責任編輯:姜華 來源: 微醫(yī)大前端技術(shù)
相關(guān)推薦

2011-08-23 09:52:31

CSS

2015-11-18 14:14:11

OPNFVNFV

2014-07-30 10:55:27

2025-02-12 11:25:39

2016-12-29 13:34:04

阿爾法狗圍棋計算機

2013-05-23 16:23:42

Windows Azu微軟公有云

2014-07-15 10:31:07

asyncawait

2012-05-18 16:54:21

FedoraFedora 17

2014-07-21 12:57:25

諾基亞微軟裁員

2016-12-29 18:21:01

2019-06-04 09:00:07

Jenkins X開源開發(fā)人員

2016-11-08 19:19:06

2016-11-03 13:33:31

2013-11-14 16:03:23

Android設(shè)計Android Des

2011-05-13 09:43:27

產(chǎn)品經(jīng)理PM

2019-08-05 10:08:25

軟件操作系統(tǒng)程序員

2019-04-28 09:34:06

2015-06-11 11:10:09

對象存儲云存儲

2021-04-15 07:01:28

區(qū)塊鏈分布式DLT

2022-11-07 14:23:35

RPA人工智能流程自動化管理
點贊
收藏

51CTO技術(shù)棧公眾號