add plugin system for Stats presets, defaults, creation and printing

This commit is contained in:
Tobias Koppers 2018-12-18 18:29:12 +01:00
parent 4e653e73c6
commit 123b0a64e7
35 changed files with 3650 additions and 2909 deletions

View File

@ -11,7 +11,7 @@ module.exports = {
es6: true
},
parserOptions: {
ecmaVersion: 2017
ecmaVersion: 2018
},
rules: {
"prettier/prettier": "error",
@ -28,7 +28,7 @@ module.exports = {
"no-extra-bind": "warn",
"no-process-exit": "warn",
"no-use-before-define": "off",
"no-unused-vars": ["error", { args: "none" }],
"no-unused-vars": ["error", { args: "none", ignoreRestSiblings: true }],
"no-unsafe-negation": "error",
"no-loop-func": "warn",
indent: "off",

View File

@ -74,6 +74,7 @@ class Chunk {
this.filenameTemplate = undefined;
/** @private @type {SortableSet<ChunkGroup>} */
this._groups = new SortableSet(undefined, sortChunkGroupById);
// TODO convert files to a Set
/** @type {string[]} */
this.files = [];
/** @type {boolean} */
@ -88,6 +89,7 @@ class Chunk {
this.chunkReason = undefined;
/** @type {boolean} */
this.extraAsync = false;
// TODO remove this, it's wrong
this.removedModules = undefined;
}

View File

@ -40,6 +40,8 @@ const RuntimeGlobals = require("./RuntimeGlobals");
const RuntimeTemplate = require("./RuntimeTemplate");
const Stats = require("./Stats");
const compareLocations = require("./compareLocations");
const StatsFactory = require("./stats/StatsFactory");
const StatsPrinter = require("./stats/StatsPrinter");
const AsyncQueue = require("./util/AsyncQueue");
const Queue = require("./util/Queue");
const SortableSet = require("./util/SortableSet");
@ -66,6 +68,8 @@ const { arrayToSetDeprecation } = require("./util/deprecation");
/** @typedef {import("./dependencies/DependencyReference")} DependencyReference */
/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */
/** @typedef {import("./dependencies/EntryDependency")} EntryDependency */
/** @typedef {import("./stats/StatsFactory")} StatsFactory */
/** @typedef {import("./stats/StatsPrinter")} StatsPrinter */
/** @typedef {import("./util/createHash").Hash} Hash */
/**
@ -425,6 +429,15 @@ class Compilation {
"compilerIndex"
]),
/** @type {HookMap<Object, Object>} */
statsPreset: new HookMap(() => new SyncHook(["options", "context"])),
/** @type {SyncHook<Object, Object>} */
statsNormalize: new SyncHook(["options", "context"]),
/** @type {SyncHook<StatsFactory, Object>} */
statsFactory: new SyncHook(["statsFactory", "options"]),
/** @type {SyncHook<StatsPrinter, Object>} */
statsPrinter: new SyncHook(["statsPrinter", "options"]),
get normalModuleLoader() {
return getNormalModuleLoader();
}
@ -555,6 +568,39 @@ class Compilation {
return new Stats(this);
}
createStatsOptions(optionsOrPreset, context = {}) {
if (
typeof optionsOrPreset === "boolean" ||
typeof optionsOrPreset === "string"
) {
optionsOrPreset = { preset: optionsOrPreset };
}
if (typeof optionsOrPreset === "object" && optionsOrPreset !== null) {
const options = Object.assign({}, optionsOrPreset);
if (options.preset !== undefined) {
this.hooks.statsPreset.for(options.preset).call(options, context);
}
this.hooks.statsNormalize.call(options, context);
return options;
} else {
const options = {};
this.hooks.statsNormalize.call(options, context);
return options;
}
}
createStatsFactory(options) {
const statsFactory = new StatsFactory();
this.hooks.statsFactory.call(statsFactory, options);
return statsFactory;
}
createStatsPrinter(options) {
const statsPrinter = new StatsPrinter();
this.hooks.statsPrinter.call(statsPrinter, options);
return statsPrinter;
}
/**
* @param {Module} module module to be added that was created
* @param {ModuleCallback} callback returns the module in the compilation,

View File

@ -5,10 +5,14 @@
"use strict";
const Stats = require("./Stats");
const identifierUtils = require("./util/identifier");
const optionOrFallback = (optionValue, fallbackValue) =>
optionValue !== undefined ? optionValue : fallbackValue;
/** @typedef {import("./Stats")} Stats */
const indent = (str, prefix) => {
const rem = str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
return prefix + rem;
};
class MultiStats {
/**
@ -33,64 +37,115 @@ class MultiStats {
return this.stats.some(stat => stat.hasWarnings());
}
toJson(options, forToString) {
if (typeof options === "boolean" || typeof options === "string") {
options = Stats.presetToOptions(options);
} else if (!options) {
options = {};
}
const jsons = this.stats.map((stat, idx) => {
const childOptions = Stats.getChildOptions(options, idx);
const obj = stat.toJson(childOptions, forToString);
obj.name = stat.compilation && stat.compilation.name;
return obj;
_createChildOptions(options, context) {
const { children: _, ...baseOptions } = options;
const children = this.stats.map((stat, idx) => {
const childOptions = Array.isArray(options.children)
? options.children[idx]
: options.children;
return stat.compilation.createStatsOptions(
Object.assign(
{},
baseOptions,
childOptions && typeof childOptions === "object"
? childOptions
: { preset: childOptions }
),
context
);
});
const showVersion =
options.version === undefined
? jsons.every(j => j.version)
: options.version !== false;
const showHash =
options.hash === undefined
? jsons.every(j => j.hash)
: options.hash !== false;
if (showVersion) {
for (const j of jsons) {
delete j.version;
const version = children.every(o => o.version);
const hash = children.every(o => o.hash);
if (version) {
for (const o of children) {
o.version = false;
}
}
const obj = {
errors: jsons.reduce((arr, j) => {
return arr.concat(
j.errors.map(msg => {
return `(${j.name}) ${msg}`;
})
);
}, []),
warnings: jsons.reduce((arr, j) => {
return arr.concat(
j.warnings.map(msg => {
return `(${j.name}) ${msg}`;
})
);
}, [])
return {
version,
hash,
children
};
if (showVersion) obj.version = require("../package.json").version;
if (showHash) obj.hash = this.hash;
if (options.children !== false) obj.children = jsons;
}
toJson(options) {
options = this._createChildOptions(options, { forToString: false });
const obj = {};
obj.children = this.stats.map((stat, idx) => {
return stat.toJson(options.children[idx]);
});
if (options.version) {
obj.version = require("../package.json").version;
}
if (options.hash) {
obj.hash = this.hash;
}
const jsons = this.stats.map((stat, idx) => {
const childOptions = Array.isArray(options) ? options[idx] : options;
const obj = stat.toJson(childOptions);
const compilationName = stat.compilation.name;
const name =
compilationName &&
identifierUtils.makePathsRelative(
options.context,
compilationName,
stat.compilation.compiler.root
);
obj.name = name;
return obj;
});
obj.errors = jsons.reduce((arr, j) => {
if (!j.errors) return arr;
return arr.concat(
j.errors.map(obj => {
return Object.assign({}, obj, {
compilerPath: obj.compilerPath
? `${j.name}.${obj.compilerPath}`
: j.name
});
})
);
}, []);
obj.warnings = jsons.reduce((arr, j) => {
if (!j.warnings) return arr;
return arr.concat(
j.warnings.map(obj => {
return Object.assign({}, obj, {
compilerPath: obj.compilerPath
? `${j.name}.${obj.compilerPath}`
: j.name
});
})
);
}, []);
return obj;
}
toString(options) {
if (typeof options === "boolean" || typeof options === "string") {
options = Stats.presetToOptions(options);
} else if (!options) {
options = {};
options = this._createChildOptions(options, { forToString: true });
const results = this.stats.map((stat, idx) => {
const str = stat.toString(options.children[idx]);
const compilationName = stat.compilation.name;
const name =
compilationName &&
identifierUtils
.makePathsRelative(
options.context,
compilationName,
stat.compilation.compiler.root
)
.replace(/\|/g, " ");
if (!str) return str;
const content = indent(str, " ");
return name ? `Child ${name}:\n${content}` : `Child\n${content}`;
});
if (options.version) {
results.unshift(`Version: webpack ${require("../package.json").version}`);
}
const useColors = optionOrFallback(options.colors, false);
const obj = this.toJson(options, true);
return Stats.jsonToString(obj, useColors);
if (options.hash) {
results.unshift(`Hash: ${this.hash}`);
}
return results.filter(Boolean).join("\n");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("./Chunk")} Chunk */
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
class WebpackError extends Error {
@ -24,6 +25,10 @@ class WebpackError extends Error {
this.loc = undefined;
/** @type {boolean} */
this.hideStack = undefined;
/** @type {Chunk} */
this.chunk = undefined;
/** @type {string} */
this.file = undefined;
Error.captureStackTrace(this, this.constructor);
}

View File

@ -73,6 +73,10 @@ const SplitChunksPlugin = require("./optimize/SplitChunksPlugin");
const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin");
const WasmFinalizeExportsPlugin = require("./wasm/WasmFinalizeExportsPlugin");
const DefaultStatsFactoryPlugin = require("./stats/DefaultStatsFactoryPlugin");
const DefaultStatsPresetPlugin = require("./stats/DefaultStatsPresetPlugin");
const DefaultStatsPrinterPlugin = require("./stats/DefaultStatsPrinterPlugin");
/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */
/** @typedef {import("./Compiler")} Compiler */
@ -334,6 +338,10 @@ class WebpackOptionsApply extends OptionsApply {
new ImportPlugin(options.module).apply(compiler);
new SystemPlugin(options.module).apply(compiler);
new DefaultStatsFactoryPlugin().apply(compiler);
new DefaultStatsPresetPlugin().apply(compiler);
new DefaultStatsPrinterPlugin().apply(compiler);
if (typeof options.mode !== "string") {
new WarnNoModeSetPlugin().apply(compiler);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RequestShortener = require("../RequestShortener");
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
const applyDefaults = (options, defaults) => {
for (const key of Object.keys(defaults)) {
if (typeof options[key] === "undefined") {
options[key] = defaults[key];
}
}
};
const NAMED_PRESETS = {
verbose: {
entrypoints: true,
chunkGroups: true,
modules: false,
chunks: true,
chunkModules: true,
chunkRootModules: false,
chunkOrigins: true,
depth: true,
env: true,
reasons: true,
usedExports: true,
providedExports: true,
optimizationBailout: true,
errorDetails: true,
publicPath: true,
orphanModules: true,
runtime: true,
exclude: false,
maxModules: Infinity
},
detailed: {
entrypoints: true,
chunkGroups: true,
chunks: true,
chunkModules: false,
chunkRootModules: false,
chunkOrigins: true,
depth: true,
usedExports: true,
providedExports: true,
optimizationBailout: true,
errorDetails: true,
publicPath: true,
runtimeModules: true,
runtime: true,
exclude: false,
maxModules: Infinity
},
minimal: {
all: false,
modules: true,
maxModules: 0,
errors: true,
warnings: true
},
"errors-only": {
all: false,
errors: true,
moduleTrace: true
},
none: {
all: false
}
};
const NORMAL_ON = ({ all }) => all !== false;
const NORMAL_OFF = ({ all }) => all === true;
const OFF_FOR_TO_STRING = (options, { forToString }) => !forToString;
/** @type {Record<string, (options: Object, context: { forToString: boolean }, compilation: Compilation) => any>} */
const DEFAULTS = {
context: (options, context, compilation) => compilation.compiler.context,
requestShortener: (options, context, compilation) =>
compilation.compiler.context === options.context
? compilation.requestShortener
: new RequestShortener(options.context),
performance: NORMAL_ON,
hash: NORMAL_ON,
env: NORMAL_OFF,
version: NORMAL_ON,
timings: NORMAL_ON,
builtAt: NORMAL_ON,
assets: NORMAL_ON,
entrypoints: NORMAL_ON,
chunkGroups: OFF_FOR_TO_STRING,
chunks: OFF_FOR_TO_STRING,
chunkModules: OFF_FOR_TO_STRING,
chunkRootModules: ({ all, chunkModules }, { forToString }) => {
if (all === false) return false;
if (all === true) return true;
if (forToString && chunkModules) return false;
return true;
},
chunkOrigins: OFF_FOR_TO_STRING,
modules: NORMAL_ON,
nestedModules: OFF_FOR_TO_STRING,
orphanModules: NORMAL_OFF,
moduleAssets: OFF_FOR_TO_STRING,
depth: OFF_FOR_TO_STRING,
cached: NORMAL_ON,
runtime: OFF_FOR_TO_STRING,
cachedAssets: NORMAL_ON,
reasons: OFF_FOR_TO_STRING,
usedExports: OFF_FOR_TO_STRING,
providedExports: OFF_FOR_TO_STRING,
optimizationBailout: OFF_FOR_TO_STRING,
children: NORMAL_ON,
source: NORMAL_OFF,
moduleTrace: NORMAL_ON,
errors: NORMAL_ON,
errorDetails: OFF_FOR_TO_STRING,
warnings: NORMAL_ON,
publicPath: OFF_FOR_TO_STRING,
excludeModules: () => [],
excludeAssets: () => [],
maxModules: (o, { forToString }) => (forToString ? 15 : Infinity),
modulesSort: () => "id",
chunksSort: () => "id",
assetsSort: () => "name",
outputPath: OFF_FOR_TO_STRING,
colors: () => false
};
const normalizeExclude = item => {
if (typeof item === "string") {
const regExp = new RegExp(
`[\\\\/]${item.replace(
// eslint-disable-next-line no-useless-escape
/[-[\]{}()*+?.\\^$|]/g,
"\\$&"
)}([\\\\/]|$|!|\\?)`
);
return ident => regExp.test(ident);
}
if (item && typeof item === "object" && typeof item.test === "function") {
return ident => item.test(ident);
}
if (typeof item === "function") {
return item;
}
if (typeof item === "boolean") {
return () => item;
}
};
const NORMALIZER = {
excludeModules: value => {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(normalizeExclude);
},
excludeAssets: value => {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(normalizeExclude);
},
warningsFilter: value => {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(filter => {
if (typeof filter === "string") {
return (warning, warningString) => warningString.includes(filter);
}
if (filter instanceof RegExp) {
return (warning, warningString) => filter.test(warningString);
}
if (typeof filter === "function") {
return filter;
}
throw new Error(
`Can only filter warnings with Strings or RegExps. (Given: ${filter})`
);
});
}
};
class DefaultStatsPresetPlugin {
/**
* @param {Compiler} compiler webpack compiler
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap("DefaultStatsPresetPlugin", compilation => {
compilation.hooks.statsPreset
.for(false)
.tap("DefaultStatsPresetPlugin", (options, context) => {
applyDefaults(options, NAMED_PRESETS.none);
});
for (const key of Object.keys(NAMED_PRESETS)) {
const defaults = NAMED_PRESETS[key];
compilation.hooks.statsPreset
.for(key)
.tap("DefaultStatsPresetPlugin", (options, context) => {
applyDefaults(options, defaults);
});
}
compilation.hooks.statsNormalize.tap(
"DefaultStatsPresetPlugin",
(options, context) => {
for (const key of Object.keys(DEFAULTS)) {
if (options[key] === undefined)
options[key] = DEFAULTS[key](options, context, compilation);
}
for (const key of Object.keys(NORMALIZER)) {
options[key] = NORMALIZER[key](options[key]);
}
}
);
});
}
}
module.exports = DefaultStatsPresetPlugin;

View File

@ -0,0 +1,994 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./StatsPrinter")} StatsPrinter */
/**
* @typedef {Object} UsualContext
* @property {string} type
* @property {Object} compilation
* @property {Object} asset
* @property {Object} module
* @property {Object} chunk
* @property {Object} moduleReason
* @property {(str: string) => string} bold
* @property {(str: string) => string} yellow
* @property {(str: string) => string} red
* @property {(str: string) => string} green
* @property {(str: string) => string} magenta
* @property {(str: string) => string} cyan
* @property {(file: string, oversize?: boolean) => string} formatFilename
* @property {(id: string) => string} formatModuleId
* @property {(id: string, direction?: "parent"|"child"|"sibling") => string} formatChunkId
* @property {(size: number) => string} formatSize
* @property {(flag: string) => string} formatFlag
* @property {(time: number) => string} formatTime
* @property {number} maxModuleId
* @property {string} chunkGroupKind
*/
const plural = (n, singular, plural) => (n === 1 ? singular : plural);
const printSizes = (sizes, { formatSize }) => {
const keys = Object.keys(sizes);
if (keys.length > 1) {
return keys.map(key => `${formatSize(sizes[key])} (${key})`).join(" ");
} else if (keys.length === 1) {
return formatSize(sizes[keys[0]]);
}
};
const isValidId = id => {
return typeof id === "number" || id;
};
/** @type {Record<string, (thing: any, context: UsualContext, printer: StatsPrinter) => string | void>} */
const SIMPLE_PRINTERS = {
"compilation.hash": (hash, { bold, type }) =>
type === "compilation.hash" ? `Hash: ${bold(hash)}` : undefined,
"compilation.version": (version, { bold, type }) =>
type === "compilation.version"
? `Version: webpack ${bold(version)}`
: undefined,
"compilation.time": (time, { bold }) => `Time: ${bold(time)}ms`,
"compilation.builtAt": (builtAt, { bold }) => {
const builtAtDate = new Date(builtAt);
return `Built at: ${builtAtDate.toLocaleDateString(undefined, {
day: "2-digit",
month: "2-digit",
year: "numeric"
})} ${bold(builtAtDate.toLocaleTimeString())}`;
},
"compilation.env": (env, { bold }) =>
env
? `Environment (--env): ${bold(JSON.stringify(env, null, 2))}`
: undefined,
"compilation.publicPath": (publicPath, { bold }) =>
`PublicPath: ${bold(publicPath || "(none)")}`,
"compilation.entrypoints": (entrypoints, context, printer) =>
Array.isArray(entrypoints)
? undefined
: printer.print(
context.type,
Object.values(entrypoints),
Object.assign({}, context, { chunkGroupKind: "Entrypoint" })
),
"compilation.namedChunkGroups": (namedChunkGroups, context, printer) =>
Array.isArray(namedChunkGroups)
? undefined
: printer.print(
context.type,
Object.values(namedChunkGroups),
Object.assign({}, context, { chunkGroupKind: "Chunk Group" })
),
"compilation.assetsByChunkName": () => "",
"compilation.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"compilation.filteredModules": (
filteredModules,
{ compilation: { modules } }
) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} ${plural(filteredModules, "module", "modules")}`
: undefined,
"compilation.filteredAssets": (filteredAssets, { compilation: { assets } }) =>
filteredAssets > 0
? `${
assets && assets.length > 0
? ` + ${filteredAssets} hidden`
: filteredAssets
} ${plural(filteredAssets, "asset", "assets")}`
: undefined,
"compilation.children[].compilation.name": name =>
name ? `Child ${name}:` : "Child",
"asset.name": (name, { formatFilename, asset: { isOverSizeLimit } }) =>
formatFilename(name, isOverSizeLimit),
"asset.size": (
size,
{ asset: { isOverSizeLimit }, yellow, green, formatSize }
) => (isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size)),
"asset.emitted": (emitted, { green, formatFlag }) =>
emitted ? green(formatFlag("emitted")) : undefined,
"asset.isOverSizeLimit": (isOverSizeLimit, { yellow, formatFlag }) =>
isOverSizeLimit ? yellow(formatFlag("big")) : undefined,
assetChunk: (id, { formatChunkId }) => formatChunkId(id),
assetChunkName: name => name,
"module.id": (id, { formatModuleId }) =>
isValidId(id) ? formatModuleId(id) : undefined,
"module.name": (name, { bold }) => bold(name),
"module.identifier": identifier => identifier,
"module.sizes": printSizes,
"module.chunks[]": (id, { formatChunkId }) => formatChunkId(id),
"module.depth": (depth, { formatFlag }) =>
depth !== null ? formatFlag(`depth ${depth}`) : undefined,
"module.cacheable": (cacheable, { formatFlag, red }) =>
cacheable === false ? red(formatFlag("not cacheable")) : undefined,
"module.orphan": (orphan, { formatFlag, yellow }) =>
orphan ? yellow(formatFlag("orphan")) : undefined,
"module.runtime": (runtime, { formatFlag, yellow }) =>
runtime ? yellow(formatFlag("runtime")) : undefined,
"module.optional": (optional, { formatFlag, yellow }) =>
optional ? yellow(formatFlag("optional")) : undefined,
"module.built": (built, { formatFlag, green }) =>
built ? green(formatFlag("built")) : undefined,
"module.assets": (assets, { formatFlag, magenta }) =>
assets && assets.length
? magenta(
formatFlag(
`${assets.length} ${plural(assets.length, "asset", "assets")}`
)
)
: undefined,
"module.failed": (failed, { formatFlag, red }) =>
failed ? red(formatFlag("failed")) : undefined,
"module.warnings": (warnings, { formatFlag, yellow }) =>
warnings
? yellow(
formatFlag(`${warnings} ${plural(warnings, "warning", "warnings")}`)
)
: undefined,
"module.errors": (errors, { formatFlag, red }) =>
errors
? red(formatFlag(`${errors} ${plural(errors, "error", "errors")}`))
: undefined,
"module.providedExports": (providedExports, { formatFlag, cyan }) => {
if (Array.isArray(providedExports)) {
if (providedExports.length === 0) return cyan(formatFlag("no exports"));
return cyan(formatFlag(`exports: ${providedExports.join(", ")}`));
}
},
"module.usedExports": (usedExports, { formatFlag, cyan, module }) => {
if (usedExports !== true) {
if (usedExports === null) return cyan(formatFlag("used exports unknown"));
if (usedExports === false) return cyan(formatFlag("module unused"));
if (Array.isArray(usedExports)) {
if (usedExports.length === 0)
return cyan(formatFlag("no exports used"));
const providedExportsCount = Array.isArray(module.providedExports)
? module.providedExports.length
: null;
if (
providedExportsCount !== null &&
providedExportsCount === module.usedExports.length
) {
return cyan(formatFlag("all exports used"));
} else {
return cyan(
formatFlag(`only some exports used: ${usedExports.join(", ")}`)
);
}
}
}
},
"module.optimizationBailout[]": (optimizationBailout, { yellow }) =>
yellow(optimizationBailout),
"module.issuerPath": (issuerPath, { module }) =>
module.profile ? undefined : "",
"module.profile": profile => undefined,
"module.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"module.filteredModules": (filteredModules, { compilation: { modules } }) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} nested ${plural(filteredModules, "module", "modules")}`
: undefined,
"module.separator!": () => "\n",
"moduleIssuer.id": (id, { formatModuleId }) => formatModuleId(id),
"moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value),
"moduleReason.type": type => type,
"moduleReason.userRequest": (userRequest, { cyan }) => cyan(userRequest),
"moduleReason.moduleId": (moduleId, { formatModuleId }) =>
moduleId !== null ? formatModuleId(moduleId) : undefined,
"moduleReason.module": (module, { magenta }) => magenta(module),
"moduleReason.loc": loc => loc,
"moduleReason.explanation": (explanation, { cyan }) => cyan(explanation),
"module.profile.total": (value, { formatTime }) => formatTime(value),
"module.profile.resolving": (value, { formatTime }) =>
`resolving: ${formatTime(value)}`,
"module.profile.restoring": (value, { formatTime }) =>
`restoring: ${formatTime(value)}`,
"module.profile.integration": (value, { formatTime }) =>
`integration: ${formatTime(value)}`,
"module.profile.building": (value, { formatTime }) =>
`building: ${formatTime(value)}`,
"module.profile.storing": (value, { formatTime }) =>
`storing: ${formatTime(value)}`,
"module.profile.additionalResolving": (value, { formatTime }) =>
value ? `additional resolving: ${formatTime(value)}` : undefined,
"module.profile.additionalIntegration": (value, { formatTime }) =>
value ? `additional integration: ${formatTime(value)}` : undefined,
"chunkGroup.kind!": (_, { chunkGroupKind }) => chunkGroupKind,
"chunkGroup.name": (name, { bold }) => bold(name),
"chunkGroup.isOverSizeLimit": (isOverSizeLimit, { formatFlag, yellow }) =>
isOverSizeLimit ? yellow(formatFlag("big")) : undefined,
"chunkGroup.separator!": () => "=",
"chunkGroup.assets[]": (asset, { green }) => green(asset),
"chunkGroup.childAssets": (childAssets, context, printer) =>
Array.isArray(childAssets)
? undefined
: printer.print(
context.type,
Object.keys(childAssets).map(key => ({
type: key,
children: childAssets[key]
})),
context
),
"chunkGroup.childAssets[].type": type => `${type}:`,
"chunkGroup.childAssets[].children[]": (file, { formatFilename }) =>
formatFilename(file),
"chunk.id": (id, { formatChunkId }) => formatChunkId(id),
"chunk.files[]": (file, { formatFilename }) => formatFilename(file),
"chunk.names[]": name => name,
"chunk.sizes": (sizes, context) => printSizes(sizes, context),
"chunk.parents[]": (parents, context) =>
context.formatChunkId(parents, "parent"),
"chunk.siblings[]": (siblings, context) =>
context.formatChunkId(siblings, "sibling"),
"chunk.children[]": (children, context) =>
context.formatChunkId(children, "child"),
"chunk.childrenByOrder": (childrenByOrder, context, printer) =>
Array.isArray(childrenByOrder)
? undefined
: printer.print(
context.type,
Object.keys(childrenByOrder).map(key => ({
type: key,
children: childrenByOrder[key]
})),
context
),
"chunk.childrenByOrder[].type": type => `${type}:`,
"chunk.childrenByOrder[].children[]": (id, { formatChunkId }) =>
isValidId(id) ? formatChunkId(id) : undefined,
"chunk.entry": (entry, { formatFlag, yellow }) =>
entry ? yellow(formatFlag("entry")) : undefined,
"chunk.initial": (initial, { formatFlag, yellow }) =>
initial ? yellow(formatFlag("initial")) : undefined,
"chunk.rendered": (rendered, { formatFlag, green }) =>
rendered ? green(formatFlag("rendered")) : undefined,
"chunk.recorded": (recorded, { formatFlag, green }) =>
recorded ? green(formatFlag("recorded")) : undefined,
"chunk.reason": (reason, { yellow }) => yellow(reason),
"chunk.rootModules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"chunk.filteredRootModules": (
filteredRootModules,
{ chunk: { rootModules } }
) =>
filteredRootModules > 0
? ` ${
rootModules && rootModules.length > 0
? ` + ${filteredRootModules} hidden`
: filteredRootModules
} root ${plural(filteredRootModules, "module", "modules")}`
: undefined,
"chunk.nonRootModules": (
nonRootModules,
{ chunk: { filteredRootModules, rootModules } }
) =>
nonRootModules > 0
? ` ${
(rootModules && rootModules.length > 0) || filteredRootModules > 0
? ` + ${nonRootModules} hidden`
: nonRootModules
} dependent ${plural(nonRootModules, "module", "modules")}`
: undefined,
"chunk.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"chunk.filteredModules": (filteredModules, { chunk: { modules } }) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} chunk ${plural(filteredModules, "module", "modules")}`
: undefined,
"chunk.separator!": () => "\n",
"chunkOrigin.request": request => request,
"chunkOrigin.moduleId": (moduleId, { formatModuleId }) =>
moduleId !== null && moduleId !== undefined
? formatModuleId(moduleId)
: undefined,
"chunkOrigin.moduleName": (moduleName, { bold }) => bold(moduleName),
"chunkOrigin.loc": loc => loc,
"error.compilerPath": (compilerPath, { bold }) =>
compilerPath ? bold(`(${compilerPath})`) : undefined,
"error.chunkId": (chunkId, { formatChunkId }) =>
isValidId(chunkId) ? formatChunkId(chunkId) : undefined,
"error.chunkEntry": (chunkEntry, { formatFlag }) =>
chunkEntry ? formatFlag("entry") : undefined,
"error.chunkInitial": (chunkInitial, { formatFlag }) =>
chunkInitial ? formatFlag("initial") : undefined,
"error.file": (file, { bold }) => bold(file),
"error.moduleName": (moduleName, { bold }) => {
return moduleName.includes("!")
? `${bold(moduleName.replace(/^(\s|\S)*!/, ""))} (${moduleName})`
: `${bold(moduleName)}`;
},
"error.loc": (loc, { green }) => green(loc),
"error.message": (message, { bold }) => bold(message),
"error.details": details => details,
"error.stack": stack => stack,
"error.missing[]": missing => `[${missing}]`,
"error.moduleTrace": moduleTrace => undefined,
"error.separator!": () => "\n",
"moduleTraceItem.originName": originName => originName,
"moduleTraceDependency.loc": loc => loc
};
/** @type {Record<string, string>} */
const ITEM_NAMES = {
"compilation.assets[]": "asset",
"compilation.modules[]": "module",
"compilation.chunks[]": "chunk",
"compilation.entrypoints[]": "chunkGroup",
"compilation.namedChunkGroups[]": "chunkGroup",
"compilation.errors[]": "error",
"compilation.warnings[]": "error",
"compilation.children[]": "compilation",
"asset.chunks[]": "assetChunk",
"asset.chunkNames[]": "assetChunkName",
"module.modules[]": "module",
"module.reasons[]": "moduleReason",
"module.issuerPath[]": "moduleIssuer",
"chunk.origins[]": "chunkOrigin",
"chunk.rootModules[]": "module",
"chunk.modules[]": "module",
"error.moduleTrace[]": "moduleTraceItem",
"moduleTraceItem.dependencies[]": "moduleTraceDependency"
};
const ERROR_PREFERED_ORDER = [
"compilerPath",
"chunkId",
"chunkEntry",
"chunkInitial",
"file",
"separator!",
"moduleName",
"loc",
"separator!",
"message",
"details",
"stack",
"separator!",
"missing",
"separator!",
"moduleTrace"
];
/** @type {Record<string, string[]>} */
const PREFERED_ORDERS = {
compilation: [
"name",
"hash",
"version",
"time",
"builtAt",
"env",
"publicPath",
"assets",
"filteredAssets",
"entrypoints",
"namedChunkGroups",
"chunks",
"modules",
"filteredModules",
"warnings",
"errors",
"children",
"needAdditionalPass"
],
asset: ["name", "size", "chunks", "emitted", "isOverSizeLimit", "chunkNames"],
chunkGroup: [
"kind!",
"name",
"isOverSizeLimit",
"separator!",
"assets",
"childAssets"
],
module: [
"id",
"name",
"identifier",
"sizes",
"chunks",
"depth",
"cacheable",
"orphan",
"runtime",
"optional",
"built",
"assets",
"failed",
"warnings",
"errors",
"separator!",
"providedExports",
"separator!",
"usedExports",
"separator!",
"optimizationBailout",
"separator!",
"reasons",
"separator!",
"issuerPath",
"profile",
"separator!",
"modules"
],
moduleReason: [
"type",
"userRequest",
"moduleId",
"module",
"loc",
"explanation"
],
"module.profile": [
"total",
"separator!",
"resolving",
"restoring",
"integration",
"building",
"storing",
"additionalResolving",
"additionalIntegration"
],
chunk: [
"id",
"files",
"names",
"sizes",
"parents",
"siblings",
"children",
"childrenByOrder",
"entry",
"initial",
"rendered",
"recorded",
"reason",
"separator!",
"origins",
"separator!",
"rootModules",
"separator!",
"filteredRootModules",
"separator!",
"nonRootModules",
"separator!",
"modules",
"separator!",
"filteredModules"
],
chunkOrigin: ["request", "moduleId", "moduleName", "loc"],
error: ERROR_PREFERED_ORDER,
warning: ERROR_PREFERED_ORDER,
"chunk.childrenByOrder[]": ["type", "children"],
"chunkGroup.childAssets[]": ["type", "children"]
};
const itemsJoinOneLine = items => items.filter(Boolean).join(" ");
const itemsJoinMoreSpacing = items => items.filter(Boolean).join("\n\n");
const itemsJoinComma = items => items.filter(Boolean).join(", ");
const itemsJoinCommaBrackets = items =>
items.length > 0 ? `(${items.filter(Boolean).join(", ")})` : undefined;
/** @type {Record<string, (items: string[]) => string>} */
const SIMPLE_ITEMS_JOINER = {
"chunk.parents": itemsJoinOneLine,
"chunk.siblings": itemsJoinOneLine,
"chunk.children": itemsJoinOneLine,
"chunk.names": itemsJoinCommaBrackets,
"chunk.files": itemsJoinComma,
"chunk.childrenByOrder": itemsJoinOneLine,
"chunk.childrenByOrder[].children": itemsJoinOneLine,
"chunkGroup.assets": itemsJoinOneLine,
"chunkGroup.childAssets": itemsJoinOneLine,
"chunkGroup.childAssets[].children": itemsJoinOneLine,
"asset.chunks": itemsJoinComma,
"asset.chunkNames": itemsJoinComma,
"module.chunks": itemsJoinOneLine,
"module.issuerPath": items =>
items
.filter(Boolean)
.map(item => `${item} ->`)
.join(" "),
"compilation.errors": itemsJoinMoreSpacing,
"compilation.warnings": itemsJoinMoreSpacing,
"moduleTraceItem.dependencies": itemsJoinOneLine,
"compilation.children": items =>
items.map(item => indent(item, " ", true)).join("\n")
};
const joinOneLine = items =>
items
.map(item => item.content)
.filter(Boolean)
.join(" ");
const joinInBrackets = items => {
const res = [];
let mode = 0;
for (const item of items) {
if (item.element === "separator!") {
switch (mode) {
case 0:
case 1:
mode += 2;
break;
case 4:
res.push(")");
mode = 3;
break;
}
}
if (!item.content) continue;
switch (mode) {
case 0:
mode = 1;
break;
case 1:
res.push(" ");
break;
case 2:
res.push("(");
mode = 4;
break;
case 3:
res.push(" (");
mode = 4;
break;
case 4:
res.push(", ");
break;
}
res.push(item.content);
}
if (mode === 4) res.push(")");
return res.join("");
};
const indent = (str, prefix, noPrefixInFirstLine) => {
const rem = str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
if (noPrefixInFirstLine) return rem;
const ind = str[0] === "\n" ? "" : prefix;
return ind + rem;
};
const joinExplicitNewLine = (items, indenter) => {
let firstInLine = true;
return items
.map(item => {
if (!item.content) return;
let content = indent(item.content, indenter, !firstInLine);
if (firstInLine) {
content = content.replace(/^\n+/, "");
}
if (!content) return;
const noJoiner = firstInLine || content.startsWith("\n");
firstInLine = content.endsWith("\n");
return noJoiner ? content : " " + content;
})
.filter(Boolean)
.join("")
.trim();
};
const joinError = error => (items, { red, yellow }) =>
`${error ? red("ERROR") : yellow("WARNING")} in ${joinExplicitNewLine(
items,
""
)}`;
/** @type {Record<string, (items: ({ element: string, content: string })[], context: UsualContext) => string>} */
const SIMPLE_ELEMENT_JOINERS = {
compilation: items => {
const result = [];
let lastNeedMore = false;
for (const item of items) {
if (!item.content) continue;
const needMoreSpace =
item.element === "warnings" || item.element === "errors";
if (result.length !== 0) {
result.push(needMoreSpace || lastNeedMore ? "\n\n" : "\n");
}
result.push(item.content);
lastNeedMore = needMoreSpace;
}
if (lastNeedMore) result.push("\n");
return result.join("");
},
module: (items, { module, maxModuleId }) => {
let hasName = false;
let indenter = " ";
if (maxModuleId >= 10) indenter += " ";
if (maxModuleId >= 100) indenter += " ";
if (maxModuleId >= 1000) indenter += " ";
let prefix = "";
if (typeof module.id === "number") {
if (module.id < 1000 && maxModuleId >= 1000) prefix += " ";
if (module.id < 100 && maxModuleId >= 100) prefix += " ";
if (module.id < 10 && maxModuleId >= 10) prefix += " ";
} else if (typeof module.id === "string" && module.id !== "") {
if (maxModuleId >= 1000) prefix += " ";
if (maxModuleId >= 100) prefix += " ";
if (maxModuleId >= 10) prefix += " ";
}
return (
prefix +
joinExplicitNewLine(
items.filter(item => {
switch (item.element) {
case "id":
if (module.id === module.name && item.content) hasName = true;
break;
case "name":
if (hasName) return false;
if (item.content) hasName = true;
break;
case "identifier":
if (hasName) return false;
if (item.content) hasName = true;
break;
}
return true;
}),
indenter
)
);
},
chunk: items => {
let hasEntry = false;
return (
"chunk " +
joinExplicitNewLine(
items.filter(item => {
switch (item.element) {
case "entry":
if (item.content) hasEntry = true;
break;
case "initial":
if (hasEntry) return false;
break;
}
return true;
}),
" "
)
);
},
"chunk.childrenByOrder[]": items => `(${joinOneLine(items)})`,
chunkGroup: joinOneLine,
"chunkGroup.childAssets[]": items => `(${joinOneLine(items)})`,
moduleReason: (items, { moduleReason }) => {
let hasName = false;
return joinOneLine(
items.filter(item => {
switch (item.element) {
case "moduleId":
if (moduleReason.moduleId === moduleReason.module && item.content)
hasName = true;
break;
case "module":
if (hasName) return false;
break;
}
return true;
})
);
},
"module.profile": joinInBrackets,
moduleIssuer: joinOneLine,
chunkOrigin: items => " > " + joinOneLine(items),
"errors[].error": joinError(true),
"warnings[].error": joinError(false),
moduleTraceItem: items => " @ " + joinOneLine(items),
moduleTraceDependency: joinOneLine
};
const AVAILABLE_COLORS = {
bold: "\u001b[1m",
yellow: "\u001b[1m\u001b[33m",
red: "\u001b[1m\u001b[31m",
green: "\u001b[1m\u001b[32m",
cyan: "\u001b[1m\u001b[36m",
magenta: "\u001b[1m\u001b[35m"
};
const AVAILABLE_FORMATS = {
formatChunkId: (id, { yellow }, direction) => {
switch (direction) {
case "parent":
return `<{${yellow(id)}}>`;
case "sibling":
return `={${yellow(id)}}=`;
case "child":
return `>{${yellow(id)}}<`;
default:
return `{${yellow(id)}}`;
}
},
formatModuleId: id => `[${id}]`,
formatFilename: (filename, { green, yellow }, oversize) =>
(oversize ? yellow : green)(filename),
formatFlag: flag => `[${flag}]`,
formatSize: require("../SizeFormatHelpers").formatSize,
formatTime: (time, { timeReference, bold, green, yellow, red }) => {
if (timeReference && time !== timeReference) {
const times = [
timeReference / 2,
timeReference / 4,
timeReference / 8,
timeReference / 16
];
if (time < times[3]) return `${time}ms`;
else if (time < times[2]) return bold(`${time}ms`);
else if (time < times[1]) return green(`${time}ms`);
else if (time < times[0]) return yellow(`${time}ms`);
else return red(`${time}ms`);
} else {
return `${time}ms`;
}
}
};
const RESULT_MODIFIER = {
"module.modules": result => {
return indent(result, "| ");
}
};
const createOrder = (array, preferedOrder) => {
const originalArray = array.slice();
const set = new Set(array);
const usedSet = new Set();
array.length = 0;
for (const element of preferedOrder) {
if (element.endsWith("!") || set.has(element)) {
array.push(element);
usedSet.add(element);
}
}
for (const element of originalArray) {
if (!usedSet.has(element)) {
array.push(element);
}
}
return array;
};
const filterColors = value => value.replace(/\u001b\[\d+m/g, "");
const table = (array, align, splitter) => {
const rows = array.length;
const cols = array[0].length;
const colSizes = new Array(cols);
for (let col = 0; col < cols; col++) {
colSizes[col] = 0;
}
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const value = filterColors(`${array[row][col]}`);
if (value.length > colSizes[col]) {
colSizes[col] = value.length;
}
}
}
const lines = [];
for (let row = 0; row < rows; row++) {
let str = "";
for (let col = 0; col < cols; col++) {
const value = `${array[row][col]}`;
let l = filterColors(value).length;
if (align[col] === "l") {
str += value;
}
for (; l < colSizes[col] && col !== cols - 1; l++) {
str += " ";
}
if (align[col] === "r") {
str += value;
}
if (col + 1 < cols && colSizes[col] !== 0) {
str += splitter || " ";
}
}
lines.push(str.trimRight());
}
return lines.join("\n");
};
class DefaultStatsPrinterPlugin {
/**
* @param {Compiler} compiler webpack compiler
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap("DefaultStatsPrinterPlugin", compilation => {
compilation.hooks.statsPrinter.tap(
"DefaultStatsPrinterPlugin",
(stats, options, context) => {
// Put colors into context
stats.hooks.print
.for("compilation")
.tap("DefaultStatsPrinterPlugin", (compilation, context) => {
for (const color of Object.keys(AVAILABLE_COLORS)) {
let start;
if (options.colors) {
if (
typeof options.colors === "object" &&
typeof options.colors[color] === "string"
) {
start = options.colors[color];
} else {
start = AVAILABLE_COLORS[color];
}
}
if (start) {
context[color] = str => `${start}${str}\u001b[39m\u001b[22m`;
} else {
context[color] = str => str;
}
}
for (const format of Object.keys(AVAILABLE_FORMATS)) {
context[format] = (content, ...args) =>
AVAILABLE_FORMATS[format](content, context, ...args);
}
context.timeReference = compilation.time;
});
for (const key of Object.keys(SIMPLE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
SIMPLE_PRINTERS[key](obj, ctx, stats)
);
}
for (const key of Object.keys(PREFERED_ORDERS)) {
const preferedOrder = PREFERED_ORDERS[key];
stats.hooks.sortElements
.for(key)
.tap("DefaultStatsPrinterPlugin", (elements, context) => {
createOrder(elements, preferedOrder);
});
}
for (const key of Object.keys(ITEM_NAMES)) {
const itemName = ITEM_NAMES[key];
stats.hooks.getItemName
.for(key)
.tap("DefaultStatsPrinterPlugin", () => itemName);
}
for (const key of Object.keys(SIMPLE_ITEMS_JOINER)) {
const joiner = SIMPLE_ITEMS_JOINER[key];
stats.hooks.printItems
.for(key)
.tap("DefaultStatsPrinterPlugin", joiner);
}
for (const key of Object.keys(SIMPLE_ELEMENT_JOINERS)) {
const joiner = SIMPLE_ELEMENT_JOINERS[key];
stats.hooks.printElements
.for(key)
.tap("DefaultStatsPrinterPlugin", joiner);
}
for (const key of Object.keys(RESULT_MODIFIER)) {
const modifier = RESULT_MODIFIER[key];
stats.hooks.result
.for(key)
.tap("DefaultStatsPrinterPlugin", modifier);
}
// Print assets as table
stats.hooks.printElements
.for("compilation.assets[].asset")
.tap("DefaultStatsPrinterPlugin", (elements, { bold }) => {
const elementsMap = elements.reduce(
(obj, e) => ((obj[e.element] = e.content), obj),
Object.create(null)
);
return [
elementsMap.name || "",
elementsMap.size || "",
elementsMap.chunks || "",
elementsMap.emitted || "",
elementsMap.isOverSizeLimit || "",
elementsMap.chunkNames || ""
];
});
stats.hooks.printItems
.for("compilation.assets")
.tap("DefaultStatsPrinterPlugin", (items, { bold }) => {
if (items.length === 0) return undefined;
let header = ["Asset", "Size", "Chunks", "", "", "Chunk Names"];
header = header.map(h => (h ? bold(h) : h));
return table([header].concat(items), "rrrlll");
});
}
);
});
}
}
module.exports = DefaultStatsPrinterPlugin;

195
lib/stats/StatsFactory.js Normal file
View File

@ -0,0 +1,195 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
const { concatComparators, keepOriginalOrder } = require("../util/comparators");
const forEachLevel = (hookMap, type, fn) => {
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
const result = fn(hook);
if (result !== undefined) return result;
}
}
};
const forEachLevelWaterfall = (hookMap, type, data, fn) => {
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
data = fn(hook, data);
}
}
return data;
};
const forEachLevelFilter = (hookMap, type, items, fn, forceClone) => {
const typeParts = type.split(".");
const hooks = [];
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
hooks.push((item, idx, i) => fn(hook, item, idx, i));
}
}
if (hooks.length === 0) return forceClone ? items.slice() : items;
let i = 0;
return items.filter((item, idx) => {
for (const h of hooks) {
const r = h(item, idx, i);
if (r !== undefined) {
if (r) i++;
return r;
}
}
i++;
return true;
});
};
class StatsFactory {
constructor() {
this.hooks = Object.freeze({
/** @type {HookMap<Object, any, Object>} */
extract: new HookMap(
() => new SyncBailHook(["object", "data", "context"])
),
/** @type {HookMap<any, Object, number, number>} */
filter: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<(function(any, any): number)[], Object>} */
sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
/** @type {HookMap<any, Object, number, number>} */
filterSorted: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<(function(any, any): number)[], Object>} */
sortResults: new HookMap(
() => new SyncBailHook(["comparators", "context"])
),
filterResults: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<any[], Object>} */
merge: new HookMap(() => new SyncBailHook(["items", "context"])),
/** @type {HookMap<any[], Object>} */
result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
/** @type {HookMap<any, Object>} */
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
/** @type {HookMap<any, Object>} */
getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
});
}
create(type, data, baseContext) {
const context = Object.assign({}, baseContext, {
type,
[type]: data
});
if (Array.isArray(data)) {
// run filter on unsorted items
const items = forEachLevelFilter(
this.hooks.filter,
type,
data,
(h, r, idx, i) => h.call(r, context, idx, i),
true
);
// sort items
const comparators = [];
forEachLevel(this.hooks.sort, type, h => h.call(comparators, context));
if (comparators.length > 0) {
items.sort(
// @ts-ignore number of arguments is correct
concatComparators(...comparators, keepOriginalOrder(items))
);
}
// run filter on sorted items
const items2 = forEachLevelFilter(
this.hooks.filterSorted,
type,
items,
(h, r, idx, i) => h.call(r, context, idx, i),
false
);
// for each item
const resultItems = items2.map((item, i) => {
const itemContext = Object.assign({}, context, {
_index: i
});
// run getItemName
const itemName = forEachLevel(this.hooks.getItemName, `${type}[]`, h =>
h.call(item, itemContext)
);
if (itemName) itemContext[itemName] = item;
const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;
// run getItemFactory
const itemFactory =
forEachLevel(this.hooks.getItemFactory, innerType, h =>
h.call(item, itemContext)
) || this;
// run item factory
return itemFactory.create(innerType, item, itemContext);
});
// sort result items
const comparators2 = [];
forEachLevel(this.hooks.sortResults, type, h =>
h.call(comparators2, context)
);
if (comparators2.length > 0) {
resultItems.sort(
// @ts-ignore number of arguments is correct
concatComparators(...comparators2, keepOriginalOrder(resultItems))
);
}
// run filter on sorted result items
const finalResultItems = forEachLevelFilter(
this.hooks.filterResults,
type,
resultItems,
(h, r, idx, i) => h.call(r, context, idx, i),
false
);
// run merge on mapped items
let result = forEachLevel(this.hooks.merge, type, h =>
h.call(finalResultItems, context)
);
if (result === undefined) result = finalResultItems;
// run result on merged items
return forEachLevelWaterfall(this.hooks.result, type, result, (h, r) =>
h.call(r, context)
);
} else {
const object = {};
// run extract on value
forEachLevel(this.hooks.extract, type, h =>
h.call(object, data, context)
);
// run result on extracted object
return forEachLevelWaterfall(this.hooks.result, type, object, (h, r) =>
h.call(r, context)
);
}
}
}
module.exports = StatsFactory;

134
lib/stats/StatsPrinter.js Normal file
View File

@ -0,0 +1,134 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable");
/**
* @typedef {Object} PrintedElement
* @property {string} element
* @property {string} content
*/
const forEachLevel = (hookMap, type, fn) => {
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
const result = fn(hook);
if (result !== undefined) return result;
}
}
};
const forEachLevelWaterfall = (hookMap, type, data, fn) => {
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
data = fn(hook, data);
}
}
return data;
};
class StatsPrinter {
constructor() {
this.hooks = Object.freeze({
/** @type {HookMap<string[], Object>} */
sortElements: new HookMap(
() => new SyncBailHook(["elements", "context"])
),
/** @type {HookMap<PrintedElement[], Object>} */
printElements: new HookMap(
() => new SyncBailHook(["printedElements", "context"])
),
/** @type {HookMap<any[], Object>} */
sortItems: new HookMap(() => new SyncBailHook(["items", "context"])),
/** @type {HookMap<any, Object>} */
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
/** @type {HookMap<string[], Object>} */
printItems: new HookMap(
() => new SyncBailHook(["printedItems", "context"])
),
/** @type {HookMap<Object, Object>} */
print: new HookMap(() => new SyncBailHook(["object", "context"])),
/** @type {HookMap<string, Object>} */
result: new HookMap(() => new SyncWaterfallHook(["result", "context"]))
});
}
print(type, object, baseContext) {
const context = Object.assign({}, baseContext, {
type,
[type]: object
});
let printResult = forEachLevel(this.hooks.print, type, hook =>
hook.call(object, context)
);
if (printResult === undefined) {
if (Array.isArray(object)) {
const sortedItems = object.slice();
forEachLevel(this.hooks.sortItems, type, h =>
h.call(sortedItems, context)
);
const printedItems = sortedItems.map((item, i) => {
const itemContext = Object.assign({}, context, {
_index: i
});
const itemName = forEachLevel(
this.hooks.getItemName,
`${type}[]`,
h => h.call(item, itemContext)
);
if (itemName) itemContext[itemName] = item;
return this.print(
itemName ? `${type}[].${itemName}` : `${type}[]`,
item,
itemContext
);
});
printResult = forEachLevel(this.hooks.printItems, type, h =>
h.call(printedItems, context)
);
if (printResult === undefined) {
const result = printedItems.filter(Boolean);
if (result.length > 0) printResult = result.join("\n");
}
} else if (object !== null && typeof object === "object") {
const elements = Object.keys(object);
forEachLevel(this.hooks.sortElements, type, h =>
h.call(elements, context)
);
const printedElements = elements.map(element => {
const content = this.print(
`${type}.${element}`,
object[element],
Object.assign({}, context, {
_parent: object,
_element: element,
[element]: object[element]
})
);
return { element, content };
});
printResult = forEachLevel(this.hooks.printElements, type, h =>
h.call(printedElements, context)
);
if (printResult === undefined) {
const result = printedElements.map(e => e.content).filter(Boolean);
if (result.length > 0) printResult = result.join("\n");
}
}
}
return forEachLevelWaterfall(this.hooks.result, type, printResult, (h, r) =>
h.call(r, context)
);
}
}
module.exports = StatsPrinter;

View File

@ -10,7 +10,6 @@ const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const FakeDocument = require("./helpers/FakeDocument");
const Stats = require("../lib/Stats");
const webpack = require("..");
const prepareOptions = require("./helpers/prepareOptions");
@ -110,7 +109,12 @@ describe("ConfigTestCases", () => {
webpack(options, (err, stats) => {
if (err) {
const fakeStats = {
errors: [err.stack]
errors: [
{
message: err.message,
stack: err.stack
}
]
};
if (
checkArrayExpectation(
@ -125,8 +129,10 @@ describe("ConfigTestCases", () => {
// Wait for uncaught errors to occur
return setTimeout(done, 200);
}
const statOptions = Stats.presetToOptions("verbose");
statOptions.colors = false;
const statOptions = {
preset: "verbose",
colors: false
};
mkdirp.sync(outputDirectory);
fs.writeFileSync(
path.join(outputDirectory, "stats.txt"),
@ -136,6 +142,11 @@ describe("ConfigTestCases", () => {
const jsonStats = stats.toJson({
errorDetails: true
});
fs.writeFileSync(
path.join(outputDirectory, "stats.json"),
JSON.stringify(jsonStats, null, 2),
"utf-8"
);
if (
checkArrayExpectation(
testDirectory,

View File

@ -61,14 +61,14 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(2);
expect(warnings).toHaveLength(0);
let lines = errors[0].split("\n");
expect(lines[0]).toMatch(/missingFile.js 4:0/);
expect(lines[1]).toMatch(/^Module not found/);
expect(lines[1]).toMatch(/\.\/missing/);
lines = errors[1].split("\n");
expect(lines[0]).toMatch(/missingFile.js 12:9/);
expect(lines[1]).toMatch(/^Module not found/);
expect(lines[1]).toMatch(/\.\/dir\/missing2/);
expect(errors[0].moduleName).toMatch(/missingFile.js/);
expect(errors[0].loc).toMatch(/4:0/);
expect(errors[0].message).toMatch(/^Module not found/);
expect(errors[0].message).toMatch(/\.\/missing/);
expect(errors[1].moduleName).toMatch(/missingFile.js/);
expect(errors[1].loc).toMatch(/12:9/);
expect(errors[1].message).toMatch(/^Module not found/);
expect(errors[1].message).toMatch(/\.\/dir\/missing2/);
done();
}
);
@ -82,9 +82,8 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(1);
const lines = warnings[0].split("\n");
expect(lines[0]).toMatch(/require.extensions\.js/);
expect(lines[1]).toMatch(
expect(warnings[0].moduleName).toMatch(/require.extensions\.js/);
expect(warnings[0].message).toMatch(
/require.extensions is not supported by webpack/
);
done();
@ -100,9 +99,8 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(1);
const lines = warnings[0].split("\n");
expect(lines[0]).toMatch(/require.main.require\.js/);
expect(lines[1]).toMatch(
expect(warnings[0].moduleName).toMatch(/require.main.require\.js/);
expect(warnings[0].message).toMatch(
/require.main.require is not supported by webpack/
);
done();
@ -118,9 +116,8 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(1);
const lines = warnings[0].split("\n");
expect(lines[0]).toMatch(/module.parent.require\.js/);
expect(lines[1]).toMatch(
expect(warnings[0].moduleName).toMatch(/module.parent.require\.js/);
expect(warnings[0].message).toMatch(
/module.parent.require is not supported by webpack/
);
done();
@ -136,13 +133,13 @@ describe("Errors", () => {
(errors, warnings) => {
if (errors.length === 0) {
expect(warnings).toHaveLength(1);
const lines = warnings[0].split("\n");
expect(lines[4]).toMatch(/FILE\.js/);
expect(lines[5]).toMatch(/Used by/);
expect(lines[6]).toMatch(/case-sensitive/);
expect(lines[7]).toMatch(/file\.js/);
expect(lines[8]).toMatch(/Used by/);
expect(lines[9]).toMatch(/case-sensitive/);
const lines = warnings[0].message.split("\n");
expect(lines[3]).toMatch(/FILE\.js/);
expect(lines[4]).toMatch(/Used by/);
expect(lines[5]).toMatch(/case-sensitive/);
expect(lines[6]).toMatch(/file\.js/);
expect(lines[7]).toMatch(/Used by/);
expect(lines[8]).toMatch(/case-sensitive/);
} else {
expect(errors).toHaveLength(1);
expect(warnings).toHaveLength(0);
@ -159,11 +156,10 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(1);
let lines = warnings[0].split("\n");
expect(lines[0]).toMatch(/configuration/);
expect(lines[1]).toMatch(/mode/);
expect(lines[1]).toMatch(/development/);
expect(lines[1]).toMatch(/production/);
expect(warnings[0].message).toMatch(/configuration/);
expect(warnings[0].message).toMatch(/mode/);
expect(warnings[0].message).toMatch(/development/);
expect(warnings[0].message).toMatch(/production/);
done();
}
);
@ -190,14 +186,14 @@ describe("Errors", () => {
(errors, warnings) => {
expect(errors).toHaveLength(2);
expect(warnings).toHaveLength(0);
let lines = errors[0].split("\n");
expect(lines[0]).toMatch(/missingFile.js 4:0/);
expect(lines[1]).toMatch(/^Module not found/);
expect(lines[1]).toMatch(/\.\/missing/);
lines = errors[1].split("\n");
expect(lines[0]).toMatch(/missingFile.js 12:9/);
expect(lines[1]).toMatch(/^Module not found/);
expect(lines[1]).toMatch(/\.\/dir\/missing2/);
expect(errors[0].moduleName).toMatch(/missingFile.js/);
expect(errors[0].loc).toMatch(/4:0/);
expect(errors[0].message).toMatch(/^Module not found/);
expect(errors[0].message).toMatch(/\.\/missing/);
expect(errors[1].moduleName).toMatch(/missingFile.js/);
expect(errors[1].loc).toMatch(/12:9/);
expect(errors[1].message).toMatch(/^Module not found/);
expect(errors[1].message).toMatch(/\.\/dir\/missing2/);
done();
}
);
@ -211,12 +207,12 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(warnings).toHaveLength(1);
expect(warnings[0].split("\n")[1]).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):$/
expect(warnings[0].message).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):/
);
expect(errors).toHaveLength(1);
expect(errors[0].split("\n")[1]).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):/
);
}
),
@ -227,12 +223,12 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(warnings).toHaveLength(1);
expect(warnings[0].split("\n")[1]).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):$/
expect(warnings[0].message).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):/
);
expect(errors).toHaveLength(1);
expect(errors[0].split("\n")[1]).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):/
);
}
),
@ -256,15 +252,15 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(warnings).toHaveLength(1);
expect(warnings[0].split("\n")[1]).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):$/
expect(warnings[0].message).toMatch(
/^Module Warning \(from .\/emit-error-loader.js\):/
);
expect(errors).toHaveLength(2);
expect(errors[0].split("\n")[1]).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module Error \(from .\/emit-error-loader.js\):/
);
expect(errors[1].split("\n")[1]).toMatch(
/^Module build failed \(from \(webpack\)\/node_modules\/json-loader\/index.js\):$/
expect(errors[1].message).toMatch(
/^Module build failed \(from \(webpack\)\/node_modules\/json-loader\/index.js\):/
);
}
),
@ -283,8 +279,8 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(errors).toHaveLength(1);
expect(errors[0].split("\n")[1]).toMatch(
/^Module build failed \(from .\/async-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module build failed \(from .\/async-error-loader.js\):/
);
}
),
@ -303,8 +299,8 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(errors).toHaveLength(1);
expect(errors[0].split("\n")[1]).toMatch(
/^Module build failed \(from .\/throw-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module build failed \(from .\/throw-error-loader.js\):/
);
}
),
@ -323,22 +319,22 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(warnings).toHaveLength(2);
expect(warnings[0].split("\n")[1]).toMatch(
/^Module Warning \(from .\/irregular-error-loader.js\):$/
expect(warnings[0].message).toMatch(
/^Module Warning \(from .\/irregular-error-loader.js\):/
);
expect(warnings[1].split("\n")[1]).toMatch(
/^Module Warning \(from .\/irregular-error-loader.js\):$/
expect(warnings[1].message).toMatch(
/^Module Warning \(from .\/irregular-error-loader.js\):/
);
expect(errors).toHaveLength(3);
expect(errors[0].split("\n")[1]).toMatch(
/^Module Error \(from .\/irregular-error-loader.js\):$/
expect(errors[0].message).toMatch(
/^Module Error \(from .\/irregular-error-loader.js\):/
);
expect(errors[1].split("\n")[1]).toMatch(
/^Module Error \(from .\/irregular-error-loader.js\):$/
expect(errors[1].message).toMatch(
/^Module Error \(from .\/irregular-error-loader.js\):/
);
expect(errors[2].split("\n")[1]).toMatch(
/^Module build failed \(from .\/irregular-error-loader.js\):$/
expect(errors[2].message).toMatch(
/^Module build failed \(from .\/irregular-error-loader.js\):/
);
}
)
@ -352,8 +348,7 @@ describe("Errors", () => {
},
(errors, warnings) => {
expect(errors).toHaveLength(1);
const messages = errors[0].split("\n");
expect(messages[1]).toMatch(
expect(errors[0].message).toMatch(
/^Module build failed: Error: Final loader \(.+\) didn't return a Buffer or String/
);
done();

View File

@ -1,265 +0,0 @@
"use strict";
const packageJSON = require("../package.json");
const MultiStats = require("../lib/MultiStats");
const createStat = overrides => {
return Object.assign(
{
hash: "foo",
compilation: {
name: "bar"
},
hasErrors: () => false,
hasWarnings: () => false,
toJson: () =>
Object.assign(
{
hash: "foo",
version: "version",
warnings: [],
errors: []
},
overrides
)
},
overrides
);
};
describe("MultiStats", () => {
let packageVersion, stats, myMultiStats, result;
beforeEach(() => {
packageVersion = packageJSON.version;
packageJSON.version = "1.2.3";
});
afterEach(() => {
packageJSON.version = packageVersion;
});
describe("created", () => {
beforeEach(() => {
stats = [
createStat({
hash: "abc123"
}),
createStat({
hash: "xyz890"
})
];
myMultiStats = new MultiStats(stats);
});
it("creates a hash string", () => {
expect(myMultiStats.hash).toBe("abc123xyz890");
});
});
describe("hasErrors", () => {
describe("when both have errors", () => {
beforeEach(() => {
stats = [
createStat({
hasErrors: () => true
}),
createStat({
hasErrors: () => true
})
];
myMultiStats = new MultiStats(stats);
});
it("returns true", () => {
expect(myMultiStats.hasErrors()).toBe(true);
});
});
describe("when one has an error", () => {
beforeEach(() => {
stats = [
createStat({
hasErrors: () => true
}),
createStat()
];
myMultiStats = new MultiStats(stats);
});
it("returns true", () => {
expect(myMultiStats.hasErrors()).toBe(true);
});
});
describe("when none have errors", () => {
beforeEach(() => {
stats = [createStat(), createStat()];
myMultiStats = new MultiStats(stats);
});
it("returns false", () => {
expect(myMultiStats.hasErrors()).toBe(false);
});
});
});
describe("hasWarnings", () => {
describe("when both have warnings", () => {
beforeEach(() => {
stats = [
createStat({
hasWarnings: () => true
}),
createStat({
hasWarnings: () => true
})
];
myMultiStats = new MultiStats(stats);
});
it("returns true", () => {
expect(myMultiStats.hasWarnings()).toBe(true);
});
});
describe("when one has a warning", () => {
beforeEach(() => {
stats = [
createStat({
hasWarnings: () => true
}),
createStat()
];
myMultiStats = new MultiStats(stats);
});
it("returns true", () => {
expect(myMultiStats.hasWarnings()).toBe(true);
});
});
describe("when none have warnings", () => {
beforeEach(() => {
stats = [createStat(), createStat()];
myMultiStats = new MultiStats(stats);
});
it("returns false", () => {
expect(myMultiStats.hasWarnings()).toBe(false);
});
});
});
describe("toJson", () => {
beforeEach(() => {
stats = [
createStat({
hash: "abc123",
compilation: {
name: "abc123-compilation"
},
toJson: () => ({
warnings: ["abc123-warning"],
errors: ["abc123-error"]
})
}),
createStat({
hash: "xyz890",
compilation: {
name: "xyz890-compilation"
},
toJson: () => ({
warnings: ["xyz890-warning-1", "xyz890-warning-2"],
errors: []
})
})
];
});
it("returns plain object representation", () => {
myMultiStats = new MultiStats(stats);
result = myMultiStats.toJson({
version: false,
hash: false
});
expect(result).toEqual({
errors: ["(abc123-compilation) abc123-error"],
warnings: [
"(abc123-compilation) abc123-warning",
"(xyz890-compilation) xyz890-warning-1",
"(xyz890-compilation) xyz890-warning-2"
],
children: [
{
errors: ["abc123-error"],
name: "abc123-compilation",
warnings: ["abc123-warning"]
},
{
errors: [],
name: "xyz890-compilation",
warnings: ["xyz890-warning-1", "xyz890-warning-2"]
}
]
});
});
it("returns plain object representation with json set to true", () => {
myMultiStats = new MultiStats(stats);
result = myMultiStats.toJson(true);
expect(result).toEqual({
errors: ["(abc123-compilation) abc123-error"],
warnings: [
"(abc123-compilation) abc123-warning",
"(xyz890-compilation) xyz890-warning-1",
"(xyz890-compilation) xyz890-warning-2"
],
children: [
{
warnings: ["abc123-warning"],
errors: ["abc123-error"],
name: "abc123-compilation"
},
{
warnings: ["xyz890-warning-1", "xyz890-warning-2"],
errors: [],
name: "xyz890-compilation"
}
]
});
});
});
describe("toString", () => {
beforeEach(() => {
stats = [
createStat({
hash: "abc123",
compilation: {
name: "abc123-compilation"
}
}),
createStat({
hash: "xyz890",
compilation: {
name: "xyz890-compilation"
}
})
];
myMultiStats = new MultiStats(stats);
result = myMultiStats.toString();
});
it("returns string representation", () => {
expect(result).toEqual(
"Hash: abc123xyz890\n" +
"Version: webpack 1.2.3\n" +
"Child abc123-compilation:\n" +
" Hash: abc123\n" +
"Child xyz890-compilation:\n" +
" Hash: xyz890"
);
});
});
});

View File

@ -1,191 +0,0 @@
/*globals describe it */
"use strict";
const Stats = require("../lib/Stats");
const packageJson = require("../package.json");
describe(
"Stats",
() => {
describe("Error Handling", () => {
describe("does have", () => {
it("hasErrors", () => {
const mockStats = new Stats({
children: [],
errors: ["firstError"],
hash: "1234",
compiler: {
context: ""
}
});
expect(mockStats.hasErrors()).toBe(true);
});
it("hasWarnings", () => {
const mockStats = new Stats({
children: [],
warnings: ["firstError"],
hash: "1234",
compiler: {
context: ""
}
});
expect(mockStats.hasWarnings()).toBe(true);
});
});
describe("does not have", () => {
it("hasErrors", () => {
const mockStats = new Stats({
children: [],
errors: [],
hash: "1234",
compiler: {
context: ""
}
});
expect(mockStats.hasErrors()).toBe(false);
});
it("hasWarnings", () => {
const mockStats = new Stats({
children: [],
warnings: [],
hash: "1234",
compiler: {
context: ""
}
});
expect(mockStats.hasWarnings()).toBe(false);
});
});
describe("children have", () => {
it("hasErrors", () => {
const mockStats = new Stats({
children: [
{
getStats: () =>
new Stats({
errors: ["firstError"],
hash: "5678"
})
}
],
errors: [],
hash: "1234"
});
expect(mockStats.hasErrors()).toBe(true);
});
it("hasWarnings", () => {
const mockStats = new Stats({
children: [
{
getStats: () =>
new Stats({
warnings: ["firstError"],
hash: "5678"
})
}
],
warnings: [],
hash: "1234"
});
expect(mockStats.hasWarnings()).toBe(true);
});
});
it("formatError handles string errors", () => {
const mockStats = new Stats({
errors: ["firstError"],
warnings: [],
assets: [],
entrypoints: new Map(),
namedChunkGroups: new Map(),
chunks: [],
modules: new Set(),
children: [],
hash: "1234",
mainTemplate: {
outputOptions: {
path: ""
},
getPublicPath: () => "path"
},
compiler: {
context: ""
}
});
const obj = mockStats.toJson();
expect(obj.errors[0]).toEqual("firstError");
});
});
describe("toJson", () => {
it("returns plain object representation", () => {
const mockStats = new Stats({
errors: [],
warnings: [],
assets: [],
entrypoints: new Map(),
chunks: [],
namedChunkGroups: new Map(),
modules: new Set(),
children: [],
hash: "1234",
mainTemplate: {
outputOptions: {
path: "/"
},
getPublicPath: () => "path"
},
compiler: {
context: ""
}
});
const result = mockStats.toJson();
expect(result).toEqual({
assets: [],
assetsByChunkName: {},
children: [],
chunks: [],
entrypoints: {},
namedChunkGroups: {},
filteredAssets: 0,
filteredModules: 0,
errors: [],
hash: "1234",
modules: [],
outputPath: "/",
publicPath: "path",
version: packageJson.version,
warnings: []
});
});
});
describe("Presets", () => {
describe("presetToOptions", () => {
it("returns correct object with 'Normal'", () => {
expect(Stats.presetToOptions("Normal")).toEqual({});
});
it("truthy values behave as 'normal'", () => {
const normalOpts = Stats.presetToOptions("normal");
expect(Stats.presetToOptions("pizza")).toEqual(normalOpts);
expect(Stats.presetToOptions(true)).toEqual(normalOpts);
expect(Stats.presetToOptions(1)).toEqual(normalOpts);
expect(Stats.presetToOptions("verbose")).not.toEqual(normalOpts);
expect(Stats.presetToOptions(false)).not.toEqual(normalOpts);
});
it("returns correct object with 'none'", () => {
expect(Stats.presetToOptions("none")).toEqual({
all: false
});
});
it("falsy values behave as 'none'", () => {
const noneOpts = Stats.presetToOptions("none");
expect(Stats.presetToOptions("")).toEqual(noneOpts);
expect(Stats.presetToOptions(null)).toEqual(noneOpts);
expect(Stats.presetToOptions()).toEqual(noneOpts);
expect(Stats.presetToOptions(0)).toEqual(noneOpts);
expect(Stats.presetToOptions(false)).toEqual(noneOpts);
});
});
});
},
10000
);

View File

@ -5,7 +5,6 @@ const path = require("path");
const fs = require("fs");
const webpack = require("..");
const Stats = require("../lib/Stats");
const base = path.join(__dirname, "statsCases");
const outputBase = path.join(__dirname, "js", "stats");
@ -99,10 +98,10 @@ describe("StatsTestCases", () => {
if (typeof options.stats !== "undefined") {
toStringOptions = options.stats;
if (toStringOptions === null || typeof toStringOptions !== "object")
toStringOptions = Stats.presetToOptions(toStringOptions);
hasColorSetting = typeof options.stats.colors !== "undefined";
toStringOptions = { preset: toStringOptions };
if (!toStringOptions.context)
toStringOptions.context = path.join(base, testName);
hasColorSetting = typeof toStringOptions.colors !== "undefined";
}
if (Array.isArray(options) && !toStringOptions.children) {
toStringOptions.children = options.map(o => o.stats);

View File

@ -10,7 +10,6 @@ const TerserPlugin = require("terser-webpack-plugin");
const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const Stats = require("../lib/Stats");
const FileCachePlugin = require("../lib/cache/FileCachePlugin");
const webpack = require("..");
@ -217,8 +216,10 @@ const describeCases = config => {
done => {
const compiler = webpack(options, (err, stats) => {
if (err) return done(err);
const statOptions = Stats.presetToOptions("verbose");
statOptions.colors = false;
const statOptions = {
preset: "verbose",
colors: false
};
mkdirp.sync(outputDirectory);
fs.writeFileSync(
path.join(outputDirectory, "stats.txt"),

View File

@ -10,7 +10,6 @@ const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const { remove } = require("./helpers/remove");
const Stats = require("../lib/Stats");
const webpack = require("..");
function copyDiff(src, dest, initial) {
@ -183,8 +182,10 @@ describe("WatchTestCases", () => {
if (waitMode) return;
run.done = true;
if (err) return compilationFinished(err);
const statOptions = Stats.presetToOptions("verbose");
statOptions.colors = false;
const statOptions = {
preset: "verbose",
colors: false
};
mkdirp.sync(outputDirectory);
fs.writeFileSync(
path.join(outputDirectory, "stats.txt"),

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,6 @@
module.exports = [
[/Module build failed( \(from [^)]+\))?:\nMessage\nStack/]
[
/Module build failed( \(from [^)]+\))?:\nMessage/,
{details: /Stack/}
]
];

View File

@ -1,10 +1,10 @@
module.exports = [
[
/\.\/loaders\/no-string\/file\.js \(\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/file\.js\)/,
{moduleName: /\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/file\.js/},
/Module build failed: Error: Final loader \(\.\/loaders\/no-string\/loader\.js\) didn't return a Buffer or String/
],
[
/\.\/loaders\/no-string\/file\.js \(\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/pitch-loader\.js!\.\/loaders\/no-string\/file\.js\)/,
{moduleName: /\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/pitch-loader\.js!\.\/loaders\/no-string\/file\.js/},
/Module build failed: Error: Final loader \(\.\/loaders\/no-string\/loader\.js\) didn't return a Buffer or String/
]
];

View File

@ -1,6 +1,6 @@
module.exports = [
[/Module parse failed/, /dump-file\.txt/, /templates sync/],
[/Critical dependency/, /templateLoader\.js/],
[/Critical dependency/, /templateLoaderIndirect\.js/],
[/Critical dependency/, /templateLoaderIndirect\.js/],
[/Module parse failed/, {moduleName: /dump-file\.txt/}, {moduleTrace: /templates sync/}],
[/Critical dependency/, {moduleName: /templateLoader\.js/}],
[/Critical dependency/, {moduleName: /templateLoaderIndirect\.js/}],
[/Critical dependency/, {moduleName: /templateLoaderIndirect\.js/}],
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module not found/, /Can't resolve '\.\/b' /, /b\.js/]
[/Module not found/, /Can't resolve '\.\/b' /, {details: /b\.js/}]
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module not found/, /Can't resolve '\.\/b' /, /b\.js/]
[/Module not found/, /Can't resolve '\.\/b' /, {details: /b\.js/}]
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module not found/, /Can't resolve '\.\/missingModule' /, /extract-require\/index.js/]
];
[/Module not found/, /Can't resolve '\.\/missingModule' /, {moduleName: /extract-require\/index.js/}]
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module not found/, /Can't resolve '\.\/missingModule' /, /error-handling\/index.js/]
];
[/Module not found/, /Can't resolve '\.\/missingModule' /, {moduleName: /error-handling\/index.js/}]
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module not found/, /Can't resolve '\.\/missingModule2' /, /error-handling\/index.js/]
];
[/Module not found/, /Can't resolve '\.\/missingModule2' /, {moduleName: /error-handling\/index.js/}]
];

View File

@ -1,17 +1,17 @@
module.exports = [
[
/export-i64-param\.wat/,
{moduleName: /export-i64-param\.wat/},
/Export "a" with i64 as parameter can only be used for direct wasm to wasm dependencies/,
/export-i64-param\.js/
{moduleTrace: /export-i64-param\.js/}
],
[
/export-i64-result\.wat/,
{moduleName: /export-i64-result\.wat/},
/Export "a" with i64 as result can only be used for direct wasm to wasm dependencies/,
/export-i64-result\.js/
{moduleTrace: /export-i64-result\.js/}
],
[
/import-i64\.wat/,
{moduleName: /import-i64\.wat/},
/Import "n" from "\.\/env.js" with Non-JS-compatible Global Type \(i64\) can only be used for direct wasm to wasm dependencies/,
/index\.js/
{moduleTrace: /index\.js/}
]
]

View File

@ -2,6 +2,34 @@
const fs = require("fs");
const path = require("path");
const check = (expected, actual) => {
if(expected instanceof RegExp) {
expected = { message: expected };
}
return Object.keys(expected).every(key => {
let value = actual[key];
if(typeof value === "object") {
value = JSON.stringify(value);
}
return expected[key].test(value);
})
}
const explain = object => {
if(object instanceof RegExp) {
object = { message: object };
}
return Object.keys(object).map(key => {
let value = object[key];
if(typeof value === "object" && !(value instanceof RegExp)) {
value = JSON.stringify(value);
}
let msg = `${key} = ${value}`
if(msg.length > 100) msg = msg.slice(0, 97) + "...";
return msg;
}).join("; ");
}
module.exports = function checkArrayExpectation(
testDirectory,
object,
@ -46,25 +74,25 @@ module.exports = function checkArrayExpectation(
for (let i = 0; i < array.length; i++) {
if (Array.isArray(expected[i])) {
for (let j = 0; j < expected[i].length; j++) {
if (!expected[i][j].test(array[i]))
if (!check(expected[i][j], array[i]))
return (
done(
new Error(
`${upperCaseKind} ${i}: ${array[i]} doesn't match ${expected[
`${upperCaseKind} ${i}: ${explain(array[i])} doesn't match ${explain(expected[
i
][j].toString()}`
][j])}`
)
),
true
);
}
} else if (!expected[i].test(array[i]))
} else if (!check(expected[i], array[i]))
return (
done(
new Error(
`${upperCaseKind} ${i}: ${array[i]} doesn't match ${expected[
`${upperCaseKind} ${i}: ${explain(array[i])} doesn't match ${explain(expected[
i
].toString()}`
])}`
)
),
true

View File

@ -1,3 +1,3 @@
module.exports = [
[/Module parse failed/, /dump-file\.txt/, /templates sync \^\\\.\\\//]
[/Module parse failed/, {moduleName: /dump-file\.txt/}, {moduleTrace: /templates sync/}]
];

View File

@ -1,3 +1,3 @@
module.exports = [
[/system_true/, /System\.import\(\) is deprecated/]
[{compilerPath: /system_true/}, /System\.import\(\) is deprecated/]
];

View File

@ -26,7 +26,7 @@ modules.forEach(module => {
if (module.variables.indexOf(variable) === -1) {
// the module doesn't include the env variable, an error is expected when requiring the variable
regex.push([
new RegExp(`(${module.name})`),
{compilerPath: new RegExp(`${module.name}`)},
new RegExp(`Can't resolve '${variable}'`),
]);
}

View File

@ -1,3 +1,3 @@
module.exports = [
[/(ddd)/, /DDD environment variable is undefined./]
[{compilerPath: /ddd/}, /DDD environment variable is undefined./]
];

View File

@ -9,7 +9,7 @@ module.exports = {
hash: false,
entrypoints: false,
assets: false,
errorDetails: true,
errorDetails: false,
moduleTrace: true
}
};