require("express")(), "http"],
+ ["express", () => require("express")(), "https"],
+ ["express", () => require("express")(), "spdy"],
+ ["connect", () => require("connect")(), "http"],
+ ["connect", () => require("connect")(), "https"],
+ ["connect", () => require("connect")(), "spdy"],
+ ["connect", () => require("connect")(), "http2"],
+ ["connect (async)", () => require("connect")(), "http"],
+ [
+ "hono",
+ () => new (require("hono").Hono)(),
+ (_, app) => require("@hono/node-server").serve({ fetch: app.fetch }),
+ (_, devServer) => [
+ {
+ name: "webpack-dev-middleware",
+ middleware: wdm.honoWrapper(devServer.compiler),
+ },
+ ],
+ ],
+ [
+ "hono",
+ () => new (require("hono").Hono)(),
+ (_, app) =>
+ require("@hono/node-server").serve({
+ fetch: app.fetch,
+ createServer: require("node:https").createServer,
+ serverOptions: {
+ key: fs.readFileSync(
+ path.resolve(__dirname, "../fixtures/ssl/localhost-privkey.pem"),
+ ),
+ cert: fs.readFileSync(
+ path.resolve(__dirname, "../fixtures/ssl/localhost-cert.pem"),
+ ),
+ },
+ }),
+ (_, devServer) => [
+ {
+ name: "webpack-dev-middleware",
+ middleware: wdm.honoWrapper(devServer.compiler),
+ },
+ ],
+ ],
+ [
+ "hono",
+ () => new (require("hono").Hono)(),
+ {
+ type: (options, app) =>
+ require("@hono/node-server").serve({
+ fetch: app.fetch,
+ createServer: require("node:http2").createSecureServer,
+ serverOptions: options,
+ }),
+ options: {
+ allowHTTP1: true,
+ key: fs.readFileSync(
+ path.resolve(__dirname, "../fixtures/ssl/localhost-privkey.pem"),
+ ),
+ cert: fs.readFileSync(
+ path.resolve(__dirname, "../fixtures/ssl/localhost-cert.pem"),
+ ),
+ },
+ },
+ (_, devServer) => [
+ {
+ name: "webpack-dev-middleware",
+ middleware: wdm.honoWrapper(devServer.compiler),
+ },
+ ],
+ ],
+];
+
+describe("app option", () => {
+ for (const [appName, app, server, setupMiddlewares] of apps) {
+ let compiler;
+ let devServer;
+ let page;
+ let browser;
+ let pageErrors;
+ let consoleMessages;
+
+ describe(`should work using "${appName}" application and "${typeof server === "function" ? "custom server" : server}" server`, () => {
+ beforeEach(async () => {
+ compiler = webpack(config);
+
+ devServer = new Server(
+ {
+ static: {
+ directory: staticDirectory,
+ watch: false,
+ },
+ app,
+ server,
+ port,
+ setupMiddlewares:
+ typeof setupMiddlewares !== "undefined"
+ ? setupMiddlewares
+ : // eslint-disable-next-line no-undefined
+ undefined,
+ },
+ compiler,
+ );
+
+ await devServer.start();
+
+ ({ page, browser } = await runBrowser());
+
+ pageErrors = [];
+ consoleMessages = [];
+ });
+
+ afterEach(async () => {
+ await browser.close();
+ await devServer.stop();
+ await new Promise((resolve) => {
+ compiler.close(() => {
+ resolve();
+ });
+ });
+ });
+
+ it("should handle GET request to index route (/)", async () => {
+ page
+ .on("console", (message) => {
+ consoleMessages.push(message);
+ })
+ .on("pageerror", (error) => {
+ pageErrors.push(error);
+ });
+
+ const pageUrl = devServer.isTlsServer
+ ? `https://127.0.0.1:${port}/`
+ : `http://127.0.0.1:${port}/`;
+
+ const response = await page.goto(pageUrl, {
+ waitUntil: "networkidle0",
+ });
+
+ const HTTPVersion = await page.evaluate(
+ () => performance.getEntries()[0].nextHopProtocol,
+ );
+
+ if (
+ server === "spdy" ||
+ server === "http2" ||
+ (server.options && server.options.allowHTTP1)
+ ) {
+ expect(HTTPVersion).toEqual("h2");
+ } else {
+ expect(HTTPVersion).toEqual("http/1.1");
+ }
+
+ expect(response.status()).toMatchSnapshot("response status");
+ expect(await response.text()).toMatchSnapshot("response text");
+ expect(
+ consoleMessages.map((message) => message.text()),
+ ).toMatchSnapshot("console messages");
+ expect(pageErrors).toMatchSnapshot("page errors");
+ });
+ });
+ }
+});
diff --git a/test/e2e/host.test.js b/test/e2e/host.test.js
index 3217127115..4e6c108647 100644
--- a/test/e2e/host.test.js
+++ b/test/e2e/host.test.js
@@ -1,28 +1,53 @@
"use strict";
+const http = require("http");
const webpack = require("webpack");
const Server = require("../../lib/Server");
const config = require("../fixtures/client-config/webpack.config");
const runBrowser = require("../helpers/run-browser");
const port = require("../ports-map").host;
-const ipv4 = Server.internalIPSync("v4");
-const ipv6 = Server.internalIPSync("v6");
-// macos requires root for using ip v6
-const isMacOS = process.platform === "darwin";
+const ipv4 = Server.findIp("v4", false);
+const ipv6 = Server.findIp("v6", false);
-function getAddress(host, hostname) {
+async function getAddress(host, hostname) {
let address;
if (
typeof host === "undefined" ||
- (typeof host === "string" && host === "
")
+ (typeof host === "string" && (host === "" || host === "::"))
) {
address = "::";
- } else if (typeof host === "string" && host === "0.0.0.0") {
+ } else if (host === "0.0.0.0") {
address = "0.0.0.0";
- } else if (typeof host === "string" && host === "localhost") {
- address = parseFloat(process.versions.node) >= 18 ? "::1" : "127.0.0.1";
+ } else if (host === "::1") {
+ address = "::1";
+ } else if (host === "localhost") {
+ // It can be `127.0.0.1` or `::1` on different OS
+ const server = http.createServer((req, res) => {
+ res.statusCode = 200;
+ res.setHeader("Content-Type", "text/plain");
+ res.end("Hello World\n");
+ });
+
+ await new Promise((resolve) => {
+ server.listen({ host: "localhost", port: 23100 }, resolve);
+ });
+
+ address = server.address().address;
+
+ await new Promise((resolve, reject) => {
+ server.close((err) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ resolve();
+ });
+ });
+ } else if (host === "local-ipv6") {
+ address = "::";
} else {
address = hostname;
}
@@ -37,28 +62,17 @@ describe("host", () => {
undefined,
"0.0.0.0",
"::",
- "localhost",
"::1",
+ "localhost",
"127.0.0.1",
"local-ip",
"local-ipv4",
"local-ipv6",
];
- for (let host of hosts) {
+ for (const host of hosts) {
it(`should work using "${host}" host and port as number`, async () => {
const compiler = webpack(config);
-
- if (!ipv6 || isMacOS) {
- if (host === "::") {
- host = "127.0.0.1";
- } else if (host === "::1") {
- host = "127.0.0.1";
- } else if (host === "local-ipv6") {
- host = "127.0.0.1";
- }
- }
-
const devServerOptions = { port };
if (host !== "") {
@@ -69,24 +83,28 @@ describe("host", () => {
let hostname = host;
- if (hostname === "0.0.0.0") {
- hostname = "127.0.0.1";
- } else if (
- hostname === "" ||
- typeof hostname === "undefined" ||
- hostname === "::" ||
- hostname === "::1"
- ) {
+ if (hostname === "" || typeof hostname === "undefined") {
+ // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise.
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "0.0.0.0") {
+ hostname = ipv4;
+ } else if (hostname === "::") {
+ // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0).
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "::1") {
hostname = "[::1]";
} else if (hostname === "local-ip" || hostname === "local-ipv4") {
hostname = ipv4;
} else if (hostname === "local-ipv6") {
- hostname = `[${ipv6}]`;
+ // For test env where network ipv6 doesn't work
+ hostname = ipv6 ? `[${ipv6}]` : "[::1]";
}
await server.start();
- expect(server.server.address()).toMatchObject(getAddress(host, hostname));
+ expect(server.server.address()).toMatchObject(
+ await getAddress(host, hostname),
+ );
const { page, browser } = await runBrowser();
@@ -121,17 +139,6 @@ describe("host", () => {
it(`should work using "${host}" host and port as string`, async () => {
const compiler = webpack(config);
-
- if (!ipv6 || isMacOS) {
- if (host === "::") {
- host = "127.0.0.1";
- } else if (host === "::1") {
- host = "127.0.0.1";
- } else if (host === "local-ipv6") {
- host = "127.0.0.1";
- }
- }
-
const devServerOptions = { port: `${port}` };
if (host !== "") {
@@ -142,24 +149,28 @@ describe("host", () => {
let hostname = host;
- if (hostname === "0.0.0.0") {
- hostname = "127.0.0.1";
- } else if (
- hostname === "" ||
- typeof hostname === "undefined" ||
- hostname === "::" ||
- hostname === "::1"
- ) {
+ if (hostname === "" || typeof hostname === "undefined") {
+ // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise.
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "0.0.0.0") {
+ hostname = ipv4;
+ } else if (hostname === "::") {
+ // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0).
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "::1") {
hostname = "[::1]";
} else if (hostname === "local-ip" || hostname === "local-ipv4") {
hostname = ipv4;
} else if (hostname === "local-ipv6") {
- hostname = `[${ipv6}]`;
+ // For test env where network ipv6 doesn't work
+ hostname = ipv6 ? `[${ipv6}]` : "[::1]";
}
await server.start();
- expect(server.server.address()).toMatchObject(getAddress(host, hostname));
+ expect(server.server.address()).toMatchObject(
+ await getAddress(host, hostname),
+ );
const { page, browser } = await runBrowser();
@@ -197,16 +208,6 @@ describe("host", () => {
process.env.WEBPACK_DEV_SERVER_BASE_PORT = port;
- if (!ipv6 || isMacOS) {
- if (host === "::") {
- host = "127.0.0.1";
- } else if (host === "::1") {
- host = "127.0.0.1";
- } else if (host === "local-ipv6") {
- host = "127.0.0.1";
- }
- }
-
const devServerOptions = { port: "auto" };
if (host !== "") {
@@ -217,24 +218,28 @@ describe("host", () => {
let hostname = host;
- if (hostname === "0.0.0.0") {
- hostname = "127.0.0.1";
- } else if (
- hostname === "" ||
- typeof hostname === "undefined" ||
- hostname === "::" ||
- hostname === "::1"
- ) {
+ if (hostname === "" || typeof hostname === "undefined") {
+ // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise.
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "0.0.0.0") {
+ hostname = ipv4;
+ } else if (hostname === "::") {
+ // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0).
+ hostname = ipv6 ? `[${ipv6}]` : ipv4;
+ } else if (hostname === "::1") {
hostname = "[::1]";
} else if (hostname === "local-ip" || hostname === "local-ipv4") {
hostname = ipv4;
} else if (hostname === "local-ipv6") {
- hostname = `[${ipv6}]`;
+ // For test env where network ipv6 doesn't work
+ hostname = ipv6 ? `[${ipv6}]` : "[::1]";
}
await server.start();
- expect(server.server.address()).toMatchObject(getAddress(host, hostname));
+ expect(server.server.address()).toMatchObject(
+ await getAddress(host, hostname),
+ );
const address = server.server.address();
const { page, browser } = await runBrowser();
diff --git a/test/e2e/on-listening.test.js b/test/e2e/on-listening.test.js
index 6e97561b14..4a88d908e7 100644
--- a/test/e2e/on-listening.test.js
+++ b/test/e2e/on-listening.test.js
@@ -26,12 +26,18 @@ describe("onListening option", () => {
onListeningIsRunning = true;
- devServer.app.get("/listening/some/path", (_, response) => {
- response.send("listening");
- });
-
- devServer.app.post("/listening/some/path", (_, response) => {
- response.send("listening POST");
+ devServer.app.use("/listening/some/path", (req, res, next) => {
+ if (req.method === "GET") {
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
+ res.end("listening");
+ return;
+ } else if (req.method === "POST") {
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
+ res.end("listening POST");
+ return;
+ }
+
+ return next();
});
},
port,
diff --git a/test/e2e/overlay.test.js b/test/e2e/overlay.test.js
index 7e1efb1122..023b9a331a 100644
--- a/test/e2e/overlay.test.js
+++ b/test/e2e/overlay.test.js
@@ -1978,10 +1978,16 @@ describe("overlay", () => {
}),
).toMatchSnapshot("page html");
expect(
- await prettier.format(overlayHtml, {
- parser: "html",
- plugins: [prettierHTML, prettierCSS],
- }),
+ await prettier.format(
+ overlayHtml.replace(
+ /