070a5f316db581984bb6317e99e1eceb77ae3649c232baf151253abe18f709a078cc7bd633ef1927329b75772ade606ff1104a9ead509cf2ef6286d9dd8d7a 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. 'use strict';
  2. const path = require('path');
  3. const chalk = require('chalk');
  4. const os = require('os');
  5. const transformErrors = require('./core/transformErrors');
  6. const formatErrors = require('./core/formatErrors');
  7. const output = require('./output');
  8. const utils = require('./utils');
  9. const concat = utils.concat;
  10. const uniqueBy = utils.uniqueBy;
  11. const defaultTransformers = [
  12. require('./transformers/babelSyntax'),
  13. require('./transformers/moduleNotFound'),
  14. require('./transformers/esLintError'),
  15. ];
  16. const defaultFormatters = [
  17. require('./formatters/moduleNotFound'),
  18. require('./formatters/eslintError'),
  19. require('./formatters/defaultError'),
  20. ];
  21. class FriendlyErrorsWebpackPlugin {
  22. constructor(options) {
  23. options = options || {};
  24. this.compilationSuccessInfo = options.compilationSuccessInfo || {};
  25. this.onErrors = options.onErrors;
  26. this.shouldClearConsole = options.clearConsole == null ? true : Boolean(options.clearConsole);
  27. this.formatters = concat(defaultFormatters, options.additionalFormatters);
  28. this.transformers = concat(defaultTransformers, options.additionalTransformers);
  29. this.previousEndTimes = {};
  30. }
  31. apply(compiler) {
  32. const doneFn = stats => {
  33. this.clearConsole();
  34. const hasErrors = stats.hasErrors();
  35. const hasWarnings = stats.hasWarnings();
  36. if (!hasErrors && !hasWarnings) {
  37. this.displaySuccess(stats);
  38. return;
  39. }
  40. if (hasErrors) {
  41. this.displayErrors(extractErrorsFromStats(stats, 'errors'), 'error');
  42. return;
  43. }
  44. if (hasWarnings) {
  45. this.displayErrors(extractErrorsFromStats(stats, 'warnings'), 'warning');
  46. }
  47. };
  48. const invalidFn = () => {
  49. this.clearConsole();
  50. output.title('info', 'WAIT', 'Compiling...');
  51. };
  52. if (compiler.hooks) {
  53. const plugin = { name: 'FriendlyErrorsWebpackPlugin' };
  54. compiler.hooks.done.tap(plugin, doneFn);
  55. compiler.hooks.invalid.tap(plugin, invalidFn);
  56. } else {
  57. compiler.plugin('done', doneFn);
  58. compiler.plugin('invalid', invalidFn);
  59. }
  60. }
  61. clearConsole() {
  62. if (this.shouldClearConsole) {
  63. output.clearConsole();
  64. }
  65. }
  66. displaySuccess(stats) {
  67. const time = isMultiStats(stats) ? this.getMultiStatsCompileTime(stats) : this.getStatsCompileTime(stats);
  68. output.title('success', 'DONE', 'Compiled successfully in ' + time + 'ms');
  69. if (this.compilationSuccessInfo.messages) {
  70. this.compilationSuccessInfo.messages.forEach(message => output.info(message));
  71. }
  72. if (this.compilationSuccessInfo.notes) {
  73. output.log();
  74. this.compilationSuccessInfo.notes.forEach(note => output.note(note));
  75. }
  76. }
  77. displayErrors(errors, severity) {
  78. const processedErrors = transformErrors(errors, this.transformers);
  79. const topErrors = getMaxSeverityErrors(processedErrors);
  80. const nbErrors = topErrors.length;
  81. const subtitle = severity === 'error' ?
  82. `Failed to compile with ${nbErrors} ${severity}${nbErrors === 1 ? '' : 's'}` :
  83. `Compiled with ${nbErrors} ${severity}${nbErrors === 1 ? '' : 's'}`;
  84. output.title(severity, severity.toUpperCase(), subtitle);
  85. if (this.onErrors) {
  86. this.onErrors(severity, topErrors);
  87. }
  88. formatErrors(topErrors, this.formatters, severity)
  89. .forEach(chunk => output.log(chunk));
  90. }
  91. getStatsCompileTime(stats, statsIndex) {
  92. // When we have multi compilations but only one of them is rebuilt, we need to skip the
  93. // unchanged compilers to report the true rebuild time.
  94. if (statsIndex !== undefined) {
  95. if (this.previousEndTimes[statsIndex] === stats.endTime) {
  96. return 0;
  97. }
  98. this.previousEndTimes[statsIndex] = stats.endTime;
  99. }
  100. return stats.endTime - stats.startTime;
  101. }
  102. getMultiStatsCompileTime(stats) {
  103. // Webpack multi compilations run in parallel so using the longest duration.
  104. // https://webpack.github.io/docs/configuration.html#multiple-configurations
  105. return stats.stats
  106. .reduce((time, stats, index) => Math.max(time, this.getStatsCompileTime(stats, index)), 0);
  107. }
  108. }
  109. function extractErrorsFromStats(stats, type) {
  110. if (isMultiStats(stats)) {
  111. const errors = stats.stats
  112. .reduce((errors, stats) => errors.concat(extractErrorsFromStats(stats, type)), []);
  113. // Dedupe to avoid showing the same error many times when multiple
  114. // compilers depend on the same module.
  115. return uniqueBy(errors, error => error.message);
  116. }
  117. const findErrorsRecursive = (compilation) => {
  118. const errors = compilation[type];
  119. if (errors.length === 0 && compilation.children) {
  120. for (const child of compilation.children) {
  121. errors.push(...findErrorsRecursive(child));
  122. }
  123. }
  124. return uniqueBy(errors, error => error.message);
  125. };
  126. return findErrorsRecursive(stats.compilation);
  127. }
  128. function isMultiStats(stats) {
  129. return stats.stats;
  130. }
  131. function getMaxSeverityErrors(errors) {
  132. const maxSeverity = getMaxInt(errors, 'severity');
  133. return errors.filter(e => e.severity === maxSeverity);
  134. }
  135. function getMaxInt(collection, propertyName) {
  136. return collection.reduce((res, curr) => {
  137. return curr[propertyName] > res ? curr[propertyName] : res;
  138. }, 0)
  139. }
  140. module.exports = FriendlyErrorsWebpackPlugin;