Skip to content

Commit c0cf560

Browse files
fix: assign distinct ports to dev servers sharing devServer options
Configurations can share one devServer object, the port assigned to the first dev server was written to it and reused by the others. Enable the test for multiple dev servers without ports. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
1 parent c84679a commit c0cf560

4 files changed

Lines changed: 88 additions & 48 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: assign distinct ports to dev servers of configurations that share the same `devServer` object.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2245,7 +2245,8 @@ class WebpackCLI {
22452245
const getDevServerOptions = (
22462246
devServerOptions: DevServerConfiguration | undefined,
22472247
): DevServerConfiguration => {
2248-
const configuration: DevServerConfiguration = devServerOptions || {};
2248+
// Configurations can share options, so a port assigned to one isn't used by others
2249+
const configuration: DevServerConfiguration = { ...devServerOptions };
22492250
const args: Record<string, WebpackArgument> = {};
22502251
const values: ProcessedArguments = {};
22512252

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,25 @@
1-
const { getFreePort } = require("../../utils/test-utils");
21
const WebpackCLITestPlugin = require("../../utils/webpack-cli-test-plugin");
3-
const { devServerConfig } = require("./helper/base-dev-server.config");
2+
const devServerConfig = require("./helper/base-dev-server.config");
43

5-
module.exports = async () => {
6-
const port1 = await getFreePort();
7-
const port2 = await getFreePort();
8-
9-
return [
10-
{
11-
name: "one",
12-
mode: "development",
13-
devtool: false,
14-
output: {
15-
filename: "first-output/[name].js",
16-
},
17-
devServer: {
18-
...devServerConfig,
19-
port: port1,
20-
},
21-
plugins: [new WebpackCLITestPlugin(["mode", "output"], false, "hooks.compilation.taps")],
4+
module.exports = [
5+
{
6+
name: "one",
7+
mode: "development",
8+
devtool: false,
9+
output: {
10+
filename: "first-output/[name].js",
2211
},
23-
{
24-
name: "two",
25-
mode: "development",
26-
devtool: false,
27-
entry: "./src/other.js",
28-
output: {
29-
filename: "second-output/[name].js",
30-
},
31-
devServer: {
32-
...devServerConfig,
33-
port: port2,
34-
},
12+
devServer: devServerConfig,
13+
plugins: [new WebpackCLITestPlugin(["mode", "output"], false, "hooks.compilation.taps")],
14+
},
15+
{
16+
name: "two",
17+
mode: "development",
18+
devtool: false,
19+
entry: "./src/other.js",
20+
output: {
21+
filename: "second-output/[name].js",
3522
},
36-
];
37-
};
23+
devServer: devServerConfig,
24+
},
25+
];

‎test/serve/basic/serve-basic.test.js‎

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22

33
const path = require("node:path");
4+
const [devServerVersion] = require("webpack-dev-server/package.json").version;
45
const {
56
getFreePort,
67
normalizeStderr,
@@ -10,6 +11,8 @@ const {
1011

1112
const testPath = path.resolve(__dirname);
1213

14+
const describeDevServer6 = devServerVersion === "5" ? describe.skip : describe;
15+
1316
const normalStdKillOptions = {
1417
stdoutKillStr: /webpack \d+\.\d+\.\d/,
1518
stderrKillStr: /Content not from webpack is served from/,
@@ -95,21 +98,64 @@ describe("basic serve usage", () => {
9598
expect(stdout).toContain("second-output/main.js");
9699
});
97100

98-
// TODO need fix in future, edge case
99-
// eslint-disable-next-line jest/no-disabled-tests
100-
it.skip("should work in multi compiler mode with multiple dev servers", async () => {
101-
const { stderr, stdout } = await runWatch(
102-
__dirname,
103-
["serve", "--config", "multi-dev-server.config.js"],
104-
normalStdKillOptions,
105-
);
101+
// TODO in the next major release, remove the condition, only dev servers with plugin support
102+
// select distinct ports for configurations without them
103+
describeDevServer6("with multiple dev servers", () => {
104+
it("should work in multi compiler mode without ports", async () => {
105+
const ports = [];
106+
const responses = {};
107+
const { exitCode, stdout } = await runWatch(
108+
__dirname,
109+
["serve", "--config", "multi-dev-server.config.js", "--watch-options-stdin"],
110+
{
111+
// Automatic ports start from the base one
112+
env: { WEBPACK_DEV_SERVER_BASE_PORT: String(port) },
113+
handler: (proc) => {
114+
let output = "";
115+
116+
proc.stderr.on("data", (chunk) => {
117+
output += chunk.toString();
118+
119+
if (ports.length > 0) {
120+
return;
121+
}
122+
123+
const found = [...output.matchAll(/Loopback: http:\/\/localhost:(\d+)\//g)].map(
124+
([, found]) => found,
125+
);
126+
127+
if (found.length < 2) {
128+
return;
129+
}
130+
131+
ports.push(...found);
132+
133+
// Every dev server serves all compilers
134+
Promise.all(
135+
ports.flatMap((port) =>
136+
["first-output", "second-output"].map((directory) =>
137+
fetch(
138+
`http://127.0.0.1:${port}/dev-server-my-public-path/${directory}/main.js`,
139+
).then((response) => {
140+
responses[`${port}:${directory}`] = response.status;
141+
}),
142+
),
143+
),
144+
).finally(() => {
145+
proc.stdin.end();
146+
});
147+
});
148+
},
149+
},
150+
);
106151

107-
expect(normalizeStderr(stderr)).toMatchSnapshot("stderr");
108-
expect(stdout).toContain("HotModuleReplacementPlugin");
109-
expect(stdout).toContain("one");
110-
expect(stdout).toContain("first-output/main.js");
111-
expect(stdout).toContain("two");
112-
expect(stdout).toContain("second-output/main.js");
152+
expect(exitCode).toBe(0);
153+
expect(new Set(ports).size).toBe(2);
154+
expect(Object.values(responses)).toEqual([200, 200, 200, 200]);
155+
expect(stdout).toContain("HotModuleReplacementPlugin");
156+
expect(stdout).toContain("first-output/main.js");
157+
expect(stdout).toContain("second-output/main.js");
158+
});
113159
});
114160

115161
it('should work with the "--mode" option', async () => {

0 commit comments

Comments
 (0)