chushihua
This commit is contained in:
+65
@@ -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;
|
||||
+18
@@ -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;
|
||||
+34
@@ -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;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const concat = require('../utils').concat;
|
||||
const formatTitle = require('../utils/colors').formatTitle;
|
||||
|
||||
function displayError(severity, error) {
|
||||
const baseError = formatTitle(severity, severity);
|
||||
|
||||
return concat(
|
||||
`${baseError} ${removeLoaders(error.file)}`,
|
||||
'',
|
||||
error.message,
|
||||
(error.origin ? error.origin : undefined),
|
||||
'',
|
||||
error.infos
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
+31
@@ -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;
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
'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;
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
'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);
|
||||
}
|
||||
|
||||
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 = getCompileTime(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}s` :
|
||||
`Compiled with ${nbErrors} ${severity}s`;
|
||||
output.title(severity, severity.toUpperCase(), subtitle);
|
||||
|
||||
if (this.onErrors) {
|
||||
this.onErrors(severity, topErrors);
|
||||
}
|
||||
|
||||
formatErrors(topErrors, this.formatters, severity)
|
||||
.forEach(chunk => output.log(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return stats.compilation[type];
|
||||
}
|
||||
|
||||
function getCompileTime(stats) {
|
||||
if (isMultiStats(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) => Math.max(time, getCompileTime(stats)), 0);
|
||||
}
|
||||
return stats.endTime - stats.startTime;
|
||||
}
|
||||
|
||||
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;
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
'use strict';
|
||||
|
||||
const colors = require('./utils/colors');
|
||||
const chalk = require('chalk');
|
||||
const stringWidth = require('string-width');
|
||||
const readline = require('readline');
|
||||
|
||||
class Debugger {
|
||||
|
||||
constructor () {
|
||||
this.enabled = true;
|
||||
this.capturing = false;
|
||||
this.capturedMessages = [];
|
||||
}
|
||||
|
||||
enable () {
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
capture () {
|
||||
this.enabled = true;
|
||||
this.capturing = true;
|
||||
}
|
||||
|
||||
endCapture () {
|
||||
this.enabled = false;
|
||||
this.capturing = false;
|
||||
this.capturedMessages = [];
|
||||
}
|
||||
|
||||
log () {
|
||||
if (this.enabled) {
|
||||
this.captureConsole(Array.from(arguments), console.log);
|
||||
}
|
||||
}
|
||||
|
||||
info (message) {
|
||||
if (this.enabled) {
|
||||
const titleFormatted = colors.formatTitle('info', 'I');
|
||||
this.log(titleFormatted, message);
|
||||
}
|
||||
}
|
||||
|
||||
note (message) {
|
||||
if (this.enabled) {
|
||||
const titleFormatted = colors.formatTitle('note', 'N');
|
||||
this.log(titleFormatted, message);
|
||||
}
|
||||
}
|
||||
|
||||
title (severity, title, subtitle) {
|
||||
if (this.enabled) {
|
||||
const date = new Date();
|
||||
const dateString = chalk.grey(date.toLocaleTimeString());
|
||||
const titleFormatted = colors.formatTitle(severity, title);
|
||||
const subTitleFormatted = colors.formatText(severity, subtitle);
|
||||
const message = `${titleFormatted} ${subTitleFormatted}`
|
||||
|
||||
// In test environment we don't include timestamp
|
||||
if(process.env.NODE_ENV === 'test') {
|
||||
this.log(message);
|
||||
this.log();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make timestamp appear at the end of the line
|
||||
let logSpace = process.stdout.columns - stringWidth(message) - stringWidth(dateString)
|
||||
if (logSpace <= 0) {
|
||||
logSpace = 10
|
||||
}
|
||||
|
||||
this.log(`${message}${' '.repeat(logSpace)}${dateString}`);
|
||||
this.log();
|
||||
}
|
||||
}
|
||||
|
||||
clearConsole () {
|
||||
if (!this.capturing && this.enabled && 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)
|
||||
}
|
||||
}
|
||||
|
||||
captureLogs (fun) {
|
||||
try {
|
||||
this.capture();
|
||||
fun.call();
|
||||
return this.capturedMessages;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
this.endCapture();
|
||||
}
|
||||
}
|
||||
|
||||
captureConsole (args, method) {
|
||||
if (this.capturing) {
|
||||
this.capturedMessages.push(chalk.stripColor(args.join(' ')).trim());
|
||||
} else {
|
||||
method.apply(console, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
module.exports = new Debugger();
|
||||
+38
@@ -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.*:\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;
|
||||
+19
@@ -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;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
'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 module = webpackError.dependencies[0].request;
|
||||
return Object.assign({}, error, {
|
||||
message: `Module not found ${module}`,
|
||||
type: TYPE,
|
||||
severity: 900,
|
||||
module,
|
||||
name: 'Module not found'
|
||||
});
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
module.exports = transform;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
function formatTitle(severity, message) {
|
||||
return chalk[bgColor(severity)].black('', message, '');
|
||||
}
|
||||
|
||||
function formatText(severity, message) {
|
||||
return chalk[textColor(severity)](message);
|
||||
}
|
||||
|
||||
function bgColor(severity) {
|
||||
const color = textColor(severity);
|
||||
return 'bg'+ capitalizeFirstLetter(color)
|
||||
}
|
||||
|
||||
function textColor(serverity) {
|
||||
switch (serverity.toLowerCase()) {
|
||||
case 'success': return 'green';
|
||||
case 'info': return 'blue';
|
||||
case 'note': return 'white';
|
||||
case 'warning': return 'yellow';
|
||||
case 'error': return 'red';
|
||||
default: return 'red';
|
||||
}
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bgColor: bgColor,
|
||||
textColor: textColor,
|
||||
formatTitle: formatTitle,
|
||||
formatText: formatText
|
||||
};
|
||||
+31
@@ -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
|
||||
};
|
||||
Reference in New Issue
Block a user