Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions packages/webpack-cli/src/webpack-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1678,6 +1678,7 @@ class WebpackCLI {
const compilersForDevServer =
possibleCompilers.length > 0 ? possibleCompilers : [compilers[0]];
const usedPorts: number[] = [];
const usedPublicPathsByPort = new Map<number, Set<string> | null>();

for (const compilerForDevServer of compilersForDevServer) {
if (compilerForDevServer.options.devServer === false) {
Expand Down Expand Up @@ -1710,11 +1711,37 @@ class WebpackCLI {

if (devServerConfiguration.port) {
const portNumber = Number(devServerConfiguration.port);
const devMiddlewarePublicPath = devServerConfiguration.devMiddleware?.publicPath;
const outputPublicPath = compilerForDevServer.options.output?.publicPath;
const currentPublicPath =
typeof devMiddlewarePublicPath === "string"
? devMiddlewarePublicPath
: typeof outputPublicPath === "string"
? outputPublicPath
: undefined;

if (usedPorts.includes(portNumber)) {
throw new Error(
"Unique ports must be specified for each devServer option in your webpack configuration. Alternatively, run only 1 devServer config using the --config-name flag to specify your desired config.",
);
const usedPublicPaths = usedPublicPathsByPort.get(portNumber);

if (
!currentPublicPath ||
!usedPublicPaths ||
usedPublicPaths.has(currentPublicPath)
) {
throw new Error(
"Unique ports must be specified for each devServer option in your webpack configuration. Alternatively, use unique devMiddleware.publicPath/output.publicPath values for configs sharing a port, or run only 1 devServer config using the --config-name flag to specify your desired config.",
);
}

usedPublicPaths.add(currentPublicPath);

continue;
}

if (currentPublicPath) {
usedPublicPathsByPort.set(portNumber, new Set([currentPublicPath]));
} else {
usedPublicPathsByPort.set(portNumber, null);
}

usedPorts.push(portNumber);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ exports[`basic serve usage should throw error when same ports in multicompiler:
<i> [webpack-dev-server] On Your Network (IPv4): http://x.x.x.x:<port>/
<i> [webpack-dev-server] On Your Network (IPv6): http://[x:x:x:x:x:x:x:x]:<port>/
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/test/serve/basic/public' directory
[webpack-cli] Error: Unique ports must be specified for each devServer option in your webpack configuration. Alternatively, run only 1 devServer config using the --config-name flag to specify your desired config.
[webpack-cli] Error: Unique ports must be specified for each devServer option in your webpack configuration. Alternatively, use unique devMiddleware.publicPath/output.publicPath values for configs sharing a port, or run only 1 devServer config using the --config-name flag to specify your desired config.
at stack"
`;

Expand All @@ -92,6 +92,14 @@ exports[`basic serve usage should work in multi compiler mode: stderr 1`] = `
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/test/serve/basic/public' directory"
`;

exports[`basic serve usage should work when same ports in multicompiler with unique public paths: stderr 1`] = `
"<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:<port>/, http://[::1]:<port>/
<i> [webpack-dev-server] On Your Network (IPv4): http://x.x.x.x:<port>/
<i> [webpack-dev-server] On Your Network (IPv6): http://[x:x:x:x:x:x:x:x]:<port>/
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/test/serve/basic/public' directory"
`;

exports[`basic serve usage should work with "--hot" and "--port" options: stderr 1`] = `
"<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:<port>/, http://[::1]:<port>/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const WebpackCLITestPlugin = require("../../utils/webpack-cli-test-plugin");
const devServerConfig = require("./helper/base-dev-server.config");

const getGetPort = () => import("get-port");

module.exports = async () => {
const sharedPort = await (await getGetPort()).default();

return [
{
name: "one",
mode: "development",
devtool: false,
output: {
publicPath: "/login/",
filename: "first-output/[name].js",
},
devServer: {
...devServerConfig,
port: sharedPort,
devMiddleware: {
...devServerConfig.devMiddleware,
publicPath: "/login/",
},
},
plugins: [new WebpackCLITestPlugin(["mode", "output"], false, "hooks.compilation.taps")],
},
{
name: "two",
mode: "development",
devtool: false,
entry: "./src/other.js",
output: {
publicPath: "/admin/",
filename: "second-output/[name].js",
},
devServer: {
...devServerConfig,
port: sharedPort,
devMiddleware: {
...devServerConfig.devMiddleware,
publicPath: "/admin/",
},
},
plugins: [new WebpackCLITestPlugin(["mode", "output"], false, "hooks.compilation.taps")],
},
];
};
12 changes: 12 additions & 0 deletions test/serve/basic/serve-basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,16 @@ describe("basic serve usage", () => {
// Due to racing logic, first dev server can be started and compiled, but then the second always fails
// expect(normalizeStdout(stdout)).toMatchSnapshot("stdout");
});

it("should work when same ports in multicompiler with unique public paths", async () => {
const { stderr, stdout } = await runWatch(
__dirname,
["serve", "--config", "same-ports-dev-server-unique-public-paths.config.js"],
normalStdKillOptions,
);

expect(normalizeStderr(stderr)).toMatchSnapshot("stderr");
expect(stderr).not.toContain("Unique ports must be specified");
expect(stdout).toBeDefined();
});
});