Hook.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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 = this._createCompileDelegate("call", "sync");
  13. this.promise = this._promise = this._createCompileDelegate("promise", "promise");
  14. this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async");
  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. _createCompileDelegate(name, type) {
  29. const lazyCompileHook = (...args) => {
  30. this[name] = this._createCall(type);
  31. return this[name](...args);
  32. };
  33. return lazyCompileHook;
  34. }
  35. tap(options, fn) {
  36. if(typeof options === "string")
  37. options = { name: options };
  38. if(typeof options !== "object" || options === null)
  39. throw new Error("Invalid arguments to tap(options: Object, fn: function)");
  40. options = Object.assign({ type: "sync", fn: fn }, options);
  41. if(typeof options.name !== "string" || options.name === "")
  42. throw new Error("Missing name for tap");
  43. options = this._runRegisterInterceptors(options);
  44. this._insert(options);
  45. }
  46. tapAsync(options, fn) {
  47. if(typeof options === "string")
  48. options = { name: options };
  49. if(typeof options !== "object" || options === null)
  50. throw new Error("Invalid arguments to tapAsync(options: Object, fn: function)");
  51. options = Object.assign({ type: "async", fn: fn }, options);
  52. if(typeof options.name !== "string" || options.name === "")
  53. throw new Error("Missing name for tapAsync");
  54. options = this._runRegisterInterceptors(options);
  55. this._insert(options);
  56. }
  57. tapPromise(options, fn) {
  58. if(typeof options === "string")
  59. options = { name: options };
  60. if(typeof options !== "object" || options === null)
  61. throw new Error("Invalid arguments to tapPromise(options: Object, fn: function)");
  62. options = Object.assign({ type: "promise", fn: fn }, options);
  63. if(typeof options.name !== "string" || options.name === "")
  64. throw new Error("Missing name for tapPromise");
  65. options = this._runRegisterInterceptors(options);
  66. this._insert(options);
  67. }
  68. _runRegisterInterceptors(options) {
  69. for(const interceptor of this.interceptors) {
  70. if(interceptor.register) {
  71. const newOptions = interceptor.register(options);
  72. if(newOptions !== undefined)
  73. options = newOptions;
  74. }
  75. }
  76. return options;
  77. }
  78. withOptions(options) {
  79. const mergeOptions = opt => Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
  80. // Prevent creating endless prototype chains
  81. options = Object.assign({}, options, this._withOptions);
  82. const base = this._withOptionsBase || this;
  83. const newHook = Object.create(base);
  84. newHook.tapAsync = (opt, fn) => base.tapAsync(mergeOptions(opt), fn),
  85. newHook.tap = (opt, fn) => base.tap(mergeOptions(opt), fn);
  86. newHook.tapPromise = (opt, fn) => base.tapPromise(mergeOptions(opt), fn);
  87. newHook._withOptions = options;
  88. newHook._withOptionsBase = base;
  89. return newHook;
  90. }
  91. isUsed() {
  92. return this.taps.length > 0 || this.interceptors.length > 0;
  93. }
  94. intercept(interceptor) {
  95. this._resetCompilation();
  96. this.interceptors.push(Object.assign({}, interceptor));
  97. if(interceptor.register) {
  98. for(let i = 0; i < this.taps.length; i++)
  99. this.taps[i] = interceptor.register(this.taps[i]);
  100. }
  101. }
  102. _resetCompilation() {
  103. this.call = this._call;
  104. this.callAsync = this._callAsync;
  105. this.promise = this._promise;
  106. }
  107. _insert(item) {
  108. this._resetCompilation();
  109. let before;
  110. if(typeof item.before === "string")
  111. before = new Set([item.before]);
  112. else if(Array.isArray(item.before)) {
  113. before = new Set(item.before);
  114. }
  115. let stage = 0;
  116. if(typeof item.stage === "number")
  117. stage = item.stage;
  118. let i = this.taps.length;
  119. while(i > 0) {
  120. i--;
  121. const x = this.taps[i];
  122. this.taps[i+1] = x;
  123. const xStage = x.stage || 0;
  124. if(before) {
  125. if(before.has(x.name)) {
  126. before.delete(x.name);
  127. continue;
  128. }
  129. if(before.size > 0) {
  130. continue;
  131. }
  132. }
  133. if(xStage > stage) {
  134. continue;
  135. }
  136. i++;
  137. break;
  138. }
  139. this.taps[i] = item;
  140. }
  141. }
  142. module.exports = Hook;