Skip to content

Commit 5d4e9e4

Browse files
fix: keep multi-compiler options when --config-name or extends rebuild the configuration array (#4871)
1 parent 430bdd6 commit 5d4e9e4

9 files changed

Lines changed: 160 additions & 25 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack-cli": patch
3+
---
4+
5+
fix: keep multi-compiler options such as `parallelism` and `plugins` set on a configuration array when `--config-name` or `extends` rebuild it

‎packages/webpack-cli/src/webpack-cli.ts‎

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3289,24 +3289,46 @@ class WebpackCLI {
32893289
}
32903290
}
32913291

3292+
// Options for the whole MultiCompiler, such as `parallelism`, are set on the
3293+
// exported array itself, so carry them over whenever the array is rebuilt.
3294+
const keepMultiCompilerOptions = <T extends Configuration[]>(
3295+
source: Configuration | MultiConfiguration,
3296+
target: T,
3297+
): T => {
3298+
if (Array.isArray(source)) {
3299+
for (const key of Object.keys(source)) {
3300+
if (!/^\d+$/.test(key)) {
3301+
(target as unknown as Record<string, unknown>)[key] = (
3302+
source as unknown as Record<string, unknown>
3303+
)[key];
3304+
}
3305+
}
3306+
}
3307+
3308+
return target;
3309+
};
3310+
32923311
if (options.configName) {
32933312
const notFoundConfigNames: string[] = [];
32943313

3295-
config.options = options.configName.map((configName) => {
3296-
let found;
3314+
config.options = keepMultiCompilerOptions(
3315+
config.options,
3316+
options.configName.map((configName) => {
3317+
let found;
32973318

3298-
if (this.isMultipleConfiguration(config.options)) {
3299-
found = config.options.find((options) => options.name === configName);
3300-
} else {
3301-
found = config.options.name === configName ? config.options : undefined;
3302-
}
3319+
if (this.isMultipleConfiguration(config.options)) {
3320+
found = config.options.find((options) => options.name === configName);
3321+
} else {
3322+
found = config.options.name === configName ? config.options : undefined;
3323+
}
33033324

3304-
if (!found) {
3305-
notFoundConfigNames.push(configName);
3306-
}
3325+
if (!found) {
3326+
notFoundConfigNames.push(configName);
3327+
}
33073328

3308-
return found;
3309-
}) as MultiConfiguration;
3329+
return found;
3330+
}) as Configuration[],
3331+
);
33103332

33113333
if (notFoundConfigNames.length > 0) {
33123334
this.logger.error(
@@ -3366,8 +3388,11 @@ class WebpackCLI {
33663388
const extendsPaths = options.extends;
33673389

33683390
if (this.isMultipleConfiguration(config.options)) {
3369-
config.options = await Promise.all(
3370-
config.options.map((options) => resolveExtends(options, config.path, extendsPaths)),
3391+
config.options = keepMultiCompilerOptions(
3392+
config.options,
3393+
await Promise.all(
3394+
config.options.map((options) => resolveExtends(options, config.path, extendsPaths)),
3395+
),
33713396
);
33723397
} else {
33733398
// load the config from the extends option
@@ -3379,17 +3404,20 @@ class WebpackCLI {
33793404
this.isMultipleConfiguration(config.options) &&
33803405
config.options.some((options) => options.extends)
33813406
) {
3382-
config.options = await Promise.all(
3383-
config.options.map((options) => {
3384-
if (options.extends) {
3385-
return resolveExtends(
3386-
options,
3387-
config.path,
3388-
typeof options.extends === "string" ? [options.extends] : options.extends,
3389-
);
3390-
}
3391-
return options;
3392-
}),
3407+
config.options = keepMultiCompilerOptions(
3408+
config.options,
3409+
await Promise.all(
3410+
config.options.map((options) => {
3411+
if (options.extends) {
3412+
return resolveExtends(
3413+
options,
3414+
config.path,
3415+
typeof options.extends === "string" ? [options.extends] : options.extends,
3416+
);
3417+
}
3418+
return options;
3419+
}),
3420+
),
33933421
);
33943422
} else if (!this.isMultipleConfiguration(config.options) && config.options.extends) {
33953423
config.options = await resolveExtends(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use strict";
2+
3+
module.exports = {
4+
mode: "development",
5+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use strict";
2+
3+
// Shared by every child: reports how many compilers ran at the same time.
4+
let running = 0;
5+
let max = 0;
6+
let finished = 0;
7+
8+
class ConcurrencyPlugin {
9+
apply(compiler) {
10+
compiler.hooks.run.tap("ConcurrencyPlugin", () => {
11+
running++;
12+
max = Math.max(max, running);
13+
});
14+
compiler.hooks.done.tap("ConcurrencyPlugin", () => {
15+
running--;
16+
finished++;
17+
18+
if (finished === 2) {
19+
console.log(`max concurrent compilers: ${max}`);
20+
}
21+
});
22+
}
23+
}
24+
25+
module.exports = ConcurrencyPlugin;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use strict";
2+
3+
const ConcurrencyPlugin = require("./concurrency-plugin");
4+
5+
const child = (name) => ({
6+
name,
7+
extends: "./base.config.js",
8+
entry: `./src/${name}.js`,
9+
output: { filename: `./dist-${name}.js` },
10+
plugins: [new ConcurrencyPlugin()],
11+
});
12+
13+
module.exports = Object.assign([child("first"), child("second")], { parallelism: 1 });
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use strict";
2+
3+
const { run } = require("../../utils/test-utils");
4+
5+
// Options for the whole MultiCompiler, such as `parallelism`, are set on the
6+
// exported array itself and must survive every step that rebuilds the array.
7+
describe("multi compiler options", () => {
8+
it("should pass the array's own options to webpack", async () => {
9+
const { exitCode, stderr, stdout } = await run(__dirname, []);
10+
11+
expect(exitCode).toBe(0);
12+
expect(stderr).toBeFalsy();
13+
expect(stdout).toContain("max concurrent compilers: 1");
14+
});
15+
16+
it("should keep the array's own options with --config-name", async () => {
17+
const { exitCode, stderr, stdout } = await run(__dirname, [
18+
"--config-name",
19+
"first",
20+
"--config-name",
21+
"second",
22+
]);
23+
24+
expect(exitCode).toBe(0);
25+
expect(stderr).toBeFalsy();
26+
expect(stdout).toContain("max concurrent compilers: 1");
27+
});
28+
29+
it("should keep the array's own options when configurations use extends", async () => {
30+
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "./extends.config.js"]);
31+
32+
expect(exitCode).toBe(0);
33+
expect(stderr).toBeFalsy();
34+
expect(stdout).toContain("max concurrent compilers: 1");
35+
});
36+
37+
it("should keep the array's own options with --extends", async () => {
38+
const { exitCode, stderr, stdout } = await run(__dirname, ["--extends", "./base.config.js"]);
39+
40+
expect(exitCode).toBe(0);
41+
expect(stderr).toBeFalsy();
42+
expect(stdout).toContain("max concurrent compilers: 1");
43+
});
44+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("first");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("second");
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use strict";
2+
3+
const ConcurrencyPlugin = require("./concurrency-plugin");
4+
5+
const child = (name) => ({
6+
name,
7+
entry: `./src/${name}.js`,
8+
output: { filename: `./dist-${name}.js` },
9+
mode: "development",
10+
plugins: [new ConcurrencyPlugin()],
11+
});
12+
13+
module.exports = Object.assign([child("first"), child("second")], { parallelism: 1 });

0 commit comments

Comments
 (0)