diff --git a/packages/webpack-cli/src/webpack-cli.ts b/packages/webpack-cli/src/webpack-cli.ts index dd88847a165..e1b64f3277c 100644 --- a/packages/webpack-cli/src/webpack-cli.ts +++ b/packages/webpack-cli/src/webpack-cli.ts @@ -1678,6 +1678,7 @@ class WebpackCLI { const compilersForDevServer = possibleCompilers.length > 0 ? possibleCompilers : [compilers[0]]; const usedPorts: number[] = []; + const usedPublicPathsByPort = new Map | null>(); for (const compilerForDevServer of compilersForDevServer) { if (compilerForDevServer.options.devServer === false) { @@ -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); diff --git a/test/serve/basic/__snapshots__/serve-basic.test.js.snap.devServer5.webpack5 b/test/serve/basic/__snapshots__/serve-basic.test.js.snap.devServer5.webpack5 index 4f18e0f6b3e..c7853395b42 100644 --- a/test/serve/basic/__snapshots__/serve-basic.test.js.snap.devServer5.webpack5 +++ b/test/serve/basic/__snapshots__/serve-basic.test.js.snap.devServer5.webpack5 @@ -71,7 +71,7 @@ exports[`basic serve usage should throw error when same ports in multicompiler: [webpack-dev-server] On Your Network (IPv4): http://x.x.x.x:/ [webpack-dev-server] On Your Network (IPv6): http://[x:x:x:x:x:x:x:x]:/ [webpack-dev-server] Content not from webpack is served from '/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" `; @@ -92,6 +92,14 @@ exports[`basic serve usage should work in multi compiler mode: stderr 1`] = ` [webpack-dev-server] Content not from webpack is served from '/test/serve/basic/public' directory" `; +exports[`basic serve usage should work when same ports in multicompiler with unique public paths: stderr 1`] = ` +" [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://localhost:/, http://[::1]:/ + [webpack-dev-server] On Your Network (IPv4): http://x.x.x.x:/ + [webpack-dev-server] On Your Network (IPv6): http://[x:x:x:x:x:x:x:x]:/ + [webpack-dev-server] Content not from webpack is served from '/test/serve/basic/public' directory" +`; + exports[`basic serve usage should work with "--hot" and "--port" options: stderr 1`] = ` " [webpack-dev-server] Project is running at: [webpack-dev-server] Loopback: http://localhost:/, http://[::1]:/ diff --git a/test/serve/basic/same-ports-dev-server-unique-public-paths.config.js b/test/serve/basic/same-ports-dev-server-unique-public-paths.config.js new file mode 100644 index 00000000000..e44ae51eeb9 --- /dev/null +++ b/test/serve/basic/same-ports-dev-server-unique-public-paths.config.js @@ -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")], + }, + ]; +}; diff --git a/test/serve/basic/serve-basic.test.js b/test/serve/basic/serve-basic.test.js index 3a40429160a..4512850b287 100644 --- a/test/serve/basic/serve-basic.test.js +++ b/test/serve/basic/serve-basic.test.js @@ -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(); + }); });