Merge tag 'v4.37.0' into next

4.37.0
This commit is contained in:
Tobias Koppers 2019-07-24 10:51:04 +02:00
commit c17dfde7a8
30 changed files with 1339 additions and 125 deletions

View File

@ -75,6 +75,16 @@ export type ExternalItem =
* via the `definition` "ArrayOfStringValues".
*/
export type ArrayOfStringValues = string[];
/**
* This interface was referenced by `WebpackOptions`'s JSON-Schema
* via the `definition` "FilterTypes".
*/
export type FilterTypes = FilterItemTypes | FilterItemTypes[];
/**
* This interface was referenced by `WebpackOptions`'s JSON-Schema
* via the `definition` "FilterItemTypes".
*/
export type FilterItemTypes = RegExp | string | ((value: string) => boolean);
/**
* One or multiple rule conditions
*
@ -256,16 +266,6 @@ export type OptimizationSplitChunksSizes =
*/
[k: string]: number;
};
/**
* This interface was referenced by `WebpackOptions`'s JSON-Schema
* via the `definition` "FilterTypes".
*/
export type FilterTypes = FilterItemTypes | FilterItemTypes[];
/**
* This interface was referenced by `WebpackOptions`'s JSON-Schema
* via the `definition` "FilterItemTypes".
*/
export type FilterItemTypes = RegExp | string | Function;
export interface WebpackOptions {
/**
@ -314,6 +314,19 @@ export interface WebpackOptions {
* Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on `output.libraryTarget`.
*/
externals?: Externals;
/**
* Options for infrastructure level logging
*/
infrastructureLogging?: {
/**
* Enable debug logging for specific loggers
*/
debug?: FilterTypes | boolean;
/**
* Log level
*/
level?: "none" | "error" | "warn" | "info" | "log" | "verbose";
};
/**
* Custom values available in the loader context.
*/
@ -1435,6 +1448,18 @@ export interface StatsOptions {
* add the hash of the compilation
*/
hash?: boolean;
/**
* add logging output
*/
logging?: boolean | ("none" | "error" | "warn" | "info" | "log" | "verbose");
/**
* Include debug logging of specified loggers (i. e. for plugins or loaders). Filters can be Strings, RegExps or Functions
*/
loggingDebug?: FilterTypes | boolean;
/**
* add stack traces to logging output
*/
loggingTrace?: boolean;
/**
* Set the maximum number of modules to be shown
*/

View File

@ -23,6 +23,7 @@ const ChunkRenderError = require("./ChunkRenderError");
const ChunkTemplate = require("./ChunkTemplate");
const DependencyTemplates = require("./DependencyTemplates");
const Entrypoint = require("./Entrypoint");
const ErrorHelpers = require("./ErrorHelpers");
const FileSystemInfo = require("./FileSystemInfo");
const {
connectChunkGroupAndChunk,
@ -41,6 +42,7 @@ const RuntimeGlobals = require("./RuntimeGlobals");
const RuntimeTemplate = require("./RuntimeTemplate");
const Stats = require("./Stats");
const WebpackError = require("./WebpackError");
const { Logger, LogType } = require("./logging/Logger");
const StatsFactory = require("./stats/StatsFactory");
const StatsPrinter = require("./stats/StatsPrinter");
const AsyncQueue = require("./util/AsyncQueue");
@ -127,6 +129,14 @@ const { arrayToSetDeprecation } = require("./util/deprecation");
* @property {(Record<string, (length: number) => string>)=} contentHashWithLength
*/
/**
* @typedef {Object} LogEntry
* @property {string} type
* @property {any[]} args
* @property {number} time
* @property {string[]=} trace
*/
/**
* @typedef {Object} ModulePathData
* @property {string|number} id
@ -405,6 +415,9 @@ class Compilation {
"compilerIndex"
]),
/** @type {SyncBailHook<[string, LogEntry], true>} */
log: new SyncBailHook(["origin", "logEntry"]),
/** @type {HookMap<SyncHook<[Object, Object]>>} */
statsPreset: new HookMap(() => new SyncHook(["options", "context"])),
/** @type {SyncHook<[Object, Object]>} */
@ -520,6 +533,8 @@ class Compilation {
this.warnings = [];
/** @type {Compilation[]} */
this.children = [];
/** @type {Map<string, LogEntry[]>} */
this.logging = new Map();
/** @type {Map<DepConstructor, ModuleFactory>} */
this.dependencyFactories = new Map();
/** @type {DependencyTemplates} */
@ -582,6 +597,69 @@ class Compilation {
return statsPrinter;
}
/**
* @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
* @returns {Logger} a logger with that name
*/
getLogger(name) {
if (!name) {
throw new TypeError("Compilation.getLogger(name) called without a name");
}
/** @type {LogEntry[] | undefined} */
let logEntries;
return new Logger((type, args) => {
if (typeof name === "function") {
name = name();
if (!name) {
throw new TypeError(
"Compilation.getLogger(name) called with a function not returning a name"
);
}
}
let trace;
switch (type) {
case LogType.warn:
case LogType.error:
case LogType.trace:
trace = ErrorHelpers.cutOffLoaderExecution(new Error("Trace").stack)
.split("\n")
.slice(3);
break;
}
/** @type {LogEntry} */
const logEntry = {
time: Date.now(),
type,
args,
trace
};
if (this.hooks.log.call(name, logEntry) === undefined) {
if (logEntry.type === LogType.profileEnd) {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profileEnd === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profileEnd(`[${name}] ${logEntry.args[0]}`);
}
}
if (logEntries === undefined) {
logEntries = this.logging.get(name);
if (logEntries === undefined) {
logEntries = [];
this.logging.set(name, logEntries);
}
}
logEntries.push(logEntry);
if (logEntry.type === LogType.profile) {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profile === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profile(`[${name}] ${logEntry.args[0]}`);
}
}
}
});
}
/**
* @param {Module} module module to be added that was created
* @param {ModuleCallback} callback returns the module in the compilation,

View File

@ -24,6 +24,7 @@ const RequestShortener = require("./RequestShortener");
const ResolverFactory = require("./ResolverFactory");
const Stats = require("./Stats");
const Watching = require("./Watching");
const { Logger } = require("./logging/Logger");
const { join, dirname, mkdirp } = require("./util/fs");
const { makePathsRelative } = require("./util/identifier");
@ -133,6 +134,9 @@ class Compiler {
/** @type {SyncHook<[]>} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<[string, string, any[]], true>} */
infrastructurelog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook<[]>} */
@ -178,6 +182,8 @@ class Compiler {
/** @type {ResolverFactory} */
this.resolverFactory = new ResolverFactory();
this.infrastructureLogger = undefined;
/** @type {WebpackOptions} */
this.options = /** @type {WebpackOptions} */ ({});
@ -201,6 +207,33 @@ class Compiler {
this._assetEmittingWrittenFiles = new Map();
}
/**
* @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
* @returns {Logger} a logger with that name
*/
getInfrastructureLogger(name) {
if (!name) {
throw new TypeError(
"Compiler.getInfrastructureLogger(name) called without a name"
);
}
return new Logger((type, args) => {
if (typeof name === "function") {
name = name();
if (!name) {
throw new TypeError(
"Compiler.getInfrastructureLogger(name) called with a function not returning a name"
);
}
}
if (this.hooks.infrastructurelog.call(name, type, args) === undefined) {
if (this.infrastructureLogger !== undefined) {
this.infrastructureLogger(name, type, args);
}
}
});
}
/**
* @param {WatchOptions} watchOptions the watcher's options
* @param {Callback<Stats>} handler signals when the call finishes

View File

@ -248,16 +248,20 @@ class NormalModule extends Module {
createLoaderContext(resolver, options, compilation, fs) {
const requestShortener = compilation.runtimeTemplate.requestShortener;
const getCurrentLoaderName = () => {
const currentLoader = this.getCurrentLoader(loaderContext);
if (!currentLoader) return "(not in loader scope)";
return requestShortener.shorten(currentLoader.loader);
};
const loaderContext = {
version: 2,
emitWarning: warning => {
if (!(warning instanceof Error)) {
warning = new NonErrorEmittedError(warning);
}
const currentLoader = this.getCurrentLoader(loaderContext);
this.warnings.push(
new ModuleWarning(warning, {
from: requestShortener.shorten(currentLoader.loader)
from: getCurrentLoaderName()
})
);
},
@ -265,13 +269,20 @@ class NormalModule extends Module {
if (!(error instanceof Error)) {
error = new NonErrorEmittedError(error);
}
const currentLoader = this.getCurrentLoader(loaderContext);
this.errors.push(
new ModuleError(error, {
from: requestShortener.shorten(currentLoader.loader)
from: getCurrentLoaderName()
})
);
},
getLogger: name => {
const currentLoader = this.getCurrentLoader(loaderContext);
return compilation.getLogger(() =>
[currentLoader && currentLoader.loader, name, this.identifier()]
.filter(Boolean)
.join("|")
);
},
resolve(context, request, callback) {
resolver.resolve({}, context, request, {}, callback);
},

View File

@ -443,6 +443,12 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
this.set("resolveLoader.mainFields", ["loader", "main"]);
this.set("resolveLoader.extensions", [".js"]);
this.set("resolveLoader.mainFiles", ["index"]);
this.set("infrastructureLogging", "call", value =>
Object.assign({}, value)
);
this.set("infrastructureLogging.level", "info");
this.set("infrastructureLogging.debug", false);
}
}

126
lib/logging/Logger.js Normal file
View File

@ -0,0 +1,126 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/**
* @enum {string}
*/
const LogType = Object.freeze({
error: "error", // message, c style arguments
warn: "warn", // message, c style arguments
info: "info", // message, c style arguments
log: "log", // message, c style arguments
debug: "debug", // message, c style arguments
trace: "trace", // no arguments
group: "group", // [label]
groupCollapsed: "groupCollapsed", // [label]
groupEnd: "groupEnd", // [label]
profile: "profile", // [profileName]
profileEnd: "profileEnd", // [profileName]
time: "time", // name, time as [seconds, nanoseconds]
clear: "clear" // no arguments
});
exports.LogType = LogType;
/** @typedef {LogType} LogTypeEnum */
const LOG_SYMBOL = Symbol("webpack logger raw log method");
const TIMERS_SYMBOL = Symbol("webpack logger times");
class WebpackLogger {
/**
* @param {function(LogType, any[]=): void} log log function
*/
constructor(log) {
this[LOG_SYMBOL] = log;
}
error(...args) {
this[LOG_SYMBOL](LogType.error, args);
}
warn(...args) {
this[LOG_SYMBOL](LogType.warn, args);
}
info(...args) {
this[LOG_SYMBOL](LogType.info, args);
}
log(...args) {
this[LOG_SYMBOL](LogType.log, args);
}
debug(...args) {
this[LOG_SYMBOL](LogType.debug, args);
}
assert(assertion, ...args) {
if (!assertion) {
this[LOG_SYMBOL](LogType.error, args);
}
}
trace() {
this[LOG_SYMBOL](LogType.trace, ["Trace"]);
}
clear() {
this[LOG_SYMBOL](LogType.clear);
}
group(...args) {
this[LOG_SYMBOL](LogType.group, args);
}
groupCollapsed(...args) {
this[LOG_SYMBOL](LogType.groupCollapsed, args);
}
groupEnd(...args) {
this[LOG_SYMBOL](LogType.groupEnd, args);
}
profile(label) {
this[LOG_SYMBOL](LogType.profile, [label]);
}
profileEnd(label) {
this[LOG_SYMBOL](LogType.profileEnd, [label]);
}
time(label) {
this[TIMERS_SYMBOL] = this[TIMERS_SYMBOL] || new Map();
this[TIMERS_SYMBOL].set(label, process.hrtime());
}
timeLog(label) {
const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label);
if (!prev) {
throw new Error(`No such label '${label}' for WebpackLogger.timeLog()`);
}
const time = process.hrtime(prev);
this[LOG_SYMBOL](LogType.time, [label, ...time]);
}
timeEnd(label) {
const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label);
if (!prev) {
throw new Error(`No such label '${label}' for WebpackLogger.timeEnd()`);
}
const time = process.hrtime(prev);
this[TIMERS_SYMBOL].delete(label);
this[LOG_SYMBOL](LogType.time, [label, ...time]);
}
}
exports.Logger = WebpackLogger;

View File

@ -0,0 +1,188 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { LogType } = require("./Logger");
/** @typedef {import("../../declarations/WebpackOptions").FilterItemTypes} FilterItemTypes */
/** @typedef {import("../../declarations/WebpackOptions").FilterTypes} FilterTypes */
/** @typedef {import("./Logger").LogTypeEnum} LogTypeEnum */
/** @typedef {function(string): boolean} FilterFunction */
/**
* @typedef {Object} LoggerOptions
* @property {false|true|"none"|"error"|"warn"|"info"|"log"|"verbose"} options.level loglevel
* @property {FilterTypes|boolean} options.debug filter for debug logging
*/
/**
* @param {FilterItemTypes} item an input item
* @returns {FilterFunction} filter funtion
*/
const filterToFunction = 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;
}
};
/**
* @enum {number} */
const LogLevel = {
none: 6,
false: 6,
error: 5,
warn: 4,
info: 3,
log: 2,
true: 2,
verbose: 1
};
/**
* @param {LoggerOptions} options options object
* @returns {function(string, LogTypeEnum, any[]): void} logging function
*/
module.exports = ({ level = "info", debug = false }) => {
const debugFilters =
typeof debug === "boolean"
? [() => debug]
: /** @type {FilterItemTypes[]} */ ([])
.concat(debug)
.map(filterToFunction);
/** @type {number} */
const loglevel = LogLevel[`${level}`] || 0;
/**
* @param {string} name name of the logger
* @param {LogTypeEnum} type type of the log entry
* @param {any[]} args arguments of the log entry
* @returns {void}
*/
const logger = (name, type, args) => {
const labeledArgs = (prefix = "") => {
if (Array.isArray(args)) {
if (args.length > 0 && typeof args[0] === "string") {
return [`${prefix}[${name}] ${args[0]}`, ...args.slice(1)];
} else {
return [`${prefix}[${name}]`, ...args];
}
} else {
return [];
}
};
const debug = debugFilters.some(f => f(name));
switch (type) {
case LogType.debug:
if (!debug) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.debug === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.debug(...labeledArgs());
} else {
console.log(...labeledArgs());
}
break;
case LogType.log:
if (!debug && loglevel > LogLevel.log) return;
console.log(...labeledArgs());
break;
case LogType.info:
if (!debug && loglevel > LogLevel.info) return;
console.info(...labeledArgs("<i> "));
break;
case LogType.warn:
if (!debug && loglevel > LogLevel.warn) return;
console.warn(...labeledArgs("<w> "));
break;
case LogType.error:
if (!debug && loglevel > LogLevel.error) return;
console.error(...labeledArgs("<e> "));
break;
case LogType.trace:
if (!debug) return;
console.trace();
break;
case LogType.group:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.group === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.group(...labeledArgs());
} else {
console.log(...labeledArgs());
}
break;
case LogType.groupCollapsed:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.groupCollapsed === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.groupCollapsed(...labeledArgs());
} else {
console.log(...labeledArgs("<g> "));
}
break;
case LogType.groupEnd:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.groupEnd === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.groupEnd();
} else {
console.log(...labeledArgs("</g> "));
}
break;
case LogType.time:
if (!debug && loglevel > LogLevel.log) return;
console.log(
`[${name}] ${args[0]}: ${args[1] * 1000 + args[2] / 1000000}ms`
);
break;
case LogType.profile:
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profile === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profile(...labeledArgs());
}
break;
case LogType.profileEnd:
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.profileEnd === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.profileEnd(...labeledArgs());
}
break;
case LogType.clear:
if (!debug && loglevel > LogLevel.log) return;
// eslint-disable-next-line node/no-unsupported-features/node-builtins
if (typeof console.clear === "function") {
// eslint-disable-next-line node/no-unsupported-features/node-builtins
console.clear();
}
break;
default:
throw new Error(`Unexpected LogType ${type}`);
}
};
return logger;
};

42
lib/logging/runtime.js Normal file
View File

@ -0,0 +1,42 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const SyncBailHook = require("tapable/lib/SyncBailHook");
const { Logger } = require("./Logger");
const createConsoleLogger = require("./createConsoleLogger");
/** @type {createConsoleLogger.LoggerOptions} */
let currentDefaultLoggerOptions = {
level: "info",
debug: false
};
let currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions);
/**
* @param {string} name name of the logger
* @returns {Logger} a logger
*/
exports.getLogger = name => {
return new Logger((type, args) => {
if (exports.hooks.log.call(name, type, args) === undefined) {
currentDefaultLogger(name, type, args);
}
});
};
/**
* @param {createConsoleLogger.LoggerOptions} options new options, merge with old options
* @returns {void}
*/
exports.configureDefaultLogger = options => {
Object.assign(currentDefaultLoggerOptions, options);
currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions);
};
exports.hooks = {
log: new SyncBailHook(["origin", "type", "args"])
};

View File

@ -7,16 +7,30 @@
const CachedInputFileSystem = require("enhanced-resolve/lib/CachedInputFileSystem");
const fs = require("graceful-fs");
const createConsoleLogger = require("../logging/createConsoleLogger");
const NodeWatchFileSystem = require("./NodeWatchFileSystem");
/** @typedef {import("../Compiler")} Compiler */
class NodeEnvironmentPlugin {
constructor(options) {
this.options = options || {};
}
/**
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.infrastructureLogger = createConsoleLogger(
Object.assign(
{
level: "info",
debug: false
},
this.options.infrastructureLogging
)
);
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
const inputFileSystem = compiler.inputFileSystem;
compiler.outputFileSystem = fs;

View File

@ -6,6 +6,7 @@
"use strict";
const formatLocation = require("../formatLocation");
const { LogType } = require("../logging/Logger");
const AggressiveSplittingPlugin = require("../optimize/AggressiveSplittingPlugin");
const ConcatenatedModule = require("../optimize/ConcatenatedModule");
const SizeLimitsPlugin = require("../performance/SizeLimitsPlugin");
@ -19,6 +20,7 @@ const {
compareModulesById,
compareModulesByIdOrIdentifier
} = require("../util/comparators");
const identifierUtils = require("../util/identifier");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Chunk")} Chunk */
@ -46,6 +48,7 @@ const {
/**
* @typedef {Object} UsualOptions
* @property {string} context
* @property {RequestShortener} requestShortener
* @property {string} chunksSort
* @property {string} modulesSort
@ -54,6 +57,9 @@ const {
* @property {Function[]} excludeModules
* @property {Function[]} warningsFilter
* @property {number} maxModules
* @property {false|"none"|"error"|"warn"|"info"|"log"|"verbose"} logging
* @property {Function[]} loggingDebug
* @property {boolean} loggingTrace
* @property {any} _env
*/
@ -280,6 +286,128 @@ const SIMPLE_EXTRACTORS = {
context
);
},
logging: (object, compilation, _context, options, factory) => {
const util = require("util");
const { loggingDebug, loggingTrace, context } = options;
object.logging = {};
let acceptedTypes;
let collapsedGroups = false;
switch (options.logging) {
case "none":
acceptedTypes = new Set([]);
break;
case "error":
acceptedTypes = new Set([LogType.error]);
break;
case "warn":
acceptedTypes = new Set([LogType.error, LogType.warn]);
break;
case "info":
acceptedTypes = new Set([LogType.error, LogType.warn, LogType.info]);
break;
case "log":
acceptedTypes = new Set([
LogType.error,
LogType.warn,
LogType.info,
LogType.log,
LogType.group,
LogType.groupEnd,
LogType.groupCollapsed,
LogType.clear
]);
break;
case "verbose":
acceptedTypes = new Set([
LogType.error,
LogType.warn,
LogType.info,
LogType.log,
LogType.group,
LogType.groupEnd,
LogType.groupCollapsed,
LogType.profile,
LogType.profileEnd,
LogType.time,
LogType.clear
]);
collapsedGroups = true;
break;
}
let depthInCollapsedGroup = 0;
for (const [origin, logEntries] of compilation.logging) {
const debugMode = loggingDebug.some(fn => fn(origin));
const groupStack = [];
const rootList = [];
let currentList = rootList;
let processedLogEntries = 0;
for (const entry of logEntries) {
let type = entry.type;
if (!debugMode && !acceptedTypes.has(type)) continue;
// Expand groups in verbose and debug modes
if (type === LogType.groupCollapsed && (debugMode || collapsedGroups))
type = LogType.group;
if (depthInCollapsedGroup === 0) {
processedLogEntries++;
}
if (type === LogType.groupEnd) {
groupStack.pop();
if (groupStack.length > 0) {
currentList = groupStack[groupStack.length - 1].children;
} else {
currentList = rootList;
}
if (depthInCollapsedGroup > 0) depthInCollapsedGroup--;
continue;
}
let message = undefined;
if (entry.type === LogType.time) {
message = `${entry.args[0]}: ${entry.args[1] * 1000 +
entry.args[2] / 1000000}ms`;
} else if (entry.args && entry.args.length > 0) {
message = util.format(entry.args[0], ...entry.args.slice(1));
}
const newEntry = {
...entry,
type,
message,
trace: loggingTrace ? entry.trace : undefined,
children:
type === LogType.group || type === LogType.groupCollapsed
? []
: undefined
};
currentList.push(newEntry);
if (newEntry.children) {
groupStack.push(newEntry);
currentList = newEntry.children;
if (depthInCollapsedGroup > 0) {
depthInCollapsedGroup++;
} else if (type === LogType.groupCollapsed) {
depthInCollapsedGroup = 1;
}
}
}
let name = identifierUtils
.makePathsRelative(context, origin, compilation.cache)
.replace(/\|/g, " ");
if (name in object.logging) {
let i = 1;
while (`${name}#${i}` in object.logging) {
i++;
}
name = `${name}#${i}`;
}
object.logging[name] = {
entries: rootList,
filteredEntries: logEntries.length - processedLogEntries,
debug: debugMode
};
}
},
children: (object, compilation, context, options, factory) => {
const { type } = context;
object.children = factory.create(

View File

@ -36,6 +36,7 @@ const NAMED_PRESETS = {
optimizationBailout: true,
errorDetails: true,
publicPath: true,
logging: "verbose",
orphanModules: true,
runtime: true,
exclude: false,
@ -55,6 +56,7 @@ const NAMED_PRESETS = {
optimizationBailout: true,
errorDetails: true,
publicPath: true,
logging: true,
runtimeModules: true,
runtime: true,
exclude: false,
@ -65,17 +67,20 @@ const NAMED_PRESETS = {
modules: true,
maxModules: 0,
errors: true,
warnings: true
warnings: true,
logging: "warn"
},
"errors-only": {
all: false,
errors: true,
moduleTrace: true
moduleTrace: true,
logging: "error"
},
"errors-warnings": {
all: false,
errors: true,
warnings: true
warnings: true,
logging: "warn"
},
none: {
all: false
@ -132,6 +137,10 @@ const DEFAULTS = {
errorDetails: OFF_FOR_TO_STRING,
warnings: NORMAL_ON,
publicPath: OFF_FOR_TO_STRING,
logging: ({ all }, { forToString }) =>
(forToString && all !== false ? "info" : false),
loggingDebug: () => [],
loggingTrace: OFF_FOR_TO_STRING,
excludeModules: () => [],
excludeAssets: () => [],
maxModules: (o, { forToString }) => ((forToString ? 15 : Infinity)),
@ -142,7 +151,7 @@ const DEFAULTS = {
colors: () => false
};
const normalizeExclude = item => {
const normalizeFilter = item => {
if (typeof item === "string") {
const regExp = new RegExp(
`[\\\\/]${item.replace(
@ -169,13 +178,13 @@ const NORMALIZER = {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(normalizeExclude);
return value.map(normalizeFilter);
},
excludeAssets: value => {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(normalizeExclude);
return value.map(normalizeFilter);
},
warningsFilter: value => {
if (!Array.isArray(value)) {
@ -195,6 +204,16 @@ const NORMALIZER = {
`Can only filter warnings with Strings or RegExps. (Given: ${filter})`
);
});
},
logging: value => {
if (value === true) value = "log";
return value;
},
loggingDebug: value => {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(normalizeFilter);
}
};

View File

@ -43,6 +43,12 @@ const printSizes = (sizes, { formatSize }) => {
}
};
const mapLines = (str, fn) =>
str
.split("\n")
.map(fn)
.join("\n");
/**
* @param {number} n a number
* @returns {string} number as two digit string, leading 0
@ -130,6 +136,14 @@ const SIMPLE_PRINTERS = {
: filteredAssets
} ${plural(filteredAssets, "asset", "assets")}`
: undefined,
"compilation.logging": (logging, context, printer) =>
Array.isArray(logging)
? undefined
: printer.print(
context.type,
Object.entries(logging).map(([name, value]) => ({ ...value, name })),
context
),
"compilation.children[].compilation.name": name =>
name ? `Child ${name}:` : "Child",
@ -402,12 +416,45 @@ const SIMPLE_PRINTERS = {
"error.moduleTrace": moduleTrace => undefined,
"error.separator!": () => "\n",
"loggingEntry(error).loggingEntry.message": (message, { red }) =>
mapLines(message, x => `<e> ${red(x)}`),
"loggingEntry(warn).loggingEntry.message": (message, { yellow }) =>
mapLines(message, x => `<w> ${yellow(x)}`),
"loggingEntry(info).loggingEntry.message": (message, { green }) =>
mapLines(message, x => `<i> ${green(x)}`),
"loggingEntry(log).loggingEntry.message": (message, { bold }) =>
mapLines(message, x => bold(x)),
"loggingEntry(debug).loggingEntry.message": message => message,
"loggingEntry(trace).loggingEntry.message": message => message,
"loggingEntry(profile).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => `<p> ${magenta(x)}`),
"loggingEntry(profileEnd).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => `</p> ${magenta(x)}`),
"loggingEntry(time).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => `<t> ${magenta(x)}`),
"loggingEntry(group).loggingEntry.message": (message, { cyan }) =>
mapLines(message, x => cyan(x)),
"loggingEntry(groupCollapsed).loggingEntry.message": (message, { cyan }) =>
mapLines(message, x => `<+> ${cyan(x)}`),
"loggingEntry(clear).loggingEntry": () => "-------",
"loggingEntry(groupCollapsed).loggingEntry.children": () => "",
"loggingEntry.trace[]": trace =>
trace ? mapLines(trace, x => `| ${x}`) : undefined,
"moduleTraceItem.originName": originName => originName,
loggingGroup: loggingGroup =>
loggingGroup.entries.length === 0 ? "" : undefined,
"loggingGroup.debug": (flag, { red }) => ((flag ? red("DEBUG") : undefined)),
"loggingGroup.name": (name, { bold }) => bold(`LOG from ${name}`),
"loggingGroup.separator!": () => "\n",
"loggingGroup.filteredEntries": filteredEntries =>
filteredEntries > 0 ? `+ ${filteredEntries} hidden lines` : undefined,
"moduleTraceDependency.loc": loc => loc
};
/** @type {Record<string, string>} */
/** @type {Record<string, string | Function>} */
const ITEM_NAMES = {
"compilation.assets[]": "asset",
"compilation.modules[]": "module",
@ -416,6 +463,7 @@ const ITEM_NAMES = {
"compilation.namedChunkGroups[]": "chunkGroup",
"compilation.errors[]": "error",
"compilation.warnings[]": "error",
"compilation.logging[]": "loggingGroup",
"compilation.children[]": "compilation",
"asset.chunks[]": "assetChunk",
"asset.auxiliaryChunks[]": "assetChunk",
@ -427,6 +475,10 @@ const ITEM_NAMES = {
"chunk.origins[]": "chunkOrigin",
"chunk.rootModules[]": "module",
"chunk.modules[]": "module",
"loggingGroup.entries[]": logEntry =>
`loggingEntry(${logEntry.type}).loggingEntry`,
"loggingEntry.children[]": logEntry =>
`loggingEntry(${logEntry.type}).loggingEntry`,
"error.moduleTrace[]": "moduleTraceItem",
"moduleTraceItem.dependencies[]": "moduleTraceDependency"
};
@ -467,6 +519,7 @@ const PREFERED_ORDERS = {
"chunks",
"modules",
"filteredModules",
"logging",
"warnings",
"errors",
"children",
@ -572,6 +625,15 @@ const PREFERED_ORDERS = {
error: ERROR_PREFERED_ORDER,
warning: ERROR_PREFERED_ORDER,
"chunk.childrenByOrder[]": ["type", "children"],
loggingGroup: [
"debug",
"name",
"separator!",
"entries",
"separator!",
"filtredEntries"
],
loggingEntry: ["message", "trace", "children"],
"chunkGroup.childAssets[]": ["type", "children"]
};
@ -608,7 +670,10 @@ const SIMPLE_ITEMS_JOINER = {
.join(" "),
"compilation.errors": itemsJoinMoreSpacing,
"compilation.warnings": itemsJoinMoreSpacing,
"compilation.logging": itemsJoinMoreSpacing,
"moduleTraceItem.dependencies": itemsJoinOneLine,
"loggingEntry.children": items =>
indent(items.filter(Boolean).join("\n"), " ", false),
"compilation.children": items =>
items.map(item => indent(item, " ", true)).join("\n")
};
@ -701,7 +766,9 @@ const SIMPLE_ELEMENT_JOINERS = {
for (const item of items) {
if (!item.content) continue;
const needMoreSpace =
item.element === "warnings" || item.element === "errors";
item.element === "warnings" ||
item.element === "errors" ||
item.element === "logging";
if (result.length !== 0) {
result.push(needMoreSpace || lastNeedMore ? "\n\n" : "\n");
}
@ -797,6 +864,7 @@ const SIMPLE_ELEMENT_JOINERS = {
chunkOrigin: items => " > " + joinOneLine(items),
"errors[].error": joinError(true),
"warnings[].error": joinError(false),
loggingGroup: items => joinExplicitNewLine(items, "").trimRight(),
moduleTraceItem: items => " @ " + joinOneLine(items),
moduleTraceDependency: joinOneLine
};
@ -973,7 +1041,10 @@ class DefaultStatsPrinterPlugin {
const itemName = ITEM_NAMES[key];
stats.hooks.getItemName
.for(key)
.tap("DefaultStatsPrinterPlugin", () => itemName);
.tap(
"DefaultStatsPrinterPlugin",
typeof itemName === "string" ? () => itemName : itemName
);
}
for (const key of Object.keys(SIMPLE_ITEMS_JOINER)) {

View File

@ -52,7 +52,9 @@ const createCompiler = options => {
options = new WebpackOptionsDefaulter().process(options);
const compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {

View File

@ -252,7 +252,7 @@
},
{
"instanceof": "Function",
"tsType": "Function"
"tsType": "((value: string) => boolean)"
}
]
},
@ -2006,6 +2006,35 @@
"description": "add the hash of the compilation",
"type": "boolean"
},
"logging": {
"description": "add logging output",
"anyOf": [
{
"description": "enable/disable logging output (true: shows normal logging output, loglevel: log)",
"type": "boolean"
},
{
"description": "specify log level of logging output",
"enum": ["none", "error", "warn", "info", "log", "verbose"]
}
]
},
"loggingDebug": {
"description": "Include debug logging of specified loggers (i. e. for plugins or loaders). Filters can be Strings, RegExps or Functions",
"anyOf": [
{
"$ref": "#/definitions/FilterTypes"
},
{
"description": "Enable/Disable debug logging for all loggers",
"type": "boolean"
}
]
},
"loggingTrace": {
"description": "add stack traces to logging output",
"type": "boolean"
},
"maxModules": {
"description": "Set the maximum number of modules to be shown",
"type": "number"
@ -2263,6 +2292,29 @@
}
]
},
"infrastructureLogging": {
"description": "Options for infrastructure level logging",
"type": "object",
"additionalProperties": false,
"properties": {
"debug": {
"description": "Enable debug logging for specific loggers",
"anyOf": [
{
"$ref": "#/definitions/FilterTypes"
},
{
"description": "Enable/Disable debug logging for all loggers",
"type": "boolean"
}
]
},
"level": {
"description": "Log level",
"enum": ["none", "error", "warn", "info", "log", "verbose"]
}
}
},
"loader": {
"description": "Custom values available in the loader context.",
"type": "object"

View File

@ -709,4 +709,152 @@ describe("Compiler", () => {
done();
});
});
describe("infrastructure logging", () => {
const CONSOLE_METHODS = [
"error",
"warn",
"info",
"log",
"debug",
"trace",
"profile",
"profileEnd",
"group",
"groupEnd",
"groupCollapsed"
];
const spies = {};
beforeEach(() => {
for (const method of CONSOLE_METHODS) {
if (console[method]) {
spies[method] = jest.spyOn(console, method).mockImplementation();
}
}
});
afterEach(() => {
for (const method in spies) {
spies[method].mockRestore();
delete spies[method];
}
});
class MyPlugin {
apply(compiler) {
const logger = compiler.getInfrastructureLogger("MyPlugin");
logger.time("Time");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
logger.timeEnd("Time");
}
}
it("should log to the console (verbose)", done => {
const compiler = webpack({
context: path.join(__dirname, "fixtures"),
entry: "./a",
output: {
path: "/",
filename: "bundle.js"
},
infrastructureLogging: {
level: "verbose"
},
plugins: [new MyPlugin()]
});
compiler.outputFileSystem = new MemoryFs();
compiler.run((err, stats) => {
expect(spies.group).toHaveBeenCalledTimes(1);
expect(spies.group).toHaveBeenCalledWith("[MyPlugin] Group");
expect(spies.groupCollapsed).toHaveBeenCalledTimes(1);
expect(spies.groupCollapsed).toHaveBeenCalledWith(
"[MyPlugin] Collaped group"
);
expect(spies.error).toHaveBeenCalledTimes(1);
expect(spies.error).toHaveBeenCalledWith("<e> [MyPlugin] Error");
expect(spies.warn).toHaveBeenCalledTimes(1);
expect(spies.warn).toHaveBeenCalledWith("<w> [MyPlugin] Warning");
expect(spies.info).toHaveBeenCalledTimes(1);
expect(spies.info).toHaveBeenCalledWith("<i> [MyPlugin] Info");
expect(spies.log).toHaveBeenCalledTimes(3);
expect(spies.log).toHaveBeenCalledWith("[MyPlugin] Log");
expect(spies.log).toHaveBeenCalledWith(
"[MyPlugin] Log inside collapsed group"
);
expect(spies.debug).toHaveBeenCalledTimes(0);
expect(spies.groupEnd).toHaveBeenCalledTimes(2);
done();
});
});
it("should log to the console (debug mode)", done => {
const compiler = webpack({
context: path.join(__dirname, "fixtures"),
entry: "./a",
output: {
path: "/",
filename: "bundle.js"
},
infrastructureLogging: {
level: "error",
debug: /MyPlugin/
},
plugins: [new MyPlugin()]
});
compiler.outputFileSystem = new MemoryFs();
compiler.run((err, stats) => {
expect(spies.group).toHaveBeenCalledTimes(1);
expect(spies.group).toHaveBeenCalledWith("[MyPlugin] Group");
expect(spies.groupCollapsed).toHaveBeenCalledTimes(1);
expect(spies.groupCollapsed).toHaveBeenCalledWith(
"[MyPlugin] Collaped group"
);
expect(spies.error).toHaveBeenCalledTimes(1);
expect(spies.error).toHaveBeenCalledWith("<e> [MyPlugin] Error");
expect(spies.warn).toHaveBeenCalledTimes(1);
expect(spies.warn).toHaveBeenCalledWith("<w> [MyPlugin] Warning");
expect(spies.info).toHaveBeenCalledTimes(1);
expect(spies.info).toHaveBeenCalledWith("<i> [MyPlugin] Info");
expect(spies.log).toHaveBeenCalledTimes(3);
expect(spies.log).toHaveBeenCalledWith("[MyPlugin] Log");
expect(spies.log).toHaveBeenCalledWith(
"[MyPlugin] Log inside collapsed group"
);
expect(spies.debug).toHaveBeenCalledTimes(1);
expect(spies.debug).toHaveBeenCalledWith("[MyPlugin] Debug");
expect(spies.groupEnd).toHaveBeenCalledTimes(2);
done();
});
});
it("should log to the console (none)", done => {
const compiler = webpack({
context: path.join(__dirname, "fixtures"),
entry: "./a",
output: {
path: "/",
filename: "bundle.js"
},
infrastructureLogging: {
level: "none"
},
plugins: [new MyPlugin()]
});
compiler.outputFileSystem = new MemoryFs();
compiler.run((err, stats) => {
expect(spies.group).toHaveBeenCalledTimes(0);
expect(spies.groupCollapsed).toHaveBeenCalledTimes(0);
expect(spies.error).toHaveBeenCalledTimes(0);
expect(spies.warn).toHaveBeenCalledTimes(0);
expect(spies.info).toHaveBeenCalledTimes(0);
expect(spies.log).toHaveBeenCalledTimes(0);
expect(spies.debug).toHaveBeenCalledTimes(0);
expect(spies.groupEnd).toHaveBeenCalledTimes(0);
done();
});
});
});
});

View File

@ -259,69 +259,63 @@ describe("JavascriptParser", () => {
const state = testCases[name][1];
const testParser = new JavascriptParser({});
testParser.hooks.canRename.tap(
"abc",
"JavascriptParserTest",
expr => true
);
testParser.hooks.canRename.tap(
"ijk",
"JavascriptParserTest",
expr => true
);
testParser.hooks.call.tap("abc", "JavascriptParserTest", expr => {
testParser.hooks.canRename
.for("abc")
.tap("JavascriptParserTest", expr => true);
testParser.hooks.canRename
.for("ijk")
.tap("JavascriptParserTest", expr => true);
testParser.hooks.call.for("abc").tap("JavascriptParserTest", expr => {
if (!testParser.state.abc) testParser.state.abc = [];
testParser.state.abc.push(testParser.parseString(expr.arguments[0]));
return true;
});
testParser.hooks.call.tap("cde.abc", "JavascriptParserTest", expr => {
testParser.hooks.call.for("cde.abc").tap("JavascriptParserTest", expr => {
if (!testParser.state.cdeabc) testParser.state.cdeabc = [];
testParser.state.cdeabc.push(testParser.parseString(expr.arguments[0]));
return true;
});
testParser.hooks.call.tap("cde.ddd.abc", "JavascriptParserTest", expr => {
if (!testParser.state.cdedddabc) testParser.state.cdedddabc = [];
testParser.state.cdedddabc.push(
testParser.parseString(expr.arguments[0])
);
return true;
});
testParser.hooks.expression.tap("fgh", "JavascriptParserTest", expr => {
if (!testParser.state.fgh) testParser.state.fgh = [];
testParser.state.fgh.push(
Array.from(testParser.scope.definitions.asSet()).join(" ")
);
return true;
});
testParser.hooks.expression.tap(
"fgh.sub",
"JavascriptParserTest",
expr => {
testParser.hooks.call
.for("cde.ddd.abc")
.tap("JavascriptParserTest", expr => {
if (!testParser.state.cdedddabc) testParser.state.cdedddabc = [];
testParser.state.cdedddabc.push(
testParser.parseString(expr.arguments[0])
);
return true;
});
testParser.hooks.expression
.for("fgh")
.tap("JavascriptParserTest", expr => {
if (!testParser.state.fgh) testParser.state.fgh = [];
testParser.state.fgh.push(
Array.from(testParser.scope.definitions.asSet()).join(" ")
);
return true;
});
testParser.hooks.expression
.for("fgh.sub")
.tap("JavascriptParserTest", expr => {
if (!testParser.state.fghsub) testParser.state.fghsub = [];
testParser.state.fghsub.push(
testParser.scope.inTry ? "try" : "notry"
);
return true;
}
);
testParser.hooks.expression.tap(
"ijk.sub",
"JavascriptParserTest",
expr => {
});
testParser.hooks.expression
.for("ijk.sub")
.tap("JavascriptParserTest", expr => {
if (!testParser.state.ijksub) testParser.state.ijksub = [];
testParser.state.ijksub.push("test");
return true;
}
);
testParser.hooks.expression.tap(
"memberExpr",
"JavascriptParserTest",
expr => {
});
testParser.hooks.expression
.for("memberExpr")
.tap("JavascriptParserTest", expr => {
if (!testParser.state.expressions) testParser.state.expressions = [];
testParser.state.expressions.push(expr.name);
return true;
}
);
});
testParser.hooks.new.tap("xyz", "JavascriptParserTest", expr => {
if (!testParser.state.xyz) testParser.state.xyz = [];
testParser.state.xyz.push(testParser.parseString(expr.arguments[0]));
@ -367,23 +361,21 @@ describe("JavascriptParser", () => {
describe("expression evaluation", () => {
function evaluateInParser(source) {
const parser = new JavascriptParser();
parser.hooks.call.tap("test", "JavascriptParserTest", expr => {
parser.hooks.call.for("test").tap("JavascriptParserTest", expr => {
parser.state.result = parser.evaluateExpression(expr.arguments[0]);
});
parser.hooks.evaluateIdentifier.tap(
"aString",
"JavascriptParserTest",
expr =>
parser.hooks.evaluateIdentifier
.for("aString")
.tap("JavascriptParserTest", expr =>
new BasicEvaluatedExpression()
.setString("aString")
.setRange(expr.range)
);
parser.hooks.evaluateIdentifier.tap(
"b.Number",
"JavascriptParserTest",
expr =>
);
parser.hooks.evaluateIdentifier
.for("b.Number")
.tap("JavascriptParserTest", expr =>
new BasicEvaluatedExpression().setNumber(123).setRange(expr.range)
);
);
return parser.parse("test(" + source + ");").result;
}
@ -615,7 +607,7 @@ describe("JavascriptParser", () => {
};
const parser = new JavascriptParser();
parser.hooks.call.tap("require", "JavascriptParserTest", expr => {
parser.hooks.call.for("require").tap("JavascriptParserTest", expr => {
const param = parser.evaluateExpression(expr.arguments[0]);
parser.state.param = param.string;
});

View File

@ -6,6 +6,15 @@ const fs = require("graceful-fs");
const webpack = require("..");
/**
* Escapes regular expression metacharacters
* @param {string} str String to quote
* @returns {string} Escaped string
*/
const quotemeta = str => {
return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
};
const base = path.join(__dirname, "statsCases");
const outputBase = path.join(__dirname, "js", "stats");
const tests = fs
@ -129,24 +138,21 @@ describe("StatsTestCases", () => {
if (!hasColorSetting) {
actual = actual
.replace(/\u001b\[[0-9;]*m/g, "")
.replace(/[0-9]+(\s?ms)/g, "X$1");
.replace(/[.0-9]+(\s?ms)/g, "X$1");
} else {
actual = actual
.replace(/\u001b\[1m\u001b\[([0-9;]*)m/g, "<CLR=$1,BOLD>")
.replace(/\u001b\[1m/g, "<CLR=BOLD>")
.replace(/\u001b\[39m\u001b\[22m/g, "</CLR>")
.replace(/\u001b\[([0-9;]*)m/g, "<CLR=$1>")
.replace(/[0-9]+(<\/CLR>)?(\s?ms)/g, "X$1$2");
.replace(/[.0-9]+(<\/CLR>)?(\s?ms)/g, "X$1$2");
}
const testPath = path.join(base, testName);
const testPathPattern = testPath.replace(
/[-[\]\\/{}()*+?.^$|]/g,
"\\$&"
);
actual = actual
.replace(/\r\n?/g, "\n")
.replace(/[\t ]*Version:.+\n/g, "")
.replace(new RegExp(testPathPattern, "g"), "Xdir/" + testName)
.replace(new RegExp(quotemeta(testPath), "g"), "Xdir/" + testName)
.replace(/(\w)\\(\w)/g, "$1/$2")
.replace(/, additional resolving: Xms/g, "");
expect(actual).toMatchSnapshot();
done();

View File

@ -193,7 +193,7 @@ describe("Validation", () => {
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration has an unknown property 'postcss'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, serve?, stats?, target?, watch?, watchOptions? }
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, serve?, stats?, target?, watch?, watchOptions? }
For typos: please correct them.
For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration.
Loaders should be updated to allow passing options via loader options in module.rules.

View File

@ -1277,6 +1277,43 @@ Child 4 chunks:
[767] ./d.js 22 bytes {524} [built]"
`;
exports[`StatsTestCases should print correct stats for logging 1`] = `
"Hash: <CLR=BOLD>17d1aced1f8edabe0aa9</CLR>
Time: <CLR=BOLD>X</CLR>ms
Built at: 1970-04-20 <CLR=BOLD>12:42:42</CLR>
<CLR=BOLD>Asset</CLR> <CLR=BOLD>Size</CLR> <CLR=BOLD>Chunks</CLR> <CLR=BOLD>Chunk Names</CLR>
<CLR=32,BOLD>main.js</CLR> 1.32 KiB {<CLR=33,BOLD>179</CLR>} <CLR=32,BOLD>[emitted]</CLR> main
Entrypoint <CLR=BOLD>main</CLR> = <CLR=32,BOLD>main.js</CLR>
[390] <CLR=BOLD>./index.js</CLR> 1 bytes {<CLR=33,BOLD>179</CLR>} <CLR=32,BOLD>[built]</CLR>
<CLR=BOLD>LOG from MyPlugin</CLR>
<i> <CLR=32,BOLD>Plugin is now active</CLR>
<+> <CLR=36,BOLD>Nested</CLR>
+ 3 hidden lines
<CLR=31,BOLD>DEBUG</CLR> <CLR=BOLD>LOG from ./node_modules/custom-loader/index.js ./node_modules/custom-loader/index.js!./index.js</CLR>
<e> <CLR=31,BOLD>An error</CLR>
| at Object.<anonymous>.module.exports (Xdir/logging/node_modules/custom-loader/index.js:5:9)
<w> <CLR=33,BOLD>A warning</CLR>
| at Object.<anonymous>.module.exports (Xdir/logging/node_modules/custom-loader/index.js:6:9)
<CLR=36,BOLD>Unimportant</CLR>
<i> <CLR=32,BOLD>Info message</CLR>
<CLR=BOLD>Just log</CLR>
Just debug
<t> <CLR=35,BOLD>Measure: Xms</CLR>
<CLR=36,BOLD>Nested</CLR>
<CLR=BOLD>Log inside collapsed group</CLR>
Trace
| at Object.<anonymous>.module.exports (Xdir/logging/node_modules/custom-loader/index.js:15:9)
<t> <CLR=35,BOLD>Measure: Xms</CLR>
-------
<CLR=BOLD>After clear</CLR>
<CLR=31,BOLD>DEBUG</CLR> <CLR=BOLD>LOG from ./node_modules/custom-loader/index.js Named Logger ./node_modules/custom-loader/index.js!./index.js</CLR>
Message with named logger
"
`;
exports[`StatsTestCases should print correct stats for max-modules 1`] = `
"Hash: 8a3d2df87a4f01705c82
Time: Xms
@ -2062,20 +2099,48 @@ webpack/runtime/jsonp chunk loading 3.36 KiB {179} [runtime]
[used exports unknown]
webpack/runtime/publicPath 27 bytes {179} [runtime]
[no exports]
[used exports unknown]"
[used exports unknown]
LOG from MyPlugin
Group
<e> Error
<w> Warning
<i> Info
Log
<+> Collaped group
+ 3 hidden lines
"
`;
exports[`StatsTestCases should print correct stats for preset-errors-only 1`] = `""`;
exports[`StatsTestCases should print correct stats for preset-errors-only-error 1`] = `
"ERROR in ./index.js 1:0-25
"LOG from MyPlugin
<e> Error
+ 9 hidden lines
ERROR in ./index.js 1:0-25
Module not found: Error: Can't resolve 'does-not-exist' in 'Xdir/preset-errors-only-error'
"
`;
exports[`StatsTestCases should print correct stats for preset-errors-warnings 1`] = `""`;
exports[`StatsTestCases should print correct stats for preset-errors-warnings 1`] = `
"LOG from MyPlugin
<e> Error
<w> Warning
+ 8 hidden lines
"
`;
exports[`StatsTestCases should print correct stats for preset-minimal 1`] = `" 10 modules"`;
exports[`StatsTestCases should print correct stats for preset-minimal 1`] = `
" 10 modules
LOG from MyPlugin
<e> Error
<w> Warning
+ 8 hidden lines
"
`;
exports[`StatsTestCases should print correct stats for preset-minimal-simple 1`] = `" 1 module"`;
@ -2109,7 +2174,14 @@ Entrypoint main = main.js
[767] ./d.js 22 bytes {524} [built]
[847] ./a.js 22 bytes {179} [built]
[996] ./b.js 22 bytes {996} [built]
+ 4 hidden modules"
+ 4 hidden modules
LOG from MyPlugin
<e> Error
<w> Warning
<i> Info
+ 7 hidden lines
"
`;
exports[`StatsTestCases should print correct stats for preset-normal-performance 1`] = `
@ -2230,7 +2302,18 @@ chunk {996} 996.js 22 bytes <{179}> [rendered]
[996] ./b.js 22 bytes {996} [depth 1] [built]
ModuleConcatenation bailout: Module is not an ECMAScript module
amd require ./b [10] ./index.js 2:0-16
[10] Xms -> Xms (resolving: Xms, restoring: Xms, integration: Xms, building: Xms, storing: Xms)"
[10] Xms -> Xms (resolving: Xms, restoring: Xms, integration: Xms, building: Xms, storing: Xms)
LOG from MyPlugin
Group
<e> Error
<w> Warning
<i> Info
Log
Collaped group
Log inside collapsed group
+ 1 hidden lines
"
`;
exports[`StatsTestCases should print correct stats for resolve-plugin-context 1`] = `

View File

View File

@ -0,0 +1,21 @@
/* eslint-disable node/no-unsupported-features/node-builtins */
module.exports = function(source) {
const logger = this.getLogger ? this.getLogger() : console;
logger.time("Measure");
logger.error("An error");
logger.warn("A %s", "warning");
logger.group("Unimportant");
logger.info("Info message");
logger.log("Just log");
logger.debug("Just debug");
logger.timeLog("Measure");
logger.groupCollapsed("Nested");
logger.log("Log inside collapsed group");
logger.groupEnd("Nested");
logger.trace();
logger.timeEnd("Measure");
logger.clear();
logger.log("After clear");
this.getLogger("Named Logger").debug("Message with named logger");
return source;
};

View File

@ -0,0 +1,36 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.info("Plugin is now active");
logger.debug("Debug message should not be visible");
logger.groupCollapsed("Nested");
logger.log("Log inside collapsed group");
logger.groupEnd("Nested");
const otherLogger = compilation.getLogger("MyOtherPlugin");
otherLogger.debug("debug message only");
});
}
}
module.exports = {
mode: "production",
entry: "./index",
performance: false,
module: {
rules: [
{
test: /index\.js$/,
use: "custom-loader"
}
]
},
plugins: [new MyPlugin()],
stats: {
colors: true,
logging: true,
loggingDebug: "custom-loader",
loggingTrace: true
}
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: "detailed"
stats: "detailed",
plugins: [new MyPlugin()]
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: "errors-only"
stats: "errors-only",
plugins: [new MyPlugin()]
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: "errors-warnings"
stats: "errors-warnings",
plugins: [new MyPlugin()]
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: "minimal"
stats: "minimal",
plugins: [new MyPlugin()]
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: false
stats: false,
plugins: [new MyPlugin()]
};

View File

@ -1,5 +1,24 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
stats: "normal"
stats: "normal",
plugins: [new MyPlugin()]
};

View File

@ -1,6 +1,25 @@
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", compilation => {
const logger = compilation.getLogger("MyPlugin");
logger.group("Group");
logger.error("Error");
logger.warn("Warning");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collaped group");
logger.log("Log inside collapsed group");
logger.groupEnd();
logger.groupEnd();
});
}
}
module.exports = {
mode: "production",
entry: "./index",
profile: true,
stats: "verbose"
stats: "verbose",
plugins: [new MyPlugin()]
};

View File

@ -373,9 +373,9 @@
integrity sha512-MeatbbUsZ80BEsKPXby6pUZjUM9ZuHIpWElN0siopih3fvnlpX2O9L6D5+dzDIb36lf9tM/8U4PVdLQ+L4qr4A==
"@types/node@^10.12.21":
version "10.14.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.12.tgz#0eec3155a46e6c4db1f27c3e588a205f767d622f"
integrity sha512-QcAKpaO6nhHLlxWBvpc4WeLrTvPqlHOvaj0s5GriKkA1zq+bsFBPpfYCvQhLqLgYlIko8A9YrPdaMHCo5mBcpg==
version "10.14.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.13.tgz#ac786d623860adf39a3f51d629480aacd6a6eec7"
integrity sha512-yN/FNNW1UYsRR1wwAoyOwqvDuLDtVXnaJTZ898XIw/Q5cCaeVAlVwvsmXLX5PuiScBYwZsZU4JYSHB3TvfdwvQ==
"@types/prettier@^1.16.1":
version "1.16.1"
@ -609,9 +609,9 @@ acorn@^5.5.3:
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.0.tgz#67f0da2fc339d6cfb5d6fb244fd449f33cd8bbe3"
integrity sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==
version "6.2.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
ajv-errors@^1.0.0:
version "1.0.1"
@ -1321,10 +1321,10 @@ commander@^2.14.1, commander@^2.19.0, commander@^2.9.0, commander@~2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
comment-parser@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.5.5.tgz#c2584cae7c2f0afc773e96b2ee98f8c10cbd693d"
integrity sha512-oB3TinFT+PV3p8UwDQt71+HkG03+zwPwikDlKU6ZDmql6QX2zFlQ+G0GGSDqyJhdZi4PSlzFBm+YJ+ebOX3Vgw==
comment-parser@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.6.1.tgz#88040c7c0a57c62e64962c3e888518620a42e7c9"
integrity sha512-Putzd7Ilyvknmb1KxGf5el9uw0sPx9gEVnDrm8tlvXGN1i8Uaa2VBxB32hUhfzTlrEhhxNQ+pKq4ZNe8wNxjmw==
commondir@^1.0.1:
version "1.0.1"
@ -1841,16 +1841,16 @@ eslint-plugin-es@^1.3.1:
regexpp "^2.0.1"
eslint-plugin-jest@^22.2.2:
version "22.8.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.8.0.tgz#242ef5459e8da25d2c41438e95eb546e03d7fae1"
integrity sha512-2VftZMfILmlhL3VMq5ptHRIuyyXb3ShDEDb1J1UjvWNzm4l+UK/YmwNuTuJcM0gv8pJuOfiR/8ZptJ8Ou68pFw==
version "22.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.13.0.tgz#d7d134c6e3c2f67cc50f5fa89a329db579d28428"
integrity sha512-bIr8LL7buUXS8Pk69SFgaDKgyvPQkDu6i8ko0lP54uccszlo4EOwtstDXOZl5Af3JwudbECxRUbCpL/2cKDkkg==
eslint-plugin-jsdoc@^15.3.2:
version "15.5.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-15.5.2.tgz#89768320c64ec2f30d12209e1926c4decca0568a"
integrity sha512-5s39RYGaqugWVoOfc6pAwj9yeNh7mclygBWTyYVJX+sGiNchwCtgHbn2AjeonOw0g168CPI3itiXetHj2Yo8gg==
version "15.5.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-15.5.3.tgz#44e408e3bf2f60f3ad18dc829f9b9a1e34f33154"
integrity sha512-lw8wYa1UFV53JLoqKOQR8YBkKlE/aguR+HGyytL9VKsVvm83DK8ReYnNNDRKik3MF661cGuaUuGfIEcdqg9l4A==
dependencies:
comment-parser "^0.5.5"
comment-parser "^0.6.0"
debug "^4.1.1"
flat-map-polyfill "^0.3.8"
jsdoctypeparser "5.0.1"
@ -3835,9 +3835,9 @@ lodash.sortby@^4.7.0:
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4:
version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-driver@^1.2.7:
version "1.2.7"
@ -5500,9 +5500,9 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
simple-git@^1.65.0, simple-git@^1.85.0:
version "1.121.0"
resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.121.0.tgz#4bdf0828cd1b0bb3cb7ed9bead2771982ef5876a"
integrity sha512-LyYri/nuAX8+cx9nZw38mWO6oHNi//CmiPlkBL7aVjZIsdldve7eeDwXu9L4wP/74MpNHucXkXc/BOuIQShhPg==
version "1.122.0"
resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.122.0.tgz#33b2d3a760aa02df470c79fbab5413d4f4e68945"
integrity sha512-plTwhnkIHrw2TFMJbJH/mKwWGgFbj03V9wcfBKa4FsuvgJbpwdlSJnlvkIQWDV1CVLaf2Gl6zSNeRRnxBRhX1g==
dependencies:
debug "^4.0.1"