diff --git a/generate-types-config.js b/generate-types-config.js index a3ce7af12..89205e349 100644 --- a/generate-types-config.js +++ b/generate-types-config.js @@ -4,7 +4,8 @@ module.exports = { nameMapping: { FsStats: /^Stats Import fs/, validateFunction: /^validate Import/, - Configuration: /^WebpackOptions / + Configuration: /^WebpackOptions /, + MultiConfiguration: /^MultiWebpackOptions / }, exclude: [/^devServer in WebpackOptions /], include: [/^(_module|_compilation|_compiler) in NormalModuleLoaderContext /] diff --git a/lib/MultiCompiler.js b/lib/MultiCompiler.js index dfec39920..3ab64e4af 100644 --- a/lib/MultiCompiler.js +++ b/lib/MultiCompiler.js @@ -16,6 +16,7 @@ const ArrayQueue = require("./util/ArrayQueue"); /** @template T @typedef {import("tapable").AsyncSeriesHook} AsyncSeriesHook */ /** @template T @template R @typedef {import("tapable").SyncBailHook} SyncBailHook */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ /** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */ /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Stats")} Stats */ @@ -44,6 +45,8 @@ const ArrayQueue = require("./util/ArrayQueue"); * @property {number=} parallelism how many Compilers are allows to run at the same time in parallel */ +/** @typedef {ReadonlyArray & MultiCompilerOptions} MultiWebpackOptions */ + const CLASS_NAME = "MultiCompiler"; module.exports = class MultiCompiler { @@ -576,7 +579,7 @@ module.exports = class MultiCompiler { } /** - * @param {WatchOptions|WatchOptions[]} watchOptions the watcher's options + * @param {WatchOptions | WatchOptions[]} watchOptions the watcher's options * @param {Callback} handler signals when the call finishes * @returns {MultiWatching} a compiler watcher */ diff --git a/lib/MultiStats.js b/lib/MultiStats.js index 50571cdf4..62504ab82 100644 --- a/lib/MultiStats.js +++ b/lib/MultiStats.js @@ -25,6 +25,8 @@ const indent = (str, prefix) => { return prefix + rem; }; +/** @typedef {undefined | string | boolean | StatsOptions} ChildrenStatsOptions */ +/** @typedef {Omit & { children?: ChildrenStatsOptions | ChildrenStatsOptions[] }} MultiStatsOptions */ /** @typedef {{ version: boolean, hash: boolean, errorsCount: boolean, warningsCount: boolean, errors: boolean, warnings: boolean, children: NormalizedStatsOptions[] }} ChildOptions */ class MultiStats { @@ -54,7 +56,7 @@ class MultiStats { } /** - * @param {string | boolean | StatsOptions | undefined} options stats options + * @param {undefined | string | boolean | MultiStatsOptions} options stats options * @param {CreateStatsOptionsContext} context context * @returns {ChildOptions} context context */ @@ -80,6 +82,9 @@ class MultiStats { const childOptions = Array.isArray(childrenOptions) ? childrenOptions[idx] : childrenOptions; + if (typeof childOptions === "boolean") { + return stat.compilation.createStatsOptions(childOptions, context); + } return stat.compilation.createStatsOptions( { ...baseOptions, @@ -104,7 +109,7 @@ class MultiStats { } /** - * @param {(string | boolean | StatsOptions)=} options stats options + * @param {(string | boolean | MultiStatsOptions)=} options stats options * @returns {StatsCompilation} json output */ toJson(options) { @@ -179,7 +184,7 @@ class MultiStats { } /** - * @param {(string | boolean | StatsOptions)=} options stats options + * @param {(string | boolean | MultiStatsOptions)=} options stats options * @returns {string} string output */ toString(options) { diff --git a/lib/cli.js b/lib/cli.js index 4bc007fc2..1d38af32b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -840,7 +840,12 @@ const init = (open, close, replace) => }} Colors */ /** - * @param {{ useColor?: boolean }=} options options + * @typedef {object} ColorsOptions + * @property {boolean=} useColor force use colors + */ + +/** + * @param {ColorsOptions=} options options * @returns {Colors} colors */ const createColors = ({ useColor = isColorSupported() } = {}) => ({ diff --git a/lib/index.js b/lib/index.js index 651460bef..a4003a0f0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -49,7 +49,9 @@ const memoize = require("./util/memoize"); /** @typedef {import("./Compiler").AssetEmittedInfo} AssetEmittedInfo */ /** @typedef {import("./Entrypoint")} Entrypoint */ /** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ +/** @typedef {import("./MultiCompiler").MultiWebpackOptions} MultiConfiguration */ /** @typedef {import("./MultiStats")} MultiStats */ +/** @typedef {import("./MultiStats").MultiStatsOptions} MultiStatsOptions */ /** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */ /** @typedef {import("./Parser").ParserState} ParserState */ /** @typedef {import("./ResolverFactory").ResolvePluginInstance} ResolvePluginInstance */ @@ -57,6 +59,8 @@ const memoize = require("./util/memoize"); /** @typedef {import("./Watching")} Watching */ /** @typedef {import("./cli").Argument} Argument */ /** @typedef {import("./cli").Problem} Problem */ +/** @typedef {import("./cli").Colors} Colors */ +/** @typedef {import("./cli").ColorsOptions} ColorsOptions */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ @@ -132,16 +136,16 @@ module.exports = mergeExports(fn, { return require("./webpack"); }, /** - * @returns {(configuration: Configuration | Configuration[]) => void} validate fn + * @returns {(configuration: Configuration | MultiConfiguration) => void} validate fn */ get validate() { const webpackOptionsSchemaCheck = - /** @type {(configuration: Configuration | Configuration[]) => boolean} */ + /** @type {(configuration: Configuration | MultiConfiguration) => boolean} */ (require("../schemas/WebpackOptions.check")); const getRealValidate = memoize( /** - * @returns {(configuration: Configuration | Configuration[]) => void} validate fn + * @returns {(configuration: Configuration | MultiConfiguration) => void} validate fn */ () => { const validateSchema = require("./validateSchema"); diff --git a/lib/stats/DefaultStatsPresetPlugin.js b/lib/stats/DefaultStatsPresetPlugin.js index 65a04bee6..016e7d506 100644 --- a/lib/stats/DefaultStatsPresetPlugin.js +++ b/lib/stats/DefaultStatsPresetPlugin.js @@ -144,31 +144,31 @@ const NAMED_PRESETS = { }; /** - * @param {Partial} all stats option + * @param {Partial} all stats options * @returns {boolean} true when enabled, otherwise false */ const NORMAL_ON = ({ all }) => all !== false; /** - * @param {Partial} all stats option + * @param {Partial} all stats options * @returns {boolean} true when enabled, otherwise false */ const NORMAL_OFF = ({ all }) => all === true; /** - * @param {Partial} all stats option + * @param {Partial} all stats options * @param {CreateStatsOptionsContext} forToString stats options context * @returns {boolean} true when enabled, otherwise false */ const ON_FOR_TO_STRING = ({ all }, { forToString }) => forToString ? all !== false : all === true; /** - * @param {Partial} all stats option + * @param {Partial} all stats options * @param {CreateStatsOptionsContext} forToString stats options context * @returns {boolean} true when enabled, otherwise false */ const OFF_FOR_TO_STRING = ({ all }, { forToString }) => forToString ? all === true : all !== false; /** - * @param {Partial} all stats option + * @param {Partial} all stats options * @param {CreateStatsOptionsContext} forToString stats options context * @returns {boolean | "auto"} true when enabled, otherwise false */ diff --git a/lib/webpack.js b/lib/webpack.js index f6e56f2e3..75b93687b 100644 --- a/lib/webpack.js +++ b/lib/webpack.js @@ -23,6 +23,7 @@ const memoize = require("./util/memoize"); /** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ /** @typedef {import("./Compiler").WatchOptions} WatchOptions */ /** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ +/** @typedef {import("./MultiCompiler").MultiWebpackOptions} MultiWebpackOptions */ /** @typedef {import("./MultiStats")} MultiStats */ /** @typedef {import("./Stats")} Stats */ @@ -100,14 +101,14 @@ const createCompiler = (rawOptions, compilerIndex) => { * @callback WebpackFunctionSingle * @param {WebpackOptions} options options object * @param {Callback=} callback callback - * @returns {Compiler} the compiler object + * @returns {Compiler | null} the compiler object */ /** * @callback WebpackFunctionMulti - * @param {ReadonlyArray & MultiCompilerOptions} options options objects + * @param {MultiWebpackOptions} options options objects * @param {Callback=} callback callback - * @returns {MultiCompiler} the multi compiler object + * @returns {MultiCompiler | null} the multi compiler object */ /** @@ -118,12 +119,15 @@ const createCompiler = (rawOptions, compilerIndex) => { const asArray = (options) => Array.isArray(options) ? [...options] : [options]; +/** + * @callback WebpackCallback + * @param {WebpackOptions | MultiWebpackOptions} options options + * @param {Callback & Callback=} callback callback + * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler + */ + const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ ( - /** - * @param {WebpackOptions | (ReadonlyArray & MultiCompilerOptions)} options options - * @param {Callback & Callback=} callback callback - * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler - */ + /** @type {WebpackCallback} */ (options, callback) => { const create = () => { if (!asArray(options).every(webpackOptionsSchemaCheck)) { diff --git a/test/MultiStats.test.js b/test/MultiStats.test.js index 6fcb31376..6d1f4630b 100644 --- a/test/MultiStats.test.js +++ b/test/MultiStats.test.js @@ -4,11 +4,24 @@ require("./helpers/warmup-webpack"); const { Volume, createFsFromVolume } = require("memfs"); -describe("MultiStats", () => { - it("should create JSON of children stats", (done) => { +const compile = (options) => + new Promise((resolve, reject) => { const webpack = require(".."); - const compiler = webpack([ + const compiler = webpack(options); + compiler.outputFileSystem = createFsFromVolume(new Volume()); + compiler.run((err, stats) => { + if (err) { + reject(err); + } else { + resolve(stats); + } + }); + }); + +describe("MultiStats", () => { + it("should create JSON of children stats", async () => { + const stats = await compile([ { context: __dirname, entry: "./fixtures/a" @@ -18,19 +31,278 @@ describe("MultiStats", () => { entry: "./fixtures/b" } ]); - compiler.outputFileSystem = createFsFromVolume(new Volume()); - compiler.run((err, stats) => { - if (err) return done(err); - try { - const statsObject = stats.toJson(); - expect(statsObject).toEqual( - expect.objectContaining({ children: expect.any(Array) }) - ); - expect(statsObject.children).toHaveLength(2); - done(); - } catch (err) { - done(err); + + const statsObject = stats.toJson(); + expect(statsObject).toEqual( + expect.objectContaining({ children: expect.any(Array) }) + ); + expect(statsObject.children).toHaveLength(2); + }); + + it("should work with a boolean value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" } - }); + ]); + + expect(stats.toJson(false)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(false)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + expect(stats.toJson("none")).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString("none")).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + expect( + stats.toJson({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + "errorsCount": 0, + "warningsCount": 2, + } + `); + expect( + stats.toString({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + "webpack compiled with 1 warning + + webpack compiled with 1 warning" + `); + }); + + it("should work with a boolean value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [false, false] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: ["none", "none"] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [ + { + all: false, + publicPath: true, + version: false, + errorsCount: true, + warningsCount: true + }, + { + all: false, + version: false, + errorsCount: true, + warningsCount: true + } + ] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "errorsCount": 0, + "name": undefined, + "publicPath": "auto", + "warningsCount": 1, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + "errorsCount": 0, + "warningsCount": 2, + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot(` + "PublicPath: auto + webpack compiled with 1 warning + + webpack compiled with 1 warning" + `); + }); + + it("should work with an mixed values for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [ + false, + { + all: false, + version: false, + errorsCount: true, + warningsCount: true + } + ] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot( + '"webpack compiled with 1 warning"' + ); }); }); diff --git a/test/Stats.test.js b/test/Stats.test.js index 009b5eb42..a93916153 100644 --- a/test/Stats.test.js +++ b/test/Stats.test.js @@ -20,6 +20,52 @@ const compile = (options) => }); describe("Stats", () => { + it("should work with a boolean value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect(stats.toJson(false)).toMatchInlineSnapshot("Object {}"); + expect(stats.toString(false)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect(stats.toJson("none")).toMatchInlineSnapshot("Object {}"); + expect(stats.toString("none")).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect( + stats.toJson({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + Object { + "errorsCount": 0, + "warningsCount": 1, + } + `); + expect( + stats.toString({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot('"webpack compiled with 1 warning"'); + }); + it("should print env string in stats", async () => { const stats = await compile({ context: __dirname, diff --git a/types.d.ts b/types.d.ts index 7093ba955..df39b3f50 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1180,6 +1180,7 @@ declare interface CallbackWebpack { (err: null | Error, stats?: T): void; } type Cell = undefined | T; +type ChildrenStatsOptions = undefined | string | boolean | StatsOptions; declare class Chunk { constructor(name?: null | string, backCompat?: boolean); id: null | string | number; @@ -1994,6 +1995,12 @@ declare interface Colors { bgCyanBright: (value?: any) => string; bgWhiteBright: (value?: any) => string; } +declare interface ColorsOptions { + /** + * force use colors + */ + useColor?: boolean; +} declare interface Comparator { (a: T, b: T): 0 | 1 | -1; } @@ -10460,14 +10467,18 @@ declare interface MultiCompilerOptions { */ parallelism?: number; } +type MultiConfiguration = ReadonlyArray & MultiCompilerOptions; declare abstract class MultiStats { stats: Stats[]; get hash(): string; hasErrors(): boolean; hasWarnings(): boolean; - toJson(options?: string | boolean | StatsOptions): StatsCompilation; - toString(options?: string | boolean | StatsOptions): string; + toJson(options?: string | boolean | MultiStatsOptions): StatsCompilation; + toString(options?: string | boolean | MultiStatsOptions): string; } +type MultiStatsOptions = Omit & { + children?: string | boolean | StatsOptions | ChildrenStatsOptions[]; +}; declare abstract class MultiWatching { watchings: Watching[]; compiler: MultiCompiler; @@ -10920,43 +10931,43 @@ declare class NormalModuleReplacementPlugin { type NormalizedStatsOptions = KnownNormalizedStatsOptions & Omit< StatsOptions, - | "context" - | "chunkGroups" - | "requestShortener" - | "chunksSort" - | "modulesSort" - | "chunkModulesSort" - | "nestedModulesSort" | "assetsSort" - | "ids" - | "cachedAssets" - | "groupAssetsByEmitStatus" - | "groupAssetsByPath" - | "groupAssetsByExtension" | "assetsSpace" - | "excludeAssets" - | "excludeModules" - | "warningsFilter" + | "cachedAssets" | "cachedModules" - | "orphanModules" - | "dependentModules" - | "runtimeModules" - | "groupModulesByCacheStatus" - | "groupModulesByLayer" - | "groupModulesByAttributes" - | "groupModulesByPath" - | "groupModulesByExtension" - | "groupModulesByType" - | "entrypoints" | "chunkGroupAuxiliary" | "chunkGroupChildren" | "chunkGroupMaxAssets" - | "modulesSpace" + | "chunkGroups" | "chunkModulesSpace" - | "nestedModulesSpace" + | "chunksSort" + | "context" + | "dependentModules" + | "entrypoints" + | "excludeAssets" + | "excludeModules" + | "groupAssetsByEmitStatus" + | "groupAssetsByExtension" + | "groupAssetsByPath" + | "groupModulesByAttributes" + | "groupModulesByCacheStatus" + | "groupModulesByExtension" + | "groupModulesByLayer" + | "groupModulesByPath" + | "groupModulesByType" + | "ids" | "logging" | "loggingDebug" | "loggingTrace" + | "modulesSort" + | "modulesSpace" + | "nestedModulesSpace" + | "orphanModules" + | "runtimeModules" + | "warningsFilter" + | "requestShortener" + | "chunkModulesSort" + | "nestedModulesSort" | "_env" > & Record; @@ -12617,7 +12628,7 @@ declare class ProgressPlugin { showModules?: boolean; showDependencies?: boolean; showActiveModules?: boolean; - percentBy?: null | "entries" | "modules" | "dependencies"; + percentBy?: null | "modules" | "entries" | "dependencies"; apply(compiler: Compiler | MultiCompiler): void; static getReporter( compiler: Compiler @@ -12682,7 +12693,7 @@ declare interface ProgressPluginOptions { /** * Collect percent algorithm. By default it calculates by a median from modules, entries and dependencies percent. */ - percentBy?: null | "entries" | "modules" | "dependencies"; + percentBy?: null | "modules" | "entries" | "dependencies"; /** * Collect profile data for progress steps. Default: false. @@ -17432,21 +17443,24 @@ declare interface WriteStreamOptions { declare function exports( options: Configuration, callback?: CallbackWebpack -): Compiler; +): null | Compiler; declare function exports( - options: ReadonlyArray & MultiCompilerOptions, + options: MultiConfiguration, callback?: CallbackWebpack -): MultiCompiler; +): null | MultiCompiler; declare namespace exports { export const webpack: { - (options: Configuration, callback?: CallbackWebpack): Compiler; ( - options: ReadonlyArray & MultiCompilerOptions, + options: Configuration, + callback?: CallbackWebpack + ): null | Compiler; + ( + options: MultiConfiguration, callback?: CallbackWebpack - ): MultiCompiler; + ): null | MultiCompiler; }; export const validate: ( - configuration: Configuration | Configuration[] + configuration: Configuration | MultiConfiguration ) => void; export const validateSchema: ( schema: Parameters[0], @@ -17455,7 +17469,7 @@ declare namespace exports { ) => void; export const version: string; export namespace cli { - export let createColors: (__0?: { useColor?: boolean }) => Colors; + export let createColors: (__0?: ColorsOptions) => Colors; export let getArguments: ( schema?: | (JSONSchema4 & { @@ -18145,7 +18159,9 @@ declare namespace exports { AssetEmittedInfo, Entrypoint, MultiCompilerOptions, + MultiConfiguration, MultiStats, + MultiStatsOptions, ResolveData, ParserState, ResolvePluginInstance, @@ -18153,6 +18169,8 @@ declare namespace exports { Watching, Argument, Problem, + Colors, + ColorsOptions, StatsAsset, StatsChunk, StatsChunkGroup,