index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const path = require("path");
  2. const fs = require("fs");
  3. const chalk = require("chalk");
  4. const { WrappedPlugin, clear } = require("./WrappedPlugin");
  5. const {
  6. getModuleName,
  7. getLoaderNames,
  8. prependLoader,
  9. tap,
  10. } = require("./utils");
  11. const {
  12. getHumanOutput,
  13. getMiscOutput,
  14. getPluginsOutput,
  15. getLoadersOutput,
  16. smpTag,
  17. } = require("./output");
  18. const NS = path.dirname(fs.realpathSync(__filename));
  19. module.exports = class SpeedMeasurePlugin {
  20. constructor(options) {
  21. this.options = options || {};
  22. this.timeEventData = {};
  23. this.smpPluginAdded = false;
  24. this.wrap = this.wrap.bind(this);
  25. this.getOutput = this.getOutput.bind(this);
  26. this.addTimeEvent = this.addTimeEvent.bind(this);
  27. this.apply = this.apply.bind(this);
  28. this.provideLoaderTiming = this.provideLoaderTiming.bind(this);
  29. }
  30. wrap(config) {
  31. if (this.options.disable) return config;
  32. if (Array.isArray(config)) return config.map(this.wrap);
  33. if (typeof config === "function")
  34. return (...args) => this.wrap(config(...args));
  35. config.plugins = (config.plugins || []).map(plugin => {
  36. const pluginName =
  37. Object.keys(this.options.pluginNames || {}).find(
  38. pluginName => plugin === this.options.pluginNames[pluginName]
  39. ) ||
  40. (plugin.constructor && plugin.constructor.name) ||
  41. "(unable to deduce plugin name)";
  42. return new WrappedPlugin(plugin, pluginName, this);
  43. });
  44. if (config.optimization && config.optimization.minimizer) {
  45. config.optimization.minimizer = config.optimization.minimizer.map(
  46. plugin => {
  47. return new WrappedPlugin(plugin, plugin.constructor.name, this);
  48. }
  49. );
  50. }
  51. if (config.module && this.options.granularLoaderData) {
  52. config.module = prependLoader(config.module);
  53. }
  54. if (!this.smpPluginAdded) {
  55. config.plugins = config.plugins.concat(this);
  56. this.smpPluginAdded = true;
  57. }
  58. return config;
  59. }
  60. getOutput() {
  61. const outputObj = {};
  62. if (this.timeEventData.misc)
  63. outputObj.misc = getMiscOutput(this.timeEventData.misc);
  64. if (this.timeEventData.plugins)
  65. outputObj.plugins = getPluginsOutput(this.timeEventData.plugins);
  66. if (this.timeEventData.loaders)
  67. outputObj.loaders = getLoadersOutput(this.timeEventData.loaders);
  68. if (this.options.outputFormat === "json")
  69. return JSON.stringify(outputObj, null, 2);
  70. if (typeof this.options.outputFormat === "function")
  71. return this.options.outputFormat(outputObj);
  72. return getHumanOutput(outputObj, {
  73. verbose: this.options.outputFormat === "humanVerbose",
  74. });
  75. }
  76. addTimeEvent(category, event, eventType, data = {}) {
  77. const allowFailure = data.allowFailure;
  78. delete data.allowFailure;
  79. const tED = this.timeEventData;
  80. if (!tED[category]) tED[category] = {};
  81. if (!tED[category][event]) tED[category][event] = [];
  82. const eventList = tED[category][event];
  83. const curTime = new Date().getTime();
  84. if (eventType === "start") {
  85. data.start = curTime;
  86. eventList.push(data);
  87. } else if (eventType === "end") {
  88. const matchingEvent = eventList.find(e => {
  89. const allowOverwrite = !e.end || !data.fillLast;
  90. const idMatch = e.id !== undefined && e.id === data.id;
  91. const nameMatch =
  92. !data.id && e.name !== undefined && e.name === data.name;
  93. return allowOverwrite && (idMatch || nameMatch);
  94. });
  95. const eventToModify =
  96. matchingEvent || (data.fillLast && eventList.find(e => !e.end));
  97. if (!eventToModify) {
  98. console.error(
  99. "Could not find a matching event to end",
  100. category,
  101. event,
  102. data
  103. );
  104. if (allowFailure) return;
  105. throw new Error("No matching event!");
  106. }
  107. eventToModify.end = curTime;
  108. }
  109. }
  110. apply(compiler) {
  111. if (this.options.disable) return;
  112. tap(compiler, "compile", () => {
  113. this.addTimeEvent("misc", "compile", "start", { watch: false });
  114. });
  115. tap(compiler, "done", () => {
  116. clear();
  117. this.addTimeEvent("misc", "compile", "end", { fillLast: true });
  118. const outputToFile = typeof this.options.outputTarget === "string";
  119. chalk.enabled = !outputToFile;
  120. const output = this.getOutput();
  121. chalk.enabled = true;
  122. if (outputToFile) {
  123. const writeMethod = fs.existsSync(this.options.outputTarget)
  124. ? fs.appendFileSync
  125. : fs.writeFileSync;
  126. writeMethod(this.options.outputTarget, output + "\n");
  127. console.log(
  128. smpTag() + "Outputted timing info to " + this.options.outputTarget
  129. );
  130. } else {
  131. const outputFunc = this.options.outputTarget || console.log;
  132. outputFunc(output);
  133. }
  134. this.timeEventData = {};
  135. });
  136. tap(compiler, "compilation", compilation => {
  137. tap(compilation, "normal-module-loader", loaderContext => {
  138. loaderContext[NS] = this.provideLoaderTiming;
  139. });
  140. tap(compilation, "build-module", module => {
  141. const name = getModuleName(module);
  142. if (name) {
  143. this.addTimeEvent("loaders", "build", "start", {
  144. name,
  145. fillLast: true,
  146. loaders: getLoaderNames(module.loaders),
  147. });
  148. }
  149. });
  150. tap(compilation, "succeed-module", module => {
  151. const name = getModuleName(module);
  152. if (name) {
  153. this.addTimeEvent("loaders", "build", "end", {
  154. name,
  155. fillLast: true,
  156. });
  157. }
  158. });
  159. });
  160. }
  161. provideLoaderTiming(info) {
  162. const infoData = { id: info.id };
  163. if (info.type !== "end") {
  164. infoData.loader = info.loaderName;
  165. infoData.name = info.module;
  166. }
  167. this.addTimeEvent("loaders", "build-specific", info.type, infoData);
  168. }
  169. };