This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
@@ -0,0 +1,65 @@
'use strict'
const ErrorStackParser = require('error-stack-parser')
const RequestShortener = require('webpack/lib/RequestShortener')
// TODO: allow the location to be customized in options
const requestShortener = new RequestShortener(process.cwd())
/*
This logic is mostly duplicated from webpack/lib/Stats.js#toJson()
See: https://github.com/webpack/webpack/blob/2f618e733aab4755deb42e9d8e859609005607c0/lib/Stats.js#L89
*/
function extractError (e) {
return {
message: e.message,
file: getFile(e),
origin: getOrigin(e),
name: e.name,
severity: 0,
webpackError: e,
originalStack: getOriginalErrorStack(e)
}
}
function getOriginalErrorStack (e) {
while (e.error != null) {
e = e.error
}
if (e.stack) {
return ErrorStackParser.parse(e)
}
return []
}
function getFile (e) {
if (e.file) {
return e.file
} else if (e.module && e.module.readableIdentifier && typeof e.module.readableIdentifier === 'function') {
return e.module.readableIdentifier(requestShortener)
}
}
function getOrigin (e) {
let origin = ''
if (e.dependencies && e.origin) {
origin += '\n @ ' + e.origin.readableIdentifier(requestShortener)
e.dependencies.forEach(function (dep) {
if (!dep.loc) return
if (typeof dep.loc === 'string') return
if (!dep.loc.start) return
if (!dep.loc.end) return
origin += ' ' + dep.loc.start.line + ':' + dep.loc.start.column + '-' +
(dep.loc.start.line !== dep.loc.end.line ? dep.loc.end.line + ':' : '') + dep.loc.end.column
})
var current = e.origin
while (current.issuer && typeof current.issuer.readableIdentifier === 'function') {
current = current.issuer
origin += '\n @ ' + current.readableIdentifier(requestShortener)
}
}
return origin
}
module.exports = extractError
@@ -0,0 +1,18 @@
'use strict'
/**
* Applies formatters to all AnnotatedErrors.
*
* A formatter has the following signature: FormattedError => Array<String>.
* It takes a formatted error produced by a transformer and returns a list
* of log statements to print.
*
*/
function formatErrors (errors, formatters, errorType) {
const format = (formatter) => formatter(errors, errorType) || []
const flatten = (accum, curr) => accum.concat(curr)
return formatters.map(format).reduce(flatten, [])
}
module.exports = formatErrors
@@ -0,0 +1,34 @@
'use strict'
const extractError = require('./extractWebpackError')
/**
* Applies all transformers to all errors and returns "annotated"
* errors.
*
* Each transformer should have the following signature WebpackError => AnnotatedError
*
* A WebpackError has the following fields:
* - message
* - file
* - origin
* - name
* - severity
* - webpackError (original error)
*
* An AnnotatedError should be an extension (Object.assign) of the WebpackError
* and add whatever information is convenient for formatting.
* In particular, they should have a 'priority' field.
*
* The plugin will only display errors having maximum priority at the same time.
*
* If they don't have a 'type' field, the will be handled by the default formatter.
*/
function processErrors (errors, transformers) {
const transform = (error, transformer) => transformer(error)
const applyTransformations = (error) => transformers.reduce(transform, error)
return errors.map(extractError).map(applyTransformations)
}
module.exports = processErrors
@@ -0,0 +1,42 @@
'use strict'
const concat = require('../utils').concat
function displayError (severity, error) {
const errors = concat(
error.message,
(error.origin ? error.origin : undefined),
'',
error.infos
)
errors.unshift([severity, removeLoaders(error.file) || error.name])
return errors
}
function removeLoaders (file) {
if (!file) {
return ''
}
const split = file.split('!')
const filePath = split[split.length - 1]
return `in ${filePath}`
}
function isDefaultError (error) {
return !error.type
}
/**
* Format errors without a type
*/
function format (errors, type) {
return errors
.filter(isDefaultError)
.reduce((accum, error) => (
accum.concat(displayError(type, error))
), [])
}
module.exports = format
@@ -0,0 +1,31 @@
'use strict'
const concat = require('../utils').concat
const chalk = require('chalk')
const infos = [
'You may use special comments to disable some warnings.',
'Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.',
'Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'
]
function displayError (error) {
return [error.message, '']
}
function format (errors, type) {
const lintErrors = errors.filter(e => e.type === 'lint-error')
if (lintErrors.length > 0) {
const flatten = (accum, curr) => accum.concat(curr)
return concat(
lintErrors
.map(error => displayError(error))
.reduce(flatten, []),
infos
)
}
return []
}
module.exports = format
@@ -0,0 +1,91 @@
'use strict'
const concat = require('../utils').concat
function isRelative (module) {
return module.startsWith('./') || module.startsWith('../')
}
function formatFileList (files) {
const length = files.length
if (!length) return ''
return ` in ${files[0]}${files[1] ? `, ${files[1]}` : ''}${length > 2 ? ` and ${length - 2} other${length === 3 ? '' : 's'}` : ''}`
}
function formatGroup (group) {
const files = group.errors.map(e => e.file).filter(Boolean)
return `* ${group.module}${formatFileList(files)}`
}
function forgetToInstall (missingDependencies) {
const moduleNames = missingDependencies.map(missingDependency => missingDependency.module)
if (missingDependencies.length === 1) {
return `To install it, you can run: npm install --save ${moduleNames.join(' ')}`
}
return `To install them, you can run: npm install --save ${moduleNames.join(' ')}`
}
function dependenciesNotFound (dependencies) {
if (dependencies.length === 0) return
return concat(
dependencies.length === 1 ? 'This dependency was not found:' : 'These dependencies were not found:',
'',
dependencies.map(formatGroup),
'',
forgetToInstall(dependencies)
)
}
function relativeModulesNotFound (modules) {
if (modules.length === 0) return
return concat(
modules.length === 1 ? 'This relative module was not found:' : 'These relative modules were not found:',
'',
modules.map(formatGroup)
)
}
function groupModules (errors) {
const missingModule = new Map()
errors.forEach((error) => {
if (!missingModule.has(error.module)) {
missingModule.set(error.module, [])
}
missingModule.get(error.module).push(error)
})
return Array.from(missingModule.keys()).map(module => ({
module: module,
relative: isRelative(module),
errors: missingModule.get(module)
}))
}
function formatErrors (errors) {
if (errors.length === 0) {
return []
}
const groups = groupModules(errors)
const dependencies = groups.filter(group => !group.relative)
const relativeModules = groups.filter(group => group.relative)
return concat(
dependenciesNotFound(dependencies),
dependencies.length && relativeModules.length ? ['', ''] : null,
relativeModulesNotFound(relativeModules)
)
}
function format (errors) {
return formatErrors(errors.filter((e) => (
e.type === 'module-not-found'
)))
}
module.exports = format
@@ -0,0 +1,188 @@
'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
@@ -0,0 +1,63 @@
'use strict'
const { colors, formatTitle, formatText, clearConsole } = require('../utils/log')
const chalk = require('chalk')
const stringWidth = require('string-width')
class BaseReporter {
constructor () {
this.enabled = true
this.initLevels()
}
enable () {
this.enabled = true
}
log () {
if (this.enabled) {
console.log.apply(console, arguments)
}
}
initLevels () {
for (const level of Object.keys(colors)) {
this[level] = (title, message) => {
if (!this.enabled) return
if (message === undefined) {
message = title
this.log(message)
return
}
title = formatTitle(level, title)
message = formatText(level, message)
if (process.env.NODE_ENV !== 'test') {
message = this.appendTimestamp(title, message)
}
this.log(title, message)
this.log()
}
}
}
appendTimestamp (title, message) {
// Make timestamp appear at the end of the line
const line = `${title} ${message}`
const dateString = chalk.grey(new Date().toLocaleTimeString())
let logSpace = process.stdout.columns - stringWidth(line) - stringWidth(dateString)
if (logSpace <= 0) {
logSpace = 10
}
return `${message}${' '.repeat(logSpace)}${dateString}`
}
clearConsole () {
if (this.enabled) {
clearConsole()
}
}
}
module.exports = BaseReporter
@@ -0,0 +1,45 @@
'use strict'
const { colors, clearConsole } = require('../utils/log')
const consola = require('consola')
class ConsolaReporter {
constructor () {
this.enabled = true
this.consola = consola.withTag('friendly-errors')
this.initLevels()
}
enable () {
this.enabled = true
}
log () {
if (this.enabled) {
this.consola.log.apply(this.consola, arguments)
}
}
initLevels () {
for (const level of Object.keys(colors)) {
this[level] = (title, message) => {
if (!this.enabled) return
if (title === 'WAIT') return
if (message === undefined) {
this.consola.log(title)
return
}
(this.consola[level] || this.consola.log)(message)
}
}
}
clearConsole () {
if (this.enabled) {
clearConsole()
}
}
}
module.exports = ConsolaReporter
@@ -0,0 +1,6 @@
const { resolve } = require('path')
module.exports = {
base: resolve(__dirname, 'base'),
consola: resolve(__dirname, 'consola')
}
@@ -0,0 +1,38 @@
'use strict'
/**
* This will be removed in next versions as it is not handled in the babel-loader
* See: https://github.com/geowarin/friendly-errors-webpack-plugin/issues/2
*/
function cleanStackTrace (message) {
return message
.replace(/^\s*at\s.*:\d+:\d+[\s)]*\n/gm, '') // at ... ...:x:y
}
function cleanMessage (message) {
return message
// match until the last semicolon followed by a space
// this should match
// linux => "(SyntaxError: )Unexpected token (5:11)"
// windows => "(SyntaxError: C:/projects/index.js: )Unexpected token (5:11)"
.replace(/^Module build failed.*:(\sSyntaxError:(.*:)?)?\s/, 'Syntax Error: ')
}
function isBabelSyntaxError (e) {
return e.name === 'ModuleBuildError' &&
e.message.indexOf('SyntaxError') >= 0
}
function transform (error) {
if (isBabelSyntaxError(error)) {
return Object.assign({}, error, {
message: cleanStackTrace(cleanMessage(error.message) + '\n'),
severity: 1000,
name: 'Syntax Error'
})
}
return error
}
module.exports = transform
@@ -0,0 +1,19 @@
'use strict'
function isEslintError (e) {
return e.originalStack
.some(stackframe => stackframe.fileName && stackframe.fileName.indexOf('eslint-loader') > 0)
}
function transform (error) {
if (isEslintError(error)) {
return Object.assign({}, error, {
name: 'Lint error',
type: 'lint-error'
})
}
return error
}
module.exports = transform
@@ -0,0 +1,30 @@
'use strict'
const TYPE = 'module-not-found'
function isModuleNotFoundError (e) {
const webpackError = e.webpackError || {}
return webpackError.dependencies &&
webpackError.dependencies.length > 0 &&
e.name === 'ModuleNotFoundError' &&
e.message.indexOf('Module not found') === 0
}
function transform (error) {
const webpackError = error.webpackError
if (isModuleNotFoundError(error)) {
const dependency = webpackError.dependencies[0]
const module = dependency.userRequest || dependency.request
return Object.assign({}, error, {
message: `Module not found ${module}`,
type: TYPE,
severity: 900,
module,
name: 'Module not found'
})
}
return error
}
module.exports = transform
+31
View File
@@ -0,0 +1,31 @@
'use strict'
/**
* Concat and flattens non-null values.
* Ex: concat(1, undefined, 2, [3, 4]) = [1, 2, 3, 4]
*/
function concat () {
const args = Array.from(arguments).filter(e => e != null)
const baseArray = Array.isArray(args[0]) ? args[0] : [args[0]]
return Array.prototype.concat.apply(baseArray, args.slice(1))
}
/**
* Dedupes array based on criterion returned from iteratee function.
* Ex: uniqueBy(
* [{ id: 1 }, { id: 1 }, { id: 2 }],
* val => val.id
* ) = [{ id: 1 }, { id: 2 }]
*/
function uniqueBy (arr, fun) {
const seen = {}
return arr.filter(el => {
const e = fun(el)
return !(e in seen) && (seen[e] = 1)
})
}
module.exports = {
concat: concat,
uniqueBy: uniqueBy
}
+59
View File
@@ -0,0 +1,59 @@
'use strict'
const chalk = require('chalk')
const readline = require('readline')
const colors = {
success: 'green',
info: 'blue',
note: 'white',
warn: 'yellow',
error: 'red'
}
const titles = {
success: 'success',
info: 'info',
note: 'note',
warn: 'warning',
error: 'error'
}
function bgColor (level) {
const color = textColor(level)
return 'bg' + capitalizeFirstLetter(color)
}
function textColor (level) {
return colors[level.toLowerCase()] || 'red'
}
function capitalizeFirstLetter (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
function formatTitle (severity, title) {
return chalk[bgColor(severity)].black('', title, '')
}
function formatText (severity, message) {
return chalk[textColor(severity)](message)
}
function clearConsole () {
if (process.stdout.isTTY) {
// Fill screen with blank lines. Then move to 0 (beginning of visible part) and clear it
const blank = '\n'.repeat(process.stdout.rows)
console.log(blank)
readline.cursorTo(process.stdout, 0, 0)
readline.clearScreenDown(process.stdout)
}
}
module.exports = {
colors,
titles,
formatText,
formatTitle,
clearConsole
}