'use strict'
|
|
const transformErrors = require('./core/transformErrors')
|
const formatErrors = require('./core/formatErrors')
|
const reporters = require('./reporters')
|
const utils = require('./utils')
|
const { titles } = require('./utils/log')
|
|
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')
|
]
|
|
const logLevels = {
|
'INFO': 0,
|
'WARNING': 1,
|
'ERROR': 2,
|
'SILENT': 3
|
}
|
|
class FriendlyErrorsWebpackPlugin {
|
constructor (options) {
|
options = options || {}
|
this.compilationSuccessInfo = options.compilationSuccessInfo || {}
|
this.onErrors = options.onErrors
|
this.shouldClearConsole = options.clearConsole == null ? true : Boolean(options.clearConsole)
|
this.logLevel = logLevels[(options.logLevel || 'info').toUpperCase()]
|
this.formatters = concat(defaultFormatters, options.additionalFormatters)
|
this.transformers = concat(defaultTransformers, options.additionalTransformers)
|
this.previousEndTimes = {}
|
|
let reporter = options.reporter || 'base'
|
if (typeof reporter === 'string') {
|
reporter = new (require(reporters[reporter] || reporter))()
|
}
|
this.reporter = reporter
|
}
|
|
apply (compiler) {
|
const doneFn = stats => {
|
this.clearConsole()
|
|
const hasErrors = stats.hasErrors()
|
const hasWarnings = stats.hasWarnings()
|
|
let cleared = false
|
|
if (!hasErrors && !hasWarnings && this.logLevel < 1) {
|
cleared = this.clearConsole()
|
this.displaySuccess(stats)
|
return
|
}
|
|
if (hasWarnings && this.logLevel < 2) {
|
cleared = this.clearConsole()
|
this.displayErrors(extractErrorsFromStats(stats, 'warnings'), 'warn')
|
}
|
|
if (hasErrors && this.logLevel < 3) {
|
cleared || this.clearConsole()
|
this.displayErrors(extractErrorsFromStats(stats, 'errors'), 'error')
|
}
|
}
|
|
const invalidFn = () => {
|
this.clearConsole()
|
this.reporter.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) {
|
this.reporter.clearConsole()
|
}
|
return true
|
}
|
|
displaySuccess (stats) {
|
const time = isMultiStats(stats) ? this.getMultiStatsCompileTime(stats) : this.getStatsCompileTime(stats)
|
this.reporter.success('DONE', 'Compiled successfully in ' + time + 'ms')
|
|
if (this.compilationSuccessInfo.messages) {
|
this.compilationSuccessInfo.messages.forEach(message => this.reporter.info(message))
|
}
|
if (this.compilationSuccessInfo.notes) {
|
this.reporter.log()
|
this.compilationSuccessInfo.notes.forEach(note => this.reporter.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} ${titles[severity]}s`
|
: `Compiled with ${nbErrors} ${titles[severity]}s`
|
this.reporter[severity](severity.toUpperCase(), subtitle)
|
|
if (this.onErrors) {
|
this.onErrors(severity, topErrors)
|
}
|
|
formatErrors(topErrors, this.formatters, severity)
|
.forEach(chunk => {
|
this.reporter[severity].apply(this.reporter, [].concat(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) {
|
let errors = []
|
if (isMultiStats(stats)) {
|
errors = stats.stats
|
.reduce((errors, stats) => errors.concat(extractErrorsFromStats(stats, type)), [])
|
} else {
|
if (Array.isArray(stats.compilation.children)) {
|
for (const child of stats.compilation.children) {
|
const childStats = child.getStats()
|
errors.push.apply(errors, extractErrorsFromStats(childStats, type))
|
}
|
}
|
errors.push.apply(errors, stats.compilation[type])
|
}
|
// Dedupe to avoid showing the same error many times when multiple
|
// compilers depend on the same module.
|
return uniqueBy(errors, error => error.message)
|
}
|
|
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
|