精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

談談Tapable的前世今生

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

[[406057]]

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

Tapable 和 webpack 的關系

webpack 是什么?

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

webpack 的重要模塊

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

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

webpack 的構建流程

webpack 本質上是一種事件流的機制,它的工作流程就是將各個插件串聯起來,而實現這一切的核心就是 Tapable。webpack 中最核心的負責編譯的 Compiler 和負責創(chuàng)建 bundle 的 Compilation 都是 Tapable 的實例(webpack5 前)。webpack5 之后是通過定義屬性名為 hooks 來調度觸發(fā)時機。Tapable 充當的就是一個復雜的發(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:只能被同步函數注冊,如 myHook.tap()
  • AsyncSeries:可以被同步的,基于回調的,基于 promise 的函數注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序為串行
  • AsyncParallel:可以被同步的,基于回調的,基于 promise 的函數注冊,如 myHook.tap(),myHook.tapAsync() , myHook.tapPromise()。執(zhí)行順序為并行

按執(zhí)行模式分類

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

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

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

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

使用方式

Hook 類

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

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

以最簡單的 SyncHook 為例:

  1. // 簡單來說就是實例化 Hooks 類 
  2. // 接收一個可選參數,參數是一個參數名的字符串數組 
  3. const hook = new SyncHook(["arg1""arg2""arg3"]); 
  4. // 注冊 
  5. // 第一個入參為注冊名 
  6. // 第二個為注冊回調方法 
  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í)行順序則是根據這個實例類型來決定的 
  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 來進行注冊的回調存儲和觸發(fā),通過 HookCodeFactory 來控制注冊的執(zhí)行流程。

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

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

Hook

首先看 Hook 的屬性,可以看到屬性中有熟悉的注冊的方法:tap、tapAsync、tapPromise。執(zhí)行方法:call、promise、callAsync。以及存放所有的注冊項 taps。constructor 的入參就是每個鉤子實例化時的入參。從屬性上就能夠知道是 Hook 類為繼承它的子類提供了最基礎的注冊和執(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.   // 合并參數 
  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 來實現的,只是傳入的 type 不同。_tap 主要做了兩件事。

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

收集完注冊項,接下來就是執(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í)行能力。來看看該類內部運轉邏輯是這樣的:

  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:被函數使用的參數的名稱必須是合法命名的。參數名稱是一個有效的 JavaScript 標識符的字符串,或者一個用逗號分隔的有效字符串的列表;例如“×”,“theValue”,或“a,b”。
  • functionBody:一個含有包括函數定義的 JavaScript 語句的字符串。

基本用法:

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

使用 Function 構造函數的方法:

  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í)行時傳入參數名,為后面 code 提供了對應參數值。

  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)聽的回調對象如: 
  29.  // { 
  30.  //  onError, 
  31.  //  onResult, 
  32.  //  resultReturns, 
  33.  //  onDone, 
  34.  //  rethrowIfPossible 
  35.  // } 
  36.   // 2. 執(zhí)行 this.content({...}),入參為第一步返回的對象 
  37.  ... 

而對應的 functionBody 則是通過 header 和 contentWithInterceptors 共同生成的。this.content 則是根據鉤子類型的不同調用不同的方法如下面代碼則調用的是 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í)行單個注冊回調,通過判斷 sync、async、promise 返回對應 code 
  8. callTap() {...} 
  1. 并行(Parallel)原理:并行的情況只有在異步的時候才發(fā)生,因此執(zhí)行所有的 taps 后,判斷計數器是否為 0,為 0 則執(zhí)行結束回調(計數器為 0 有可能是因為 taps 全部執(zhí)行完畢,有可能是因為返回值不為 undefined,手動設置為 0)
  2. 循環(huán)(Loop)原理:生成 do{}while(__loop)的代碼,將執(zhí)行后的值是否為 undefined 賦值給_loop,從而來控制循環(huán)
  3. 串行:就是按照 taps 的順序來生成執(zhí)行的代碼
  4. callTap:執(zhí)行單個注冊回調
  • 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]函數,當前一個函數執(zhí)行完成即調用了 callback,則會繼續(xù)執(zhí)行下一個_next[index]函數,如生成如下 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]函數,當前一個函數執(zhí)行完成即調用了 promise.then(),then 中則會繼續(xù)執(zhí)行下一個_next[index]函數,如生成如下 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í)行方式來進行組合,就得到了現在的 9 種 Hook 類。若后續(xù)需要更多的模式只需要增加執(zhí)行順序或者執(zhí)行方式就能夠完成拓展。

如圖所示:

如何助力 webpack

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

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

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

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

  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í)行過程:

總結

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

參考資料

[1]webpack 官方文檔中對于 plugin 的介紹:

https://webpack.docschina.org/concepts/plugins/

[2]tapable 相關介紹:

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ī)大前端技術
相關推薦

2011-08-23 09:52:31

CSS

2014-07-30 10:55:27

2015-11-18 14:14:11

OPNFVNFV

2025-02-12 11:25:39

2012-05-18 16:54:21

FedoraFedora 17

2016-12-29 18:21:01

2019-06-04 09:00:07

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

2013-05-23 16:23:42

Windows Azu微軟公有云

2016-12-29 13:34:04

阿爾法狗圍棋計算機

2014-07-15 10:31:07

asyncawait

2014-07-21 12:57:25

諾基亞微軟裁員

2016-11-03 13:33:31

2016-11-08 19:19:06

2015-06-11 11:10:09

對象存儲云存儲

2019-08-05 10:08:25

軟件操作系統程序員

2013-11-14 16:03:23

Android設計Android Des

2022-11-07 14:23:35

RPA人工智能流程自動化管理

2011-05-13 09:43:27

產品經理PM

2021-04-15 07:01:28

區(qū)塊鏈分布式DLT

2019-04-28 09:34:06

點贊
收藏

51CTO技術棧公眾號

视频三区二区一区| 伦理中文字幕亚洲| 激情六月丁香婷婷| 成年人视频在线观看免费| 久久91精品久久久久久秒播| 欧美成人合集magnet| 国产人妻黑人一区二区三区| 欧美男体视频| 亚洲九九爱视频| 免费中文日韩| 国产特级aaaaaa大片| 午夜亚洲精品| 久热精品视频在线免费观看| 国产熟妇久久777777| 国产色99精品9i| 日韩欧美极品在线观看| 永久域名在线精品| 日韩电影免费| 国产不卡视频在线播放| 国产精品久久久久久久久久久久久久 | 台湾佬美性中文| 台湾佬中文娱乐久久久| 一区二区三区加勒比av| 视频在线精品一区| 天堂在线观看av| 黑人精品欧美一区二区蜜桃 | 亚洲高清视频一区二区| 全部免费毛片在线播放一个| 久久国产综合精品| 日本最新高清不卡中文字幕| 国产中文字幕免费| 一区二区电影| 国产片一区二区| 亚洲美女偷拍久久| 成人片在线免费看| 久久精品99北条麻妃| 国产精品女主播一区二区三区| 久久久精品亚洲| 日本一卡二卡在线播放| 香蕉人人精品| 日韩av在线不卡| 91香蕉视频在线观看视频| 成人精品电影在线| 岛国av在线不卡| 国产一区 在线播放| 成人影院www在线观看| 国产精品久久影院| 日韩av一区在线观看| 国产又黄又猛的视频| 国产精品99久久久久久董美香 | 香蕉国产在线视频| 成人a区在线观看| 99re在线国产| 精品女同一区二区三区| 国产精品中文欧美| 91久久极品少妇xxxxⅹ软件| 国产sm主人调教女m视频| 麻豆成人91精品二区三区| 国产精品夫妻激情| 中文字幕 亚洲视频| 日本不卡视频一二三区| 国产精品最新在线观看| 在线观看免费视频a| 狠狠色狠狠色合久久伊人| 成人久久久久爱| a级片免费观看| 国产不卡视频在线播放| 精品视频第一区| 伦理片一区二区三区| 国产三级三级三级精品8ⅰ区| 日韩在线第一区| 欧美边添边摸边做边爱免费| 亚洲精品v日韩精品| 福利视频一区二区三区四区| 国产美女高潮在线| 欧美经典一区二区三区| 久久av资源网站| 91久久国产自产拍夜夜嗨| 啪啪小视频网站| 精品一区二区三区免费观看| 亚洲free嫩bbb| 免费观看黄色一级视频| 久久蜜桃av一区精品变态类天堂 | 欧美激情女人20p| 在线观看免费国产视频| 久久影院亚洲| 成人在线小视频| 黄色av网址在线| 久久综合九色综合97婷婷| 亚洲精品成人a8198a| www.在线视频| 精品国产精品自拍| 亚洲最大成人在线观看| 一区二区三区视频免费视频观看网站 | 中文字幕亚洲专区| 精品欧美一区二区久久久久| 国产欧美亚洲一区| 成人久久18免费网站图片| 三级小视频在线观看| 欧美激情一区三区| 成人一级生活片| 亚洲天堂一区二区| 精品国偷自产国产一区| 美女100%露胸无遮挡| 欧美日本久久| 国产美女久久精品| 天堂网在线播放| 亚洲欧洲日本在线| 国产精品裸体瑜伽视频| 北条麻妃在线| 亚洲黄色av一区| 91蝌蚪视频在线观看| 亚洲精品一区二区三区在线| 国产亚洲欧美另类中文| 久久精品性爱视频| 国产精品资源在线观看| 翔田千里亚洲一二三区| 欧美极品videos大乳护士| 884aa四虎影成人精品一区| 三级男人添奶爽爽爽视频| 欧美gayvideo| 国产一区欧美一区| 久久久免费在线观看| 波多野结衣在线观看视频| 国产成人免费视频一区| 色一情一乱一伦一区二区三区| www.综合网.com| 欧美一区二区啪啪| 国产主播av在线| 蜜乳av另类精品一区二区| 国产精品国产亚洲精品看不卡15| 欧美激情午夜| 欧美性大战久久久久久久蜜臀 | 亚洲欧美999| 麻豆一区二区三区精品视频| 国产一区二区三区国产| 亚洲国产精品久久久久婷婷老年| 日韩激情电影免费看| 欧美精品一区二区久久婷婷| 欧美特黄一级片| 美女视频网站久久| 日韩av图片| 免费福利视频一区二区三区| 亚洲成人激情视频| 久操免费在线视频| 国产99精品国产| 米仓穗香在线观看| 九色精品蝌蚪| 欧美精品一区二区免费| 国产裸体无遮挡| 中文字幕一区二区三中文字幕| 亚洲天堂av线| 成人中文在线| 国产精品香蕉在线观看| eeuss影院www在线观看| 欧美亚洲精品一区| 91麻豆制片厂| 激情六月婷婷综合| 欧美精品久久96人妻无码| 巨大黑人极品videos精品| 日韩在线小视频| 一区二区三区精| 亚洲日本青草视频在线怡红院| 国产免费中文字幕| 欧美日韩国产免费观看| 国产精品免费视频一区二区| www在线看| 亚洲日本中文字幕| 亚洲无码精品在线观看| 自拍av一区二区三区| 香蕉在线观看视频| 69堂免费精品视频在线播放| 美国av一区二区| 国产综合动作在线观看| 涩涩av在线| 在线观看欧美日韩国产| 国产精品久久久久久久成人午夜| 亚洲乱码日产精品bd| v天堂中文在线| 日韩va欧美va亚洲va久久| 一区二区三区视频| 一本一道久久a久久| 欧美在线视频导航| 日本网站在线免费观看视频| 日韩精品一区二区三区视频在线观看| 国产精品成人网站| 国产亚洲一区二区在线观看| 在线视频观看91| 亚洲激情网址| 亚洲精品一区二区三区蜜桃久| 免费一区二区三区在线视频| 2020国产精品视频| 秋霞午夜理伦电影在线观看| 亚洲国产91精品在线观看| 久久人人爽人人爽人人片av免费| 亚洲欧美福利一区二区| 一级国产黄色片| 激情久久久久久久久久久久久久久久| 性一交一乱一伧国产女士spa| 国产精品一区二区三区av麻| 97中文在线观看| 成人啊v在线| 欧美极品少妇xxxxx| 国产69精品久久app免费版| 日韩精品在线网站| 少妇无套内谢久久久久| 天天操天天综合网| 精品无码一区二区三区蜜臀| www国产成人| 女人扒开腿免费视频app| 日韩精品亚洲一区二区三区免费| 国产制服91一区二区三区制服| 九九久久电影| 国产另类自拍| 免费观看亚洲视频大全| 国产精品久久久久av免费| av不卡高清| 久久这里有精品视频| 国产三级在线免费观看| 亚洲国产欧美日韩精品| 国产黄色一级大片| 欧美日本在线播放| 波多野结衣不卡| 精品久久香蕉国产线看观看gif| 久草视频手机在线| 国产精品区一区二区三| 波多野结衣av在线观看| 99久久夜色精品国产网站| 精品无码av一区二区三区不卡| 日韩福利片在线观看| 91小视频在线免费看| 人妻精油按摩bd高清中文字幕| 男男成人高潮片免费网站| 国内自拍在线观看| 日韩网站在线| 一本久道高清无码视频| 亚洲综合小说| 性欧美18一19内谢| 99久久这里只有精品| 亚洲精品日韩在线观看| 欧美色图国产精品| 日韩国产精品一区二区三区| 日本欧美三级| 精品欧美一区二区在线观看视频| 波多野结衣欧美| 国产精品香蕉视屏| 大香伊人久久精品一区二区| av在线不卡一区| 欧美第一在线视频| 91久久精品一区二区别| 亚洲精品影片| 国产精品一 二 三| 久久夜色精品国产噜噜av小说| 国产精品久久国产三级国电话系列 | 国产精品美女久久久久久不卡| 免费在线国产精品| 精品久久电影| 亚洲一区在线直播| 91久久国产| a级片一区二区| 国内精品久久久久久久影视麻豆| 日本国产中文字幕| 亚洲日本久久| 欧美 激情 在线| 日韩成人伦理电影在线观看| 欧美三级午夜理伦三级富婆| 久久爱另类一区二区小说| www.com久久久| 粉嫩av一区二区三区粉嫩| 麻豆精品国产传媒av| 91天堂素人约啪| 一区二区三区久久久久| 国产精品毛片无遮挡高清| 夫妻性生活毛片| 亚洲丶国产丶欧美一区二区三区| 人妻换人妻a片爽麻豆| 成人午夜大片免费观看| 天堂久久久久久| 国产日韩精品一区| 中文字幕人妻一区二| 亚洲午夜久久久久久久久电影院| 亚洲第一在线播放| 欧美午夜精品一区| 精品二区在线观看| 日韩福利视频在线观看| yw193.com尤物在线| 欧美精品在线免费| 少妇视频在线观看| 国产精选久久久久久| 伊人久久一区二区| 黄网站免费久久| 黑人玩弄人妻一区二区三区| 久久综合一区二区| 日韩在线视频网址| 精品久久久中文| 亚洲综合精品国产一区二区三区| 欧美成人video| 成年人在线观看| 欧美高清性猛交| 四虎成人在线| 国产精品国产三级欧美二区| 欧美在线色图| 日本韩国欧美在线观看| 看国产成人h片视频| 欧美深性狂猛ⅹxxx深喉| 最新国产成人在线观看| 99久久久久久久久| 欧美va亚洲va香蕉在线| 3d成人动漫在线| 91sao在线观看国产| 日韩精品视频在线看| 色女孩综合网| 亚洲一区不卡| 免费看91视频| 日韩一区日韩二区| 亚洲第一网站在线观看| 亚洲国产精品成人va在线观看| 在线观看麻豆蜜桃| 欧美亚洲成人网| 亚洲一区二区电影| 人人妻人人澡人人爽精品欧美一区| 久久九九99| 一级黄色片毛片| 亚洲一区二区视频| 国产三级小视频| 日韩在线免费观看视频| 日韩电影大全网站| 久久国产精品久久| 最新亚洲视频| 中文字幕天堂av| 一区二区三区**美女毛片| 91片黄在线观看喷潮| 一区二区三区四区在线观看视频| 麻豆蜜桃在线观看| 国产伦一区二区三区色一情 | 国产欧美自拍视频| 日本少妇一区二区| 自拍偷拍中文字幕| 欧美日韩中文字幕日韩欧美| 人妻一区二区三区四区| 欧美日韩福利视频| 亚洲一区二区三区免费| 国产肉体ⅹxxx137大胆| 国产在线看一区| 欧美国产日韩在线观看成人| 91精品国产综合久久小美女| 麻豆传媒在线完整视频| 91亚洲精品在线| 亚洲国产一成人久久精品| 中文字幕12页| 尤物视频一区二区| 黄片毛片在线看| 91黑丝高跟在线| 在线成人动漫av| 97xxxxx| 久久精品亚洲精品国产欧美| 男人天堂2024| 亚洲深夜福利网站| 久久av日韩| 精品一区二区三区毛片| 国产v综合v亚洲欧| 日韩精品视频免费看| 日韩av在线一区| 国产亚洲一区二区手机在线观看 | 国产69精品久久app免费版| 国产精品免费看久久久香蕉| 欧美激情黄色片| 欧美一区二区三区影院| 午夜欧美视频在线观看| 性xxxx视频| 国产不卡一区二区在线播放| 久久国产电影| 制服下的诱惑暮生| 亚洲成人激情av| 国产视频精品久久| 亚洲a区在线视频| 一区二区三区成人精品| 日韩大陆欧美高清视频区| 91九色在线porn| 亚洲一区二区三区sesese| 99av国产精品欲麻豆| 91国模少妇一区二区三区| 欧美日韩激情一区| 激情图片在线观看高清国产| 久久大片网站| 久久国产日韩欧美精品| 国产无码精品一区二区| 亚洲午夜小视频| 亚洲一区网址| 蜜臀视频一区二区三区| 1区2区3区欧美| 色一情一乱一区二区三区| 国产精品久久久久久久久男| 欧美日韩精选| 无码人妻aⅴ一区二区三区69岛| 日韩一二在线观看| 日韩三级影视| 高清无码一区二区在线观看吞精| 久久免费视频色| 黄频在线免费观看|