You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Hook.js 4.5KB

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