ProgressPlugin.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const validateOptions = require("schema-utils");
  7. const schema = require("../schemas/plugins/ProgressPlugin.json");
  8. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
  9. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
  10. const createDefaultHandler = profile => {
  11. let lineCaretPosition = 0;
  12. let lastMessage = "";
  13. let lastState;
  14. let lastStateTime;
  15. const defaultHandler = (percentage, msg, ...args) => {
  16. let state = msg;
  17. const details = args;
  18. if (percentage < 1) {
  19. percentage = Math.floor(percentage * 100);
  20. msg = `${percentage}% ${msg}`;
  21. if (percentage < 100) {
  22. msg = ` ${msg}`;
  23. }
  24. if (percentage < 10) {
  25. msg = ` ${msg}`;
  26. }
  27. for (let detail of details) {
  28. if (!detail) continue;
  29. if (detail.length > 40) {
  30. detail = `...${detail.substr(detail.length - 39)}`;
  31. }
  32. msg += ` ${detail}`;
  33. }
  34. }
  35. if (profile) {
  36. state = state.replace(/^\d+\/\d+\s+/, "");
  37. if (percentage === 0) {
  38. lastState = null;
  39. lastStateTime = Date.now();
  40. } else if (state !== lastState || percentage === 1) {
  41. const now = Date.now();
  42. if (lastState) {
  43. const stateMsg = `${now - lastStateTime}ms ${lastState}`;
  44. goToLineStart(stateMsg);
  45. process.stderr.write(stateMsg + "\n");
  46. lineCaretPosition = 0;
  47. }
  48. lastState = state;
  49. lastStateTime = now;
  50. }
  51. }
  52. if (lastMessage !== msg) {
  53. goToLineStart(msg);
  54. process.stderr.write(msg);
  55. lastMessage = msg;
  56. }
  57. };
  58. const goToLineStart = nextMessage => {
  59. let str = "";
  60. for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
  61. str += "\b \b";
  62. }
  63. for (var i = 0; i < lineCaretPosition; i++) {
  64. str += "\b";
  65. }
  66. lineCaretPosition = nextMessage.length;
  67. if (str) process.stderr.write(str);
  68. };
  69. return defaultHandler;
  70. };
  71. class ProgressPlugin {
  72. /**
  73. * @param {ProgressPluginArgument} options options
  74. */
  75. constructor(options) {
  76. if (typeof options === "function") {
  77. options = {
  78. handler: options
  79. };
  80. }
  81. options = options || {};
  82. validateOptions(schema, options, "Progress Plugin");
  83. options = Object.assign({}, ProgressPlugin.defaultOptions, options);
  84. this.profile = options.profile;
  85. this.handler = options.handler;
  86. this.modulesCount = options.modulesCount;
  87. this.showEntries = options.entries;
  88. this.showModules = options.modules;
  89. this.showActiveModules = options.activeModules;
  90. }
  91. apply(compiler) {
  92. const { modulesCount } = this;
  93. const handler = this.handler || createDefaultHandler(this.profile);
  94. const showEntries = this.showEntries;
  95. const showModules = this.showModules;
  96. const showActiveModules = this.showActiveModules;
  97. if (compiler.compilers) {
  98. const states = new Array(compiler.compilers.length);
  99. compiler.compilers.forEach((compiler, idx) => {
  100. new ProgressPlugin((p, msg, ...args) => {
  101. states[idx] = [p, msg, ...args];
  102. handler(
  103. states
  104. .map(state => (state && state[0]) || 0)
  105. .reduce((a, b) => a + b) / states.length,
  106. `[${idx}] ${msg}`,
  107. ...args
  108. );
  109. }).apply(compiler);
  110. });
  111. } else {
  112. let lastModulesCount = 0;
  113. let lastEntriesCount = 0;
  114. let moduleCount = modulesCount;
  115. let entriesCount = 1;
  116. let doneModules = 0;
  117. let doneEntries = 0;
  118. const activeModules = new Set();
  119. let lastActiveModule = "";
  120. const update = () => {
  121. const percentByModules =
  122. doneModules / Math.max(lastModulesCount, moduleCount);
  123. const percentByEntries =
  124. doneEntries / Math.max(lastEntriesCount, entriesCount);
  125. const items = [
  126. 0.1 + Math.max(percentByModules, percentByEntries) * 0.6,
  127. "building"
  128. ];
  129. if (showEntries) {
  130. items.push(`${doneEntries}/${entriesCount} entries`);
  131. }
  132. if (showModules) {
  133. items.push(`${doneModules}/${moduleCount} modules`);
  134. }
  135. if (showActiveModules) {
  136. items.push(`${activeModules.size} active`);
  137. items.push(lastActiveModule);
  138. }
  139. handler(...items);
  140. };
  141. const moduleAdd = module => {
  142. moduleCount++;
  143. if (showActiveModules) {
  144. const ident = module.identifier();
  145. if (ident) {
  146. activeModules.add(ident);
  147. lastActiveModule = ident;
  148. }
  149. }
  150. update();
  151. };
  152. const entryAdd = (entry, name) => {
  153. entriesCount++;
  154. update();
  155. };
  156. const moduleDone = module => {
  157. doneModules++;
  158. if (showActiveModules) {
  159. const ident = module.identifier();
  160. if (ident) {
  161. activeModules.delete(ident);
  162. if (lastActiveModule === ident) {
  163. lastActiveModule = "";
  164. for (const m of activeModules) {
  165. lastActiveModule = m;
  166. }
  167. }
  168. }
  169. }
  170. update();
  171. };
  172. const entryDone = (entry, name) => {
  173. doneEntries++;
  174. update();
  175. };
  176. compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
  177. if (compilation.compiler.isChild()) return;
  178. lastModulesCount = moduleCount;
  179. lastEntriesCount = entriesCount;
  180. moduleCount = entriesCount = 0;
  181. doneModules = doneEntries = 0;
  182. handler(0, "compiling");
  183. compilation.hooks.buildModule.tap("ProgressPlugin", moduleAdd);
  184. compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
  185. compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);
  186. compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
  187. compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
  188. compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);
  189. const hooks = {
  190. finishModules: "finish module graph",
  191. seal: "sealing",
  192. beforeChunks: "chunk graph",
  193. afterChunks: "after chunk graph",
  194. optimizeDependenciesBasic: "basic dependencies optimization",
  195. optimizeDependencies: "dependencies optimization",
  196. optimizeDependenciesAdvanced: "advanced dependencies optimization",
  197. afterOptimizeDependencies: "after dependencies optimization",
  198. optimize: "optimizing",
  199. optimizeModulesBasic: "basic module optimization",
  200. optimizeModules: "module optimization",
  201. optimizeModulesAdvanced: "advanced module optimization",
  202. afterOptimizeModules: "after module optimization",
  203. optimizeChunksBasic: "basic chunk optimization",
  204. optimizeChunks: "chunk optimization",
  205. optimizeChunksAdvanced: "advanced chunk optimization",
  206. afterOptimizeChunks: "after chunk optimization",
  207. optimizeTree: "module and chunk tree optimization",
  208. afterOptimizeTree: "after module and chunk tree optimization",
  209. optimizeChunkModulesBasic: "basic chunk modules optimization",
  210. optimizeChunkModules: "chunk modules optimization",
  211. optimizeChunkModulesAdvanced: "advanced chunk modules optimization",
  212. afterOptimizeChunkModules: "after chunk modules optimization",
  213. reviveModules: "module reviving",
  214. optimizeModuleOrder: "module order optimization",
  215. advancedOptimizeModuleOrder: "advanced module order optimization",
  216. beforeModuleIds: "before module ids",
  217. moduleIds: "module ids",
  218. optimizeModuleIds: "module id optimization",
  219. afterOptimizeModuleIds: "module id optimization",
  220. reviveChunks: "chunk reviving",
  221. optimizeChunkOrder: "chunk order optimization",
  222. beforeChunkIds: "before chunk ids",
  223. optimizeChunkIds: "chunk id optimization",
  224. afterOptimizeChunkIds: "after chunk id optimization",
  225. recordModules: "record modules",
  226. recordChunks: "record chunks",
  227. beforeHash: "hashing",
  228. contentHash: "content hashing",
  229. afterHash: "after hashing",
  230. recordHash: "record hash",
  231. beforeModuleAssets: "module assets processing",
  232. beforeChunkAssets: "chunk assets processing",
  233. additionalChunkAssets: "additional chunk assets processing",
  234. record: "recording",
  235. additionalAssets: "additional asset processing",
  236. optimizeChunkAssets: "chunk asset optimization",
  237. afterOptimizeChunkAssets: "after chunk asset optimization",
  238. optimizeAssets: "asset optimization",
  239. afterOptimizeAssets: "after asset optimization",
  240. afterSeal: "after seal"
  241. };
  242. const numberOfHooks = Object.keys(hooks).length;
  243. Object.keys(hooks).forEach((name, idx) => {
  244. const title = hooks[name];
  245. const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
  246. compilation.hooks[name].intercept({
  247. name: "ProgressPlugin",
  248. context: true,
  249. call: () => {
  250. handler(percentage, title);
  251. },
  252. tap: (context, tap) => {
  253. if (context) {
  254. // p is percentage from 0 to 1
  255. // args is any number of messages in a hierarchical matter
  256. context.reportProgress = (p, ...args) => {
  257. handler(percentage, title, tap.name, ...args);
  258. };
  259. }
  260. handler(percentage, title, tap.name);
  261. }
  262. });
  263. });
  264. });
  265. compiler.hooks.emit.intercept({
  266. name: "ProgressPlugin",
  267. context: true,
  268. call: () => {
  269. handler(0.95, "emitting");
  270. },
  271. tap: (context, tap) => {
  272. if (context) {
  273. context.reportProgress = (p, ...args) => {
  274. handler(0.95, "emitting", tap.name, ...args);
  275. };
  276. }
  277. handler(0.95, "emitting", tap.name);
  278. }
  279. });
  280. compiler.hooks.afterEmit.intercept({
  281. name: "ProgressPlugin",
  282. context: true,
  283. call: () => {
  284. handler(0.98, "after emitting");
  285. },
  286. tap: (context, tap) => {
  287. if (context) {
  288. context.reportProgress = (p, ...args) => {
  289. handler(0.98, "after emitting", tap.name, ...args);
  290. };
  291. }
  292. handler(0.98, "after emitting", tap.name);
  293. }
  294. });
  295. compiler.hooks.done.tap("ProgressPlugin", () => {
  296. handler(1, "");
  297. });
  298. }
  299. }
  300. }
  301. ProgressPlugin.defaultOptions = {
  302. profile: false,
  303. modulesCount: 500,
  304. modules: true,
  305. activeModules: true,
  306. // TODO webpack 5 default this to true
  307. entries: false
  308. };
  309. module.exports = ProgressPlugin;