Hook.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const deprecateContext = util.deprecate(() => {},
  8. "Hook.context is deprecated and will be removed");
  9. const CALL_DELEGATE = function(...args) {
  10. this.call = this._createCall("sync");
  11. return this.call(...args);
  12. };
  13. const CALL_ASYNC_DELEGATE = function(...args) {
  14. this.callAsync = this._createCall("async");
  15. return this.callAsync(...args);
  16. };
  17. const PROMISE_DELEGATE = function(...args) {
  18. this.promise = this._createCall("promise");
  19. return this.promise(...args);
  20. };
  21. class Hook {
  22. constructor(args = [], name = undefined) {
  23. this._args = args;
  24. this.name = name;
  25. this.taps = [];
  26. this.interceptors = [];
  27. this._call = CALL_DELEGATE;
  28. this.call = CALL_DELEGATE;
  29. this._callAsync = CALL_ASYNC_DELEGATE;
  30. this.callAsync = CALL_ASYNC_DELEGATE;
  31. this._promise = PROMISE_DELEGATE;
  32. this.promise = PROMISE_DELEGATE;
  33. this._x = undefined;
  34. this.compile = this.compile;
  35. this.tap = this.tap;
  36. this.tapAsync = this.tapAsync;
  37. this.tapPromise = this.tapPromise;
  38. }
  39. compile(options) {
  40. throw new Error("Abstract: should be overridden");
  41. }
  42. _createCall(type) {
  43. return this.compile({
  44. taps: this.taps,
  45. interceptors: this.interceptors,
  46. args: this._args,
  47. type: type
  48. });
  49. }
  50. _tap(type, options, fn) {
  51. if (typeof options === "string") {
  52. options = {
  53. name: options.trim()
  54. };
  55. } else if (typeof options !== "object" || options === null) {
  56. throw new Error("Invalid tap options");
  57. }
  58. if (typeof options.name !== "string" || options.name === "") {
  59. throw new Error("Missing name for tap");
  60. }
  61. if (typeof options.context !== "undefined") {
  62. deprecateContext();
  63. }
  64. options = Object.assign({ type, fn }, options);
  65. options = this._runRegisterInterceptors(options);
  66. this._insert(options);
  67. }
  68. tap(options, fn) {
  69. this._tap("sync", options, fn);
  70. }
  71. tapAsync(options, fn) {
  72. this._tap("async", options, fn);
  73. }
  74. tapPromise(options, fn) {
  75. this._tap("promise", options, fn);
  76. }
  77. _runRegisterInterceptors(options) {
  78. for (const interceptor of this.interceptors) {
  79. if (interceptor.register) {
  80. const newOptions = interceptor.register(options);
  81. if (newOptions !== undefined) {
  82. options = newOptions;
  83. }
  84. }
  85. }
  86. return options;
  87. }
  88. withOptions(options) {
  89. const mergeOptions = opt =>
  90. Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
  91. return {
  92. name: this.name,
  93. tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
  94. tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
  95. tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
  96. intercept: interceptor => this.intercept(interceptor),
  97. isUsed: () => this.isUsed(),
  98. withOptions: opt => this.withOptions(mergeOptions(opt))
  99. };
  100. }
  101. isUsed() {
  102. return this.taps.length > 0 || this.interceptors.length > 0;
  103. }
  104. intercept(interceptor) {
  105. this._resetCompilation();
  106. this.interceptors.push(Object.assign({}, interceptor));
  107. if (interceptor.register) {
  108. for (let i = 0; i < this.taps.length; i++) {
  109. this.taps[i] = interceptor.register(this.taps[i]);
  110. }
  111. }
  112. }
  113. _resetCompilation() {
  114. this.call = this._call;
  115. this.callAsync = this._callAsync;
  116. this.promise = this._promise;
  117. }
  118. _insert(item) {
  119. this._resetCompilation();
  120. let before;
  121. if (typeof item.before === "string") {
  122. before = new Set([item.before]);
  123. } else if (Array.isArray(item.before)) {
  124. before = new Set(item.before);
  125. }
  126. let stage = 0;
  127. if (typeof item.stage === "number") {
  128. stage = item.stage;
  129. }
  130. let i = this.taps.length;
  131. while (i > 0) {
  132. i--;
  133. const x = this.taps[i];
  134. this.taps[i + 1] = x;
  135. const xStage = x.stage || 0;
  136. if (before) {
  137. if (before.has(x.name)) {
  138. before.delete(x.name);
  139. continue;
  140. }
  141. if (before.size > 0) {
  142. continue;
  143. }
  144. }
  145. if (xStage > stage) {
  146. continue;
  147. }
  148. i++;
  149. break;
  150. }
  151. this.taps[i] = item;
  152. }
  153. }
  154. Object.setPrototypeOf(Hook.prototype, null);
  155. module.exports = Hook;