176 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const path = require('path');
 | |
| const chalk = require('chalk');
 | |
| const os = require('os');
 | |
| const transformErrors = require('./core/transformErrors');
 | |
| const formatErrors = require('./core/formatErrors');
 | |
| const output = require('./output');
 | |
| const utils = require('./utils');
 | |
| 
 | |
| const concat = utils.concat;
 | |
| const uniqueBy = utils.uniqueBy;
 | |
| 
 | |
| const defaultTransformers = [
 | |
|   require('./transformers/babelSyntax'),
 | |
|   require('./transformers/moduleNotFound'),
 | |
|   require('./transformers/esLintError'),
 | |
| ];
 | |
| 
 | |
| const defaultFormatters = [
 | |
|   require('./formatters/moduleNotFound'),
 | |
|   require('./formatters/eslintError'),
 | |
|   require('./formatters/defaultError'),
 | |
| ];
 | |
| 
 | |
| class FriendlyErrorsWebpackPlugin {
 | |
| 
 | |
|   constructor(options) {
 | |
|     options = options || {};
 | |
|     this.compilationSuccessInfo = options.compilationSuccessInfo || {};
 | |
|     this.onErrors = options.onErrors;
 | |
|     this.shouldClearConsole = options.clearConsole == null ? true : Boolean(options.clearConsole);
 | |
|     this.formatters = concat(defaultFormatters, options.additionalFormatters);
 | |
|     this.transformers = concat(defaultTransformers, options.additionalTransformers);
 | |
|     this.previousEndTimes = {};
 | |
|   }
 | |
| 
 | |
|   apply(compiler) {
 | |
| 
 | |
|     const doneFn = stats => {
 | |
|       this.clearConsole();
 | |
| 
 | |
|       const hasErrors = stats.hasErrors();
 | |
|       const hasWarnings = stats.hasWarnings();
 | |
| 
 | |
|       if (!hasErrors && !hasWarnings) {
 | |
|         this.displaySuccess(stats);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (hasErrors) {
 | |
|         this.displayErrors(extractErrorsFromStats(stats, 'errors'), 'error');
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (hasWarnings) {
 | |
|         this.displayErrors(extractErrorsFromStats(stats, 'warnings'), 'warning');
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     const invalidFn = () => {
 | |
|       this.clearConsole();
 | |
|       output.title('info', 'WAIT', 'Compiling...');
 | |
|     };
 | |
| 
 | |
|     if (compiler.hooks) {
 | |
|       const plugin = { name: 'FriendlyErrorsWebpackPlugin' };
 | |
| 
 | |
|       compiler.hooks.done.tap(plugin, doneFn);
 | |
|       compiler.hooks.invalid.tap(plugin, invalidFn);
 | |
|     } else {
 | |
|       compiler.plugin('done', doneFn);
 | |
|       compiler.plugin('invalid', invalidFn);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   clearConsole() {
 | |
|     if (this.shouldClearConsole) {
 | |
|       output.clearConsole();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   displaySuccess(stats) {
 | |
|     const time = isMultiStats(stats) ? this.getMultiStatsCompileTime(stats) : this.getStatsCompileTime(stats);
 | |
|     output.title('success', 'DONE', 'Compiled successfully in ' + time + 'ms');
 | |
| 
 | |
|     if (this.compilationSuccessInfo.messages) {
 | |
|       this.compilationSuccessInfo.messages.forEach(message => output.info(message));
 | |
|     }
 | |
|     if (this.compilationSuccessInfo.notes) {
 | |
|       output.log();
 | |
|       this.compilationSuccessInfo.notes.forEach(note => output.note(note));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   displayErrors(errors, severity) {
 | |
|     const processedErrors = transformErrors(errors, this.transformers);
 | |
| 
 | |
|     const topErrors = getMaxSeverityErrors(processedErrors);
 | |
|     const nbErrors = topErrors.length;
 | |
| 
 | |
|     const subtitle = severity === 'error' ?
 | |
|       `Failed to compile with ${nbErrors} ${severity}${nbErrors === 1 ? '' : 's'}` :
 | |
|       `Compiled with ${nbErrors} ${severity}${nbErrors === 1 ? '' : 's'}`;
 | |
|     output.title(severity, severity.toUpperCase(), subtitle);
 | |
| 
 | |
|     if (this.onErrors) {
 | |
|       this.onErrors(severity, topErrors);
 | |
|     }
 | |
| 
 | |
|     formatErrors(topErrors, this.formatters, severity)
 | |
|       .forEach(chunk => output.log(chunk));
 | |
|   }
 | |
| 
 | |
|   getStatsCompileTime(stats, statsIndex) {
 | |
|     // When we have multi compilations but only one of them is rebuilt, we need to skip the
 | |
|     // unchanged compilers to report the true rebuild time.
 | |
|     if (statsIndex !== undefined) {
 | |
|       if (this.previousEndTimes[statsIndex] === stats.endTime) {
 | |
|         return 0;
 | |
|       }
 | |
| 
 | |
|       this.previousEndTimes[statsIndex] = stats.endTime;
 | |
|     }
 | |
| 
 | |
|     return stats.endTime - stats.startTime;
 | |
|   }
 | |
| 
 | |
|   getMultiStatsCompileTime(stats) {
 | |
|     // Webpack multi compilations run in parallel so using the longest duration.
 | |
|     // https://webpack.github.io/docs/configuration.html#multiple-configurations
 | |
|     return stats.stats
 | |
|       .reduce((time, stats, index) => Math.max(time, this.getStatsCompileTime(stats, index)), 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function extractErrorsFromStats(stats, type) {
 | |
|   if (isMultiStats(stats)) {
 | |
|     const errors = stats.stats
 | |
|       .reduce((errors, stats) => errors.concat(extractErrorsFromStats(stats, type)), []);
 | |
|     // Dedupe to avoid showing the same error many times when multiple
 | |
|     // compilers depend on the same module.
 | |
|     return uniqueBy(errors, error => error.message);
 | |
|   }
 | |
| 
 | |
|   const findErrorsRecursive = (compilation) => {
 | |
|     const errors = compilation[type];
 | |
|     if (errors.length === 0 && compilation.children) {
 | |
|       for (const child of compilation.children) {
 | |
|         errors.push(...findErrorsRecursive(child));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return uniqueBy(errors, error => error.message);
 | |
|   };
 | |
| 
 | |
|   return findErrorsRecursive(stats.compilation);
 | |
| }
 | |
| 
 | |
| function isMultiStats(stats) {
 | |
|   return stats.stats;
 | |
| }
 | |
| 
 | |
| function getMaxSeverityErrors(errors) {
 | |
|   const maxSeverity = getMaxInt(errors, 'severity');
 | |
|   return errors.filter(e => e.severity === maxSeverity);
 | |
| }
 | |
| 
 | |
| function getMaxInt(collection, propertyName) {
 | |
|   return collection.reduce((res, curr) => {
 | |
|     return curr[propertyName] > res ? curr[propertyName] : res;
 | |
|   }, 0)
 | |
| }
 | |
| 
 | |
| module.exports = FriendlyErrorsWebpackPlugin;
 |