webpack-dev-server 5.0.3 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Server.js CHANGED
@@ -18,9 +18,6 @@ const schema = require("./options.json");
18
18
  /** @typedef {import("webpack").Stats} Stats */
19
19
  /** @typedef {import("webpack").MultiStats} MultiStats */
20
20
  /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
21
- /** @typedef {import("express").NextFunction} NextFunction */
22
- /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
23
- /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
24
21
  /** @typedef {import("chokidar").WatchOptions} WatchOptions */
25
22
  /** @typedef {import("chokidar").FSWatcher} FSWatcher */
26
23
  /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
@@ -34,14 +31,32 @@ const schema = require("./options.json");
34
31
  /** @typedef {import("ipaddr.js").IPv4} IPv4 */
35
32
  /** @typedef {import("ipaddr.js").IPv6} IPv6 */
36
33
  /** @typedef {import("net").Socket} Socket */
34
+ /** @typedef {import("http").Server} HTTPServer*/
37
35
  /** @typedef {import("http").IncomingMessage} IncomingMessage */
38
36
  /** @typedef {import("http").ServerResponse} ServerResponse */
39
37
  /** @typedef {import("open").Options} OpenOptions */
38
+ /** @typedef {import("express").Application} ExpressApplication */
39
+ /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
40
+ /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
41
+ /** @typedef {import("express").Request} ExpressRequest */
42
+ /** @typedef {import("express").Response} ExpressResponse */
43
+
44
+ /** @typedef {(err?: any) => void} NextFunction */
45
+ /** @typedef {(req: IncomingMessage, res: ServerResponse) => void} SimpleHandleFunction */
46
+ /** @typedef {(req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} NextHandleFunction */
47
+ /** @typedef {(err: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} ErrorHandleFunction */
48
+ /** @typedef {SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction} HandleFunction */
40
49
 
41
50
  /** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
42
51
 
43
- /** @typedef {import("express").Request} Request */
44
- /** @typedef {import("express").Response} Response */
52
+ /**
53
+ * @template {BasicApplication} [T=ExpressApplication]
54
+ * @typedef {T extends ExpressApplication ? ExpressRequest : IncomingMessage} Request
55
+ */
56
+ /**
57
+ * @template {BasicApplication} [T=ExpressApplication]
58
+ * @typedef {T extends ExpressApplication ? ExpressResponse : ServerResponse} Response
59
+ */
45
60
 
46
61
  /**
47
62
  * @template {Request} T
@@ -88,8 +103,16 @@ const schema = require("./options.json");
88
103
  */
89
104
 
90
105
  /**
106
+ * @template {BasicApplication} [A=ExpressApplication]
107
+ * @template {BasicServer} [S=import("http").Server]
108
+ * @typedef {"http" | "https" | "spdy" | "http2" | string | function(ServerOptions, A): S} ServerType
109
+ */
110
+
111
+ /**
112
+ * @template {BasicApplication} [A=ExpressApplication]
113
+ * @template {BasicServer} [S=import("http").Server]
91
114
  * @typedef {Object} ServerConfiguration
92
- * @property {"http" | "https" | "spdy" | string} [type]
115
+ * @property {ServerType<A, S>} [type]
93
116
  * @property {ServerOptions} [options]
94
117
  */
95
118
 
@@ -173,10 +196,23 @@ const schema = require("./options.json");
173
196
  */
174
197
 
175
198
  /**
176
- * @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler} Middleware
199
+ * @template {BasicApplication} [T=ExpressApplication]
200
+ * @typedef {T extends ExpressApplication ? ExpressRequestHandler | ExpressErrorRequestHandler : HandleFunction} MiddlewareHandler
177
201
  */
178
202
 
179
203
  /**
204
+ * @typedef {{ name?: string, path?: string, middleware: MiddlewareHandler }} MiddlewareObject
205
+ */
206
+
207
+ /**
208
+ * @typedef {MiddlewareObject | MiddlewareHandler } Middleware
209
+ */
210
+
211
+ /** @typedef {import("net").Server | import("tls").Server} BasicServer */
212
+
213
+ /**
214
+ * @template {BasicApplication} [A=ExpressApplication]
215
+ * @template {BasicServer} [S=import("http").Server]
180
216
  * @typedef {Object} Configuration
181
217
  * @property {boolean | string} [ipc]
182
218
  * @property {Host} [host]
@@ -190,17 +226,16 @@ const schema = require("./options.json");
190
226
  * @property {boolean | Record<string, never> | BonjourOptions} [bonjour]
191
227
  * @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
192
228
  * @property {boolean | string | Static | Array<string | Static>} [static]
193
- * @property {boolean | ServerOptions} [https]
194
- * @property {boolean} [http2]
195
- * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server]
229
+ * @property {ServerType<A, S> | ServerConfiguration<A, S>} [server]
230
+ * @property {() => Promise<A>} [app]
196
231
  * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
197
232
  * @property {ProxyConfigArray} [proxy]
198
233
  * @property {boolean | string | Open | Array<string | Open>} [open]
199
234
  * @property {boolean} [setupExitSignals]
200
235
  * @property {boolean | ClientConfiguration} [client]
201
- * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
202
- * @property {(devServer: Server) => void} [onListening]
203
- * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
236
+ * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response> | undefined) => Headers)} [headers]
237
+ * @property {(devServer: Server<A, S>) => void} [onListening]
238
+ * @property {(middlewares: Middleware[], devServer: Server<A, S>) => Middleware[]} [setupMiddlewares]
204
239
  */
205
240
 
206
241
  if (!process.env.WEBPACK_SERVE) {
@@ -245,10 +280,46 @@ const encodeOverlaySettings = (setting) =>
245
280
  ? encodeURIComponent(setting.toString())
246
281
  : setting;
247
282
 
283
+ // Working for overload, because typescript doesn't support this yes
284
+ /**
285
+ * @overload
286
+ * @param {NextHandleFunction} fn
287
+ * @returns {BasicApplication}
288
+ */
289
+ /**
290
+ * @overload
291
+ * @param {HandleFunction} fn
292
+ * @returns {BasicApplication}
293
+ */
294
+ /**
295
+ * @overload
296
+ * @param {string} route
297
+ * @param {NextHandleFunction} fn
298
+ * @returns {BasicApplication}
299
+ */
300
+ /**
301
+ * @param {string} route
302
+ * @param {HandleFunction} fn
303
+ * @returns {BasicApplication}
304
+ */
305
+ // eslint-disable-next-line no-unused-vars
306
+ function useFn(route, fn) {
307
+ return /** @type {BasicApplication} */ ({});
308
+ }
309
+
310
+ /**
311
+ * @typedef {Object} BasicApplication
312
+ * @property {typeof useFn} use
313
+ */
314
+
315
+ /**
316
+ * @template {BasicApplication} [A=ExpressApplication]
317
+ * @template {BasicServer} [S=HTTPServer]
318
+ */
248
319
  class Server {
249
320
  /**
250
- * @param {Configuration | Compiler | MultiCompiler} options
251
- * @param {Compiler | MultiCompiler | Configuration} compiler
321
+ * @param {Configuration<A, S>} options
322
+ * @param {Compiler | MultiCompiler} compiler
252
323
  */
253
324
  constructor(options = {}, compiler) {
254
325
  validate(/** @type {Schema} */ (schema), options, {
@@ -256,12 +327,12 @@ class Server {
256
327
  baseDataPath: "options",
257
328
  });
258
329
 
259
- this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler);
330
+ this.compiler = compiler;
260
331
  /**
261
332
  * @type {ReturnType<Compiler["getInfrastructureLogger"]>}
262
333
  * */
263
334
  this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
264
- this.options = /** @type {Configuration} */ (options);
335
+ this.options = options;
265
336
  /**
266
337
  * @type {FSWatcher[]}
267
338
  */
@@ -324,11 +395,59 @@ class Server {
324
395
  }
325
396
 
326
397
  /**
327
- * @param {string} gateway
398
+ * @param {string} gatewayOrFamily or family
399
+ * @param {boolean} [isInternal] ip should be internal
328
400
  * @returns {string | undefined}
329
401
  */
330
- static findIp(gateway) {
331
- const gatewayIp = ipaddr.parse(gateway);
402
+ static findIp(gatewayOrFamily, isInternal) {
403
+ if (gatewayOrFamily === "v4" || gatewayOrFamily === "v6") {
404
+ let host;
405
+
406
+ const networks = Object.values(os.networkInterfaces())
407
+ .flatMap((networks) => networks ?? [])
408
+ .filter((network) => {
409
+ if (!network || !network.address) {
410
+ return false;
411
+ }
412
+
413
+ if (network.family !== `IP${gatewayOrFamily}`) {
414
+ return false;
415
+ }
416
+
417
+ if (
418
+ typeof isInternal !== "undefined" &&
419
+ network.internal !== isInternal
420
+ ) {
421
+ return false;
422
+ }
423
+
424
+ if (gatewayOrFamily === "v6") {
425
+ const range = ipaddr.parse(network.address).range();
426
+
427
+ if (
428
+ range !== "ipv4Mapped" &&
429
+ range !== "uniqueLocal" &&
430
+ range !== "loopback"
431
+ ) {
432
+ return false;
433
+ }
434
+ }
435
+
436
+ return network.address;
437
+ });
438
+
439
+ for (const network of networks) {
440
+ host = network.address;
441
+
442
+ if (host.includes(":")) {
443
+ host = `[${host}]`;
444
+ }
445
+ }
446
+
447
+ return host;
448
+ }
449
+
450
+ const gatewayIp = ipaddr.parse(gatewayOrFamily);
332
451
 
333
452
  // Look for the matching interface in all local interfaces.
334
453
  for (const addresses of Object.values(os.networkInterfaces())) {
@@ -348,32 +467,22 @@ class Server {
348
467
  }
349
468
  }
350
469
 
470
+ // TODO remove me in the next major release, we have `findIp`
351
471
  /**
352
472
  * @param {"v4" | "v6"} family
353
473
  * @returns {Promise<string | undefined>}
354
474
  */
355
475
  static async internalIP(family) {
356
- try {
357
- const { gateway } = await require("default-gateway")[family]();
358
-
359
- return Server.findIp(gateway);
360
- } catch {
361
- // ignore
362
- }
476
+ return Server.findIp(family, false);
363
477
  }
364
478
 
479
+ // TODO remove me in the next major release, we have `findIp`
365
480
  /**
366
481
  * @param {"v4" | "v6"} family
367
482
  * @returns {string | undefined}
368
483
  */
369
484
  static internalIPSync(family) {
370
- try {
371
- const { gateway } = require("default-gateway")[family].sync();
372
-
373
- return Server.findIp(gateway);
374
- } catch {
375
- // ignore
376
- }
485
+ return Server.findIp(family, false);
377
486
  }
378
487
 
379
488
  /**
@@ -383,14 +492,12 @@ class Server {
383
492
  static async getHostname(hostname) {
384
493
  if (hostname === "local-ip") {
385
494
  return (
386
- (await Server.internalIP("v4")) ||
387
- (await Server.internalIP("v6")) ||
388
- "0.0.0.0"
495
+ Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0"
389
496
  );
390
497
  } else if (hostname === "local-ipv4") {
391
- return (await Server.internalIP("v4")) || "0.0.0.0";
498
+ return Server.findIp("v4", false) || "0.0.0.0";
392
499
  } else if (hostname === "local-ipv6") {
393
- return (await Server.internalIP("v6")) || "::";
500
+ return Server.findIp("v6", false) || "::";
394
501
  }
395
502
 
396
503
  return hostname;
@@ -470,7 +577,11 @@ class Server {
470
577
  * @returns bool
471
578
  */
472
579
  static isWebTarget(compiler) {
473
- // TODO improve for the next major version - we should store `web` and other targets in `compiler.options.environment`
580
+ if (compiler.platform && compiler.platform.web) {
581
+ return compiler.platform.web;
582
+ }
583
+
584
+ // TODO improve for the next major version and keep only `webTargets` to fallback for old versions
474
585
  if (
475
586
  compiler.options.externalsPresets &&
476
587
  compiler.options.externalsPresets.web
@@ -490,6 +601,7 @@ class Server {
490
601
  "webworker",
491
602
  "electron-preload",
492
603
  "electron-renderer",
604
+ "nwjs",
493
605
  "node-webkit",
494
606
  // eslint-disable-next-line no-undefined
495
607
  undefined,
@@ -537,9 +649,7 @@ class Server {
537
649
  if (typeof webSocketURL.protocol !== "undefined") {
538
650
  protocol = webSocketURL.protocol;
539
651
  } else {
540
- protocol =
541
- /** @type {ServerConfiguration} */
542
- (this.options.server).type === "http" ? "ws:" : "wss:";
652
+ protocol = this.isTlsServer ? "wss:" : "ws:";
543
653
  }
544
654
 
545
655
  searchParams.set("protocol", protocol);
@@ -1007,39 +1117,41 @@ class Server {
1007
1117
  ? options.hot
1008
1118
  : true;
1009
1119
 
1010
- options.server = {
1011
- type:
1012
- // eslint-disable-next-line no-nested-ternary
1013
- typeof options.server === "string"
1014
- ? options.server
1015
- : typeof (options.server || {}).type === "string"
1016
- ? /** @type {ServerConfiguration} */ (options.server).type || "http"
1017
- : "http",
1018
- options: {
1019
- .../** @type {ServerOptions} */ (options.https),
1020
- .../** @type {ServerConfiguration} */ (options.server || {}).options,
1021
- },
1022
- };
1120
+ if (
1121
+ typeof options.server === "function" ||
1122
+ typeof options.server === "string"
1123
+ ) {
1124
+ options.server = {
1125
+ type: options.server,
1126
+ options: {},
1127
+ };
1128
+ } else {
1129
+ const serverOptions =
1130
+ /** @type {ServerConfiguration<A, S>} */
1131
+ (options.server || {});
1132
+
1133
+ options.server = {
1134
+ type: serverOptions.type || "http",
1135
+ options: { ...serverOptions.options },
1136
+ };
1137
+ }
1138
+
1139
+ const serverOptions = /** @type {ServerOptions} */ (options.server.options);
1023
1140
 
1024
1141
  if (
1025
1142
  options.server.type === "spdy" &&
1026
- typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
1027
- "undefined"
1143
+ typeof serverOptions.spdy === "undefined"
1028
1144
  ) {
1029
- /** @type {ServerOptions} */
1030
- (options.server.options).spdy = {
1031
- protocols: ["h2", "http/1.1"],
1032
- };
1145
+ serverOptions.spdy = { protocols: ["h2", "http/1.1"] };
1033
1146
  }
1034
1147
 
1035
- if (options.server.type === "https" || options.server.type === "spdy") {
1036
- if (
1037
- typeof (
1038
- /** @type {ServerOptions} */ (options.server.options).requestCert
1039
- ) === "undefined"
1040
- ) {
1041
- /** @type {ServerOptions} */
1042
- (options.server.options).requestCert = false;
1148
+ if (
1149
+ options.server.type === "https" ||
1150
+ options.server.type === "http2" ||
1151
+ options.server.type === "spdy"
1152
+ ) {
1153
+ if (typeof serverOptions.requestCert === "undefined") {
1154
+ serverOptions.requestCert = false;
1043
1155
  }
1044
1156
 
1045
1157
  const httpsProperties =
@@ -1047,19 +1159,13 @@ class Server {
1047
1159
  (["ca", "cert", "crl", "key", "pfx"]);
1048
1160
 
1049
1161
  for (const property of httpsProperties) {
1050
- if (
1051
- typeof (
1052
- /** @type {ServerOptions} */ (options.server.options)[property]
1053
- ) === "undefined"
1054
- ) {
1162
+ if (typeof serverOptions[property] === "undefined") {
1055
1163
  // eslint-disable-next-line no-continue
1056
1164
  continue;
1057
1165
  }
1058
1166
 
1059
1167
  /** @type {any} */
1060
- const value =
1061
- /** @type {ServerOptions} */
1062
- (options.server.options)[property];
1168
+ const value = serverOptions[property];
1063
1169
  /**
1064
1170
  * @param {string | Buffer | undefined} item
1065
1171
  * @returns {string | Buffer | undefined}
@@ -1087,17 +1193,14 @@ class Server {
1087
1193
  };
1088
1194
 
1089
1195
  /** @type {any} */
1090
- (options.server.options)[property] = Array.isArray(value)
1196
+ (serverOptions)[property] = Array.isArray(value)
1091
1197
  ? value.map((item) => readFile(item))
1092
1198
  : readFile(value);
1093
1199
  }
1094
1200
 
1095
1201
  let fakeCert;
1096
1202
 
1097
- if (
1098
- !(/** @type {ServerOptions} */ (options.server.options).key) ||
1099
- !(/** @type {ServerOptions} */ (options.server.options).cert)
1100
- ) {
1203
+ if (!serverOptions.key || !serverOptions.cert) {
1101
1204
  const certificateDir = Server.findCacheDir();
1102
1205
  const certificatePath = path.join(certificateDir, "server.pem");
1103
1206
  let certificateExists;
@@ -1116,13 +1219,11 @@ class Server {
1116
1219
 
1117
1220
  // cert is more than 30 days old, kill it with fire
1118
1221
  if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
1119
- const { rimraf } = require("rimraf");
1120
-
1121
1222
  this.logger.info(
1122
1223
  "SSL certificate is more than 30 days old. Removing...",
1123
1224
  );
1124
1225
 
1125
- await rimraf(certificatePath);
1226
+ await fs.promises.rm(certificatePath, { recursive: true });
1126
1227
 
1127
1228
  certificateExists = false;
1128
1229
  }
@@ -1131,7 +1232,6 @@ class Server {
1131
1232
  if (!certificateExists) {
1132
1233
  this.logger.info("Generating SSL certificate...");
1133
1234
 
1134
- // @ts-ignore
1135
1235
  const selfsigned = require("selfsigned");
1136
1236
  const attributes = [{ name: "commonName", value: "localhost" }];
1137
1237
  const pems = selfsigned.generate(attributes, {
@@ -1212,14 +1312,8 @@ class Server {
1212
1312
  this.logger.info(`SSL certificate: ${certificatePath}`);
1213
1313
  }
1214
1314
 
1215
- /** @type {ServerOptions} */
1216
- (options.server.options).key =
1217
- /** @type {ServerOptions} */
1218
- (options.server.options).key || fakeCert;
1219
- /** @type {ServerOptions} */
1220
- (options.server.options).cert =
1221
- /** @type {ServerOptions} */
1222
- (options.server.options).cert || fakeCert;
1315
+ serverOptions.key = serverOptions.key || fakeCert;
1316
+ serverOptions.cert = serverOptions.cert || fakeCert;
1223
1317
  }
1224
1318
 
1225
1319
  if (typeof options.ipc === "boolean") {
@@ -1281,15 +1375,15 @@ class Server {
1281
1375
  */
1282
1376
  const result = [];
1283
1377
 
1284
- options.open.forEach((item) => {
1378
+ for (const item of options.open) {
1285
1379
  if (typeof item === "string") {
1286
1380
  result.push({ target: item, options: defaultOpenOptions });
1287
-
1288
- return;
1381
+ // eslint-disable-next-line no-continue
1382
+ continue;
1289
1383
  }
1290
1384
 
1291
1385
  result.push(...getOpenItemsFromObject(item));
1292
- });
1386
+ }
1293
1387
 
1294
1388
  /** @type {NormalizedOpen[]} */
1295
1389
  (options.open) = result;
@@ -1522,8 +1616,9 @@ class Server {
1522
1616
  }
1523
1617
 
1524
1618
  /**
1619
+ * @template T
1525
1620
  * @private
1526
- * @returns {string}
1621
+ * @returns {T}
1527
1622
  */
1528
1623
  getServerTransport() {
1529
1624
  let implementation;
@@ -1553,9 +1648,8 @@ class Server {
1553
1648
  try {
1554
1649
  // eslint-disable-next-line import/no-dynamic-require
1555
1650
  implementation = require(
1556
- /** @type {WebSocketServerConfiguration} */ (
1557
- this.options.webSocketServer
1558
- ).type,
1651
+ /** @type {WebSocketServerConfiguration} */
1652
+ (this.options.webSocketServer).type,
1559
1653
  );
1560
1654
  } catch (error) {
1561
1655
  implementationFound = false;
@@ -1563,9 +1657,9 @@ class Server {
1563
1657
  }
1564
1658
  break;
1565
1659
  case "function":
1566
- implementation = /** @type {WebSocketServerConfiguration} */ (
1567
- this.options.webSocketServer
1568
- ).type;
1660
+ implementation =
1661
+ /** @type {WebSocketServerConfiguration} */
1662
+ (this.options.webSocketServer).type;
1569
1663
  break;
1570
1664
  default:
1571
1665
  implementationFound = false;
@@ -1631,12 +1725,22 @@ class Server {
1631
1725
  * @returns {Promise<void>}
1632
1726
  */
1633
1727
  async initialize() {
1728
+ this.setupHooks();
1729
+
1730
+ await this.setupApp();
1731
+ await this.createServer();
1732
+
1634
1733
  if (this.options.webSocketServer) {
1635
1734
  const compilers =
1636
1735
  /** @type {MultiCompiler} */
1637
1736
  (this.compiler).compilers || [this.compiler];
1638
1737
 
1639
- compilers.forEach((compiler) => {
1738
+ for (const compiler of compilers) {
1739
+ if (compiler.options.devServer === false) {
1740
+ // eslint-disable-next-line no-continue
1741
+ continue;
1742
+ }
1743
+
1640
1744
  this.addAdditionalEntries(compiler);
1641
1745
 
1642
1746
  const webpack = compiler.webpack || require("webpack");
@@ -1661,7 +1765,7 @@ class Server {
1661
1765
  plugin.apply(compiler);
1662
1766
  }
1663
1767
  }
1664
- });
1768
+ }
1665
1769
 
1666
1770
  if (
1667
1771
  this.options.client &&
@@ -1671,16 +1775,9 @@ class Server {
1671
1775
  }
1672
1776
  }
1673
1777
 
1674
- this.setupHooks();
1675
- this.setupApp();
1676
- this.setupHostHeaderCheck();
1677
- this.setupDevMiddleware();
1678
- // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1679
- this.setupBuiltInRoutes();
1680
1778
  this.setupWatchFiles();
1681
1779
  this.setupWatchStaticFiles();
1682
1780
  this.setupMiddlewares();
1683
- this.createServer();
1684
1781
 
1685
1782
  if (this.options.setupExitSignals) {
1686
1783
  const signals = ["SIGINT", "SIGTERM"];
@@ -1718,24 +1815,30 @@ class Server {
1718
1815
 
1719
1816
  // Proxy WebSocket without the initial http request
1720
1817
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1721
- /** @type {RequestHandler[]} */
1722
- (this.webSocketProxies).forEach((webSocketProxy) => {
1723
- /** @type {import("http").Server} */
1818
+ const webSocketProxies =
1819
+ /** @type {RequestHandler[]} */
1820
+ (this.webSocketProxies);
1821
+
1822
+ for (const webSocketProxy of webSocketProxies) {
1823
+ /** @type {S} */
1724
1824
  (this.server).on(
1725
1825
  "upgrade",
1726
1826
  /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
1727
1827
  (webSocketProxy).upgrade,
1728
1828
  );
1729
- }, this);
1829
+ }
1730
1830
  }
1731
1831
 
1732
1832
  /**
1733
1833
  * @private
1734
- * @returns {void}
1834
+ * @returns {Promise<void>}
1735
1835
  */
1736
- setupApp() {
1737
- /** @type {import("express").Application | undefined}*/
1738
- this.app = new /** @type {any} */ (getExpress())();
1836
+ async setupApp() {
1837
+ /** @type {A | undefined}*/
1838
+ this.app =
1839
+ typeof this.options.app === "function"
1840
+ ? await this.options.app()
1841
+ : getExpress()();
1739
1842
  }
1740
1843
 
1741
1844
  /**
@@ -1789,204 +1892,297 @@ class Server {
1789
1892
  * @private
1790
1893
  * @returns {void}
1791
1894
  */
1792
- setupHostHeaderCheck() {
1793
- /** @type {import("express").Application} */
1794
- (this.app).all(
1795
- "*",
1796
- /**
1797
- * @param {Request} req
1798
- * @param {Response} res
1799
- * @param {NextFunction} next
1800
- * @returns {void}
1801
- */
1802
- (req, res, next) => {
1803
- if (
1804
- this.checkHeader(
1805
- /** @type {{ [key: string]: string | undefined }} */
1806
- (req.headers),
1807
- "host",
1808
- )
1809
- ) {
1810
- return next();
1811
- }
1895
+ setupWatchStaticFiles() {
1896
+ const watchFiles = /** @type {NormalizedStatic[]} */ (this.options.static);
1812
1897
 
1813
- res.send("Invalid Host header");
1814
- },
1815
- );
1898
+ if (watchFiles.length > 0) {
1899
+ for (const item of watchFiles) {
1900
+ if (item.watch) {
1901
+ this.watchFiles(item.directory, item.watch);
1902
+ }
1903
+ }
1904
+ }
1816
1905
  }
1817
1906
 
1818
1907
  /**
1819
1908
  * @private
1820
1909
  * @returns {void}
1821
1910
  */
1822
- setupDevMiddleware() {
1823
- const webpackDevMiddleware = require("webpack-dev-middleware");
1911
+ setupWatchFiles() {
1912
+ const watchFiles = /** @type {WatchFiles[]} */ (this.options.watchFiles);
1824
1913
 
1825
- // middleware for serving webpack bundle
1826
- this.middleware = webpackDevMiddleware(
1827
- this.compiler,
1828
- this.options.devMiddleware,
1829
- );
1914
+ if (watchFiles.length > 0) {
1915
+ for (const item of watchFiles) {
1916
+ this.watchFiles(item.paths, item.options);
1917
+ }
1918
+ }
1830
1919
  }
1831
1920
 
1832
1921
  /**
1833
1922
  * @private
1834
1923
  * @returns {void}
1835
1924
  */
1836
- setupBuiltInRoutes() {
1837
- const { app, middleware } = this;
1925
+ setupMiddlewares() {
1926
+ /**
1927
+ * @type {Array<Middleware>}
1928
+ */
1929
+ let middlewares = [];
1838
1930
 
1839
- /** @type {import("express").Application} */
1840
- (app).get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
1841
- res.setHeader("Content-Type", "application/javascript");
1931
+ // Register setup host header check for security
1932
+ middlewares.push({
1933
+ name: "host-header-check",
1934
+ /**
1935
+ * @param {Request} req
1936
+ * @param {Response} res
1937
+ * @param {NextFunction} next
1938
+ * @returns {void}
1939
+ */
1940
+ middleware: (req, res, next) => {
1941
+ const headers =
1942
+ /** @type {{ [key: string]: string | undefined }} */
1943
+ (req.headers);
1944
+ const headerName = headers[":authority"] ? ":authority" : "host";
1842
1945
 
1843
- const clientPath = path.join(__dirname, "..", "client");
1946
+ if (this.checkHeader(headers, headerName)) {
1947
+ next();
1948
+ return;
1949
+ }
1844
1950
 
1845
- res.sendFile(path.join(clientPath, "modules/sockjs-client/index.js"));
1951
+ res.statusCode = 403;
1952
+ res.end("Invalid Host header");
1953
+ },
1846
1954
  });
1847
1955
 
1848
- /** @type {import("express").Application} */
1849
- (app).get("/webpack-dev-server/invalidate", (_req, res) => {
1850
- this.invalidate();
1956
+ const isHTTP2 =
1957
+ /** @type {ServerConfiguration<A, S>} */ (this.options.server).type ===
1958
+ "http2";
1851
1959
 
1852
- res.end();
1853
- });
1960
+ if (isHTTP2) {
1961
+ // TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
1962
+ middlewares.push({
1963
+ name: "http2-status-message-patch",
1964
+ middleware:
1965
+ /** @type {NextHandleFunction} */
1966
+ (_req, res, next) => {
1967
+ Object.defineProperty(res, "statusMessage", {
1968
+ get() {
1969
+ return "";
1970
+ },
1971
+ set() {},
1972
+ });
1854
1973
 
1855
- /** @type {import("express").Application} */
1856
- (app).get("/webpack-dev-server/open-editor", (req, res) => {
1857
- const fileName = req.query.fileName;
1974
+ next();
1975
+ },
1976
+ });
1977
+ }
1858
1978
 
1859
- if (typeof fileName === "string") {
1860
- // @ts-ignore
1861
- const launchEditor = require("launch-editor");
1862
- launchEditor(fileName);
1863
- }
1979
+ // compress is placed last and uses unshift so that it will be the first middleware used
1980
+ if (this.options.compress && !isHTTP2) {
1981
+ const compression = require("compression");
1982
+
1983
+ middlewares.push({ name: "compression", middleware: compression() });
1984
+ }
1985
+
1986
+ if (typeof this.options.headers !== "undefined") {
1987
+ middlewares.push({
1988
+ name: "set-headers",
1989
+ middleware: this.setHeaders.bind(this),
1990
+ });
1991
+ }
1864
1992
 
1865
- res.end();
1993
+ middlewares.push({
1994
+ name: "webpack-dev-middleware",
1995
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
1866
1996
  });
1867
1997
 
1868
- /** @type {import("express").Application} */
1869
- (app).get("/webpack-dev-server", (req, res) => {
1870
- /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
1871
- (middleware).waitUntilValid((stats) => {
1872
- res.setHeader("Content-Type", "text/html");
1873
- // HEAD requests should not return body content
1998
+ // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1999
+ middlewares.push({
2000
+ name: "webpack-dev-server-sockjs-bundle",
2001
+ path: "/__webpack_dev_server__/sockjs.bundle.js",
2002
+ /**
2003
+ * @param {Request} req
2004
+ * @param {Response} res
2005
+ * @param {NextFunction} next
2006
+ * @returns {void}
2007
+ */
2008
+ middleware: (req, res, next) => {
2009
+ if (req.method !== "GET" && req.method !== "HEAD") {
2010
+ next();
2011
+ return;
2012
+ }
2013
+
2014
+ const clientPath = path.join(
2015
+ __dirname,
2016
+ "..",
2017
+ "client/modules/sockjs-client/index.js",
2018
+ );
2019
+
2020
+ // Express send Etag and other headers by default, so let's keep them for compatibility reasons
2021
+ if (typeof res.sendFile === "function") {
2022
+ res.sendFile(clientPath);
2023
+ return;
2024
+ }
2025
+
2026
+ let stats;
2027
+
2028
+ try {
2029
+ // TODO implement `inputFileSystem.createReadStream` in webpack
2030
+ stats = fs.statSync(clientPath);
2031
+ } catch (err) {
2032
+ next();
2033
+ return;
2034
+ }
2035
+
2036
+ res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
2037
+ res.setHeader("Content-Length", stats.size);
2038
+
1874
2039
  if (req.method === "HEAD") {
1875
2040
  res.end();
1876
2041
  return;
1877
2042
  }
1878
- res.write(
1879
- '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
1880
- );
1881
2043
 
1882
- const statsForPrint =
1883
- typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
1884
- ? /** @type {MultiStats} */ (stats).toJson().children
1885
- : [/** @type {Stats} */ (stats).toJson()];
2044
+ fs.createReadStream(clientPath).pipe(res);
2045
+ },
2046
+ });
1886
2047
 
1887
- res.write(`<h1>Assets Report:</h1>`);
2048
+ middlewares.push({
2049
+ name: "webpack-dev-server-invalidate",
2050
+ path: "/webpack-dev-server/invalidate",
2051
+ /**
2052
+ * @param {Request} req
2053
+ * @param {Response} res
2054
+ * @param {NextFunction} next
2055
+ * @returns {void}
2056
+ */
2057
+ middleware: (req, res, next) => {
2058
+ if (req.method !== "GET" && req.method !== "HEAD") {
2059
+ next();
2060
+ return;
2061
+ }
1888
2062
 
1889
- /**
1890
- * @type {StatsCompilation[]}
1891
- */
1892
- (statsForPrint).forEach((item, index) => {
1893
- res.write("<div>");
2063
+ this.invalidate();
1894
2064
 
1895
- const name =
1896
- // eslint-disable-next-line no-nested-ternary
1897
- typeof item.name !== "undefined"
1898
- ? item.name
1899
- : /** @type {MultiStats} */ (stats).stats
1900
- ? `unnamed[${index}]`
1901
- : "unnamed";
2065
+ res.end();
2066
+ },
2067
+ });
1902
2068
 
1903
- res.write(`<h2>Compilation: ${name}</h2>`);
1904
- res.write("<ul>");
2069
+ middlewares.push({
2070
+ name: "webpack-dev-server-open-editor",
2071
+ path: "/webpack-dev-server/open-editor",
2072
+ /**
2073
+ * @param {Request} req
2074
+ * @param {Response} res
2075
+ * @param {NextFunction} next
2076
+ * @returns {void}
2077
+ */
2078
+ middleware: (req, res, next) => {
2079
+ if (req.method !== "GET" && req.method !== "HEAD") {
2080
+ next();
2081
+ return;
2082
+ }
1905
2083
 
1906
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2084
+ if (!req.url) {
2085
+ next();
2086
+ return;
2087
+ }
1907
2088
 
1908
- for (const asset of /** @type {NonNullable<StatsCompilation["assets"]>} */ (
1909
- item.assets
1910
- )) {
1911
- const assetName = asset.name;
1912
- const assetURL = `${publicPath}${assetName}`;
2089
+ const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
2090
+ const params = new URLSearchParams(resolveUrl.search);
2091
+ const fileName = params.get("fileName");
1913
2092
 
1914
- res.write(
1915
- `<li>
1916
- <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1917
- </li>`,
1918
- );
1919
- }
2093
+ if (typeof fileName === "string") {
2094
+ // @ts-ignore
2095
+ const launchEditor = require("launch-editor");
1920
2096
 
1921
- res.write("</ul>");
1922
- res.write("</div>");
1923
- });
2097
+ launchEditor(fileName);
2098
+ }
1924
2099
 
1925
- res.end("</body></html>");
1926
- });
2100
+ res.end();
2101
+ },
1927
2102
  });
1928
- }
1929
2103
 
1930
- /**
1931
- * @private
1932
- * @returns {void}
1933
- */
1934
- setupWatchStaticFiles() {
1935
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
1936
- /** @type {NormalizedStatic[]} */
1937
- (this.options.static).forEach((staticOption) => {
1938
- if (staticOption.watch) {
1939
- this.watchFiles(staticOption.directory, staticOption.watch);
2104
+ middlewares.push({
2105
+ name: "webpack-dev-server-assets",
2106
+ path: "/webpack-dev-server",
2107
+ /**
2108
+ * @param {Request} req
2109
+ * @param {Response} res
2110
+ * @param {NextFunction} next
2111
+ * @returns {void}
2112
+ */
2113
+ middleware: (req, res, next) => {
2114
+ if (req.method !== "GET" && req.method !== "HEAD") {
2115
+ next();
2116
+ return;
1940
2117
  }
1941
- });
1942
- }
1943
- }
1944
2118
 
1945
- /**
1946
- * @private
1947
- * @returns {void}
1948
- */
1949
- setupWatchFiles() {
1950
- const { watchFiles } = this.options;
2119
+ if (!this.middleware) {
2120
+ next();
2121
+ return;
2122
+ }
1951
2123
 
1952
- if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
1953
- /** @type {WatchFiles[]} */
1954
- (watchFiles).forEach((item) => {
1955
- this.watchFiles(item.paths, item.options);
1956
- });
1957
- }
1958
- }
2124
+ this.middleware.waitUntilValid((stats) => {
2125
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1959
2126
 
1960
- /**
1961
- * @private
1962
- * @returns {void}
1963
- */
1964
- setupMiddlewares() {
1965
- /**
1966
- * @type {Array<Middleware>}
1967
- */
1968
- let middlewares = [];
2127
+ // HEAD requests should not return body content
2128
+ if (req.method === "HEAD") {
2129
+ res.end();
2130
+ return;
2131
+ }
1969
2132
 
1970
- // compress is placed last and uses unshift so that it will be the first middleware used
1971
- if (this.options.compress) {
1972
- const compression = require("compression");
2133
+ res.write(
2134
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
2135
+ );
1973
2136
 
1974
- middlewares.push({ name: "compression", middleware: compression() });
1975
- }
2137
+ /**
2138
+ * @type {StatsCompilation[]}
2139
+ */
2140
+ const statsForPrint =
2141
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2142
+ ? /** @type {NonNullable<StatsCompilation["children"]>} */
2143
+ (/** @type {MultiStats} */ (stats).toJson().children)
2144
+ : [/** @type {Stats} */ (stats).toJson()];
2145
+
2146
+ res.write(`<h1>Assets Report:</h1>`);
2147
+
2148
+ for (const [index, item] of statsForPrint.entries()) {
2149
+ res.write("<div>");
2150
+
2151
+ const name =
2152
+ // eslint-disable-next-line no-nested-ternary
2153
+ typeof item.name !== "undefined"
2154
+ ? item.name
2155
+ : /** @type {MultiStats} */ (stats).stats
2156
+ ? `unnamed[${index}]`
2157
+ : "unnamed";
2158
+
2159
+ res.write(`<h2>Compilation: ${name}</h2>`);
2160
+ res.write("<ul>");
2161
+
2162
+ const publicPath =
2163
+ item.publicPath === "auto" ? "" : item.publicPath;
2164
+ const assets =
2165
+ /** @type {NonNullable<StatsCompilation["assets"]>} */
2166
+ (item.assets);
2167
+
2168
+ for (const asset of assets) {
2169
+ const assetName = asset.name;
2170
+ const assetURL = `${publicPath}${assetName}`;
2171
+
2172
+ res.write(
2173
+ `<li>
2174
+ <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
2175
+ </li>`,
2176
+ );
2177
+ }
1976
2178
 
1977
- if (typeof this.options.headers !== "undefined") {
1978
- middlewares.push({
1979
- name: "set-headers",
1980
- path: "*",
1981
- middleware: this.setHeaders.bind(this),
1982
- });
1983
- }
2179
+ res.write("</ul>");
2180
+ res.write("</div>");
2181
+ }
1984
2182
 
1985
- middlewares.push({
1986
- name: "webpack-dev-middleware",
1987
- middleware:
1988
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
1989
- (this.middleware),
2183
+ res.end("</body></html>");
2184
+ });
2185
+ },
1990
2186
  });
1991
2187
 
1992
2188
  if (this.options.proxy) {
@@ -2106,8 +2302,8 @@ class Server {
2106
2302
 
2107
2303
  if (typeof bypassUrl === "boolean") {
2108
2304
  // skip the proxy
2109
- // @ts-ignore
2110
- req.url = null;
2305
+ res.statusCode = 404;
2306
+ req.url = "";
2111
2307
  next();
2112
2308
  } else if (typeof bypassUrl === "string") {
2113
2309
  // byPass to that url
@@ -2124,6 +2320,7 @@ class Server {
2124
2320
  name: "http-proxy-middleware",
2125
2321
  middleware: handler,
2126
2322
  });
2323
+
2127
2324
  // Also forward error requests to the proxy so it can handle them.
2128
2325
  middlewares.push({
2129
2326
  name: "http-proxy-middleware-error-handler",
@@ -2141,16 +2338,17 @@ class Server {
2141
2338
 
2142
2339
  middlewares.push({
2143
2340
  name: "webpack-dev-middleware",
2144
- middleware:
2145
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2146
- (this.middleware),
2341
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2147
2342
  });
2148
2343
  }
2149
2344
 
2150
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2345
+ const staticOptions =
2151
2346
  /** @type {NormalizedStatic[]} */
2152
- (this.options.static).forEach((staticOption) => {
2153
- staticOption.publicPath.forEach((publicPath) => {
2347
+ (this.options.static);
2348
+
2349
+ if (staticOptions.length > 0) {
2350
+ for (const staticOption of staticOptions) {
2351
+ for (const publicPath of staticOption.publicPath) {
2154
2352
  middlewares.push({
2155
2353
  name: "express-static",
2156
2354
  path: publicPath,
@@ -2159,8 +2357,8 @@ class Server {
2159
2357
  staticOption.staticOptions,
2160
2358
  ),
2161
2359
  });
2162
- });
2163
- });
2360
+ }
2361
+ }
2164
2362
  }
2165
2363
 
2166
2364
  if (this.options.historyApiFallback) {
@@ -2197,15 +2395,12 @@ class Server {
2197
2395
  // it is able to handle '/index.html' request after redirect
2198
2396
  middlewares.push({
2199
2397
  name: "webpack-dev-middleware",
2200
- middleware:
2201
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2202
- (this.middleware),
2398
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2203
2399
  });
2204
2400
 
2205
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2206
- /** @type {NormalizedStatic[]} */
2207
- (this.options.static).forEach((staticOption) => {
2208
- staticOption.publicPath.forEach((publicPath) => {
2401
+ if (staticOptions.length > 0) {
2402
+ for (const staticOption of staticOptions) {
2403
+ for (const publicPath of staticOption.publicPath) {
2209
2404
  middlewares.push({
2210
2405
  name: "express-static",
2211
2406
  path: publicPath,
@@ -2214,17 +2409,16 @@ class Server {
2214
2409
  staticOption.staticOptions,
2215
2410
  ),
2216
2411
  });
2217
- });
2218
- });
2412
+ }
2413
+ }
2219
2414
  }
2220
2415
  }
2221
2416
 
2222
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2417
+ if (staticOptions.length > 0) {
2223
2418
  const serveIndex = require("serve-index");
2224
2419
 
2225
- /** @type {NormalizedStatic[]} */
2226
- (this.options.static).forEach((staticOption) => {
2227
- staticOption.publicPath.forEach((publicPath) => {
2420
+ for (const staticOption of staticOptions) {
2421
+ for (const publicPath of staticOption.publicPath) {
2228
2422
  if (staticOption.serveIndex) {
2229
2423
  middlewares.push({
2230
2424
  name: "serve-index",
@@ -2249,15 +2443,14 @@ class Server {
2249
2443
  },
2250
2444
  });
2251
2445
  }
2252
- });
2253
- });
2446
+ }
2447
+ }
2254
2448
  }
2255
2449
 
2256
2450
  // Register this middleware always as the last one so that it's only used as a
2257
2451
  // fallback when no other middleware responses.
2258
2452
  middlewares.push({
2259
2453
  name: "options-middleware",
2260
- path: "*",
2261
2454
  /**
2262
2455
  * @param {Request} req
2263
2456
  * @param {Response} res
@@ -2279,37 +2472,93 @@ class Server {
2279
2472
  middlewares = this.options.setupMiddlewares(middlewares, this);
2280
2473
  }
2281
2474
 
2282
- middlewares.forEach((middleware) => {
2475
+ // Lazy init webpack dev middleware
2476
+ const lazyInitDevMiddleware = () => {
2477
+ if (!this.middleware) {
2478
+ const webpackDevMiddleware = require("webpack-dev-middleware");
2479
+
2480
+ // middleware for serving webpack bundle
2481
+ /** @type {import("webpack-dev-middleware").API<Request, Response>} */
2482
+ this.middleware = webpackDevMiddleware(
2483
+ this.compiler,
2484
+ this.options.devMiddleware,
2485
+ );
2486
+ }
2487
+
2488
+ return this.middleware;
2489
+ };
2490
+
2491
+ for (const i of middlewares) {
2492
+ if (i.name === "webpack-dev-middleware") {
2493
+ const item = /** @type {MiddlewareObject} */ (i);
2494
+
2495
+ if (typeof item.middleware === "undefined") {
2496
+ item.middleware = lazyInitDevMiddleware();
2497
+ }
2498
+ }
2499
+ }
2500
+
2501
+ for (const middleware of middlewares) {
2283
2502
  if (typeof middleware === "function") {
2284
- /** @type {import("express").Application} */
2285
- (this.app).use(middleware);
2503
+ /** @type {A} */
2504
+ (this.app).use(
2505
+ /** @type {NextHandleFunction | HandleFunction} */
2506
+ (middleware),
2507
+ );
2286
2508
  } else if (typeof middleware.path !== "undefined") {
2287
- /** @type {import("express").Application} */
2288
- (this.app).use(middleware.path, middleware.middleware);
2509
+ /** @type {A} */
2510
+ (this.app).use(
2511
+ middleware.path,
2512
+ /** @type {SimpleHandleFunction | NextHandleFunction} */
2513
+ (middleware.middleware),
2514
+ );
2289
2515
  } else {
2290
- /** @type {import("express").Application} */
2291
- (this.app).use(middleware.middleware);
2516
+ /** @type {A} */
2517
+ (this.app).use(
2518
+ /** @type {NextHandleFunction | HandleFunction} */
2519
+ (middleware.middleware),
2520
+ );
2292
2521
  }
2293
- });
2522
+ }
2294
2523
  }
2295
2524
 
2296
2525
  /**
2297
2526
  * @private
2298
- * @returns {void}
2527
+ * @returns {Promise<void>}
2299
2528
  */
2300
- createServer() {
2301
- const { type, options } = /** @type {ServerConfiguration} */ (
2302
- this.options.server
2303
- );
2529
+ async createServer() {
2530
+ const { type, options } =
2531
+ /** @type {ServerConfiguration<A, S>} */
2532
+ (this.options.server);
2533
+
2534
+ if (typeof type === "function") {
2535
+ /** @type {S | undefined}*/
2536
+ this.server = await type(
2537
+ /** @type {ServerOptions} */
2538
+ (options),
2539
+ /** @type {A} */
2540
+ (this.app),
2541
+ );
2542
+ } else {
2543
+ // eslint-disable-next-line import/no-dynamic-require
2544
+ const serverType = require(/** @type {string} */ (type));
2304
2545
 
2305
- /** @type {import("http").Server | undefined | null} */
2306
- // eslint-disable-next-line import/no-dynamic-require
2307
- this.server = require(/** @type {string} */ (type)).createServer(
2308
- options,
2309
- this.app,
2310
- );
2546
+ /** @type {S | undefined}*/
2547
+ this.server =
2548
+ type === "http2"
2549
+ ? serverType.createSecureServer(
2550
+ { ...options, allowHTTP1: true },
2551
+ this.app,
2552
+ )
2553
+ : serverType.createServer(options, this.app);
2554
+ }
2555
+
2556
+ this.isTlsServer =
2557
+ typeof (
2558
+ /** @type {import("tls").Server} */ (this.server).setSecureContext
2559
+ ) !== "undefined";
2311
2560
 
2312
- /** @type {import("http").Server} */
2561
+ /** @type {S} */
2313
2562
  (this.server).on(
2314
2563
  "connection",
2315
2564
  /**
@@ -2326,7 +2575,7 @@ class Server {
2326
2575
  },
2327
2576
  );
2328
2577
 
2329
- /** @type {import("http").Server} */
2578
+ /** @type {S} */
2330
2579
  (this.server).on(
2331
2580
  "error",
2332
2581
  /**
@@ -2344,9 +2593,8 @@ class Server {
2344
2593
  */
2345
2594
  createWebSocketServer() {
2346
2595
  /** @type {WebSocketServerImplementation | undefined | null} */
2347
- this.webSocketServer = new /** @type {any} */ (this.getServerTransport())(
2348
- this,
2349
- );
2596
+ this.webSocketServer = new (this.getServerTransport())(this);
2597
+
2350
2598
  /** @type {WebSocketServerImplementation} */
2351
2599
  (this.webSocketServer).implementation.on(
2352
2600
  "connection",
@@ -2513,22 +2761,19 @@ class Server {
2513
2761
  */
2514
2762
  runBonjour() {
2515
2763
  const { Bonjour } = require("bonjour-service");
2764
+ const type = this.isTlsServer ? "https" : "http";
2765
+
2516
2766
  /**
2517
2767
  * @private
2518
2768
  * @type {Bonjour | undefined}
2519
2769
  */
2520
2770
  this.bonjour = new Bonjour();
2521
2771
  this.bonjour.publish({
2522
- // @ts-expect-error
2523
2772
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
2524
- // @ts-expect-error
2525
2773
  port: /** @type {number} */ (this.options.port),
2526
- // @ts-expect-error
2527
- type:
2528
- /** @type {ServerConfiguration} */
2529
- (this.options.server).type === "http" ? "http" : "https",
2774
+ type,
2530
2775
  subtypes: ["webpack"],
2531
- .../** @type {BonjourOptions} */ (this.options.bonjour),
2776
+ .../** @type {Partial<BonjourOptions>} */ (this.options.bonjour),
2532
2777
  });
2533
2778
  }
2534
2779
 
@@ -2608,23 +2853,15 @@ class Server {
2608
2853
  };
2609
2854
  const useColor = getColorsOption(this.getCompilerOptions());
2610
2855
 
2856
+ const server = /** @type {S} */ (this.server);
2857
+
2611
2858
  if (this.options.ipc) {
2612
- this.logger.info(
2613
- `Project is running at: "${
2614
- /** @type {import("http").Server} */
2615
- (this.server).address()
2616
- }"`,
2617
- );
2859
+ this.logger.info(`Project is running at: "${server.address()}"`);
2618
2860
  } else {
2619
- const protocol =
2620
- /** @type {ServerConfiguration} */
2621
- (this.options.server).type === "http" ? "http" : "https";
2861
+ const protocol = this.isTlsServer ? "https" : "http";
2622
2862
  const { address, port } =
2623
2863
  /** @type {import("net").AddressInfo} */
2624
- (
2625
- /** @type {import("http").Server} */
2626
- (this.server).address()
2627
- );
2864
+ (server.address());
2628
2865
  /**
2629
2866
  * @param {string} newHostname
2630
2867
  * @returns {string}
@@ -2632,7 +2869,7 @@ class Server {
2632
2869
  const prettyPrintURL = (newHostname) =>
2633
2870
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
2634
2871
 
2635
- let server;
2872
+ let host;
2636
2873
  let localhost;
2637
2874
  let loopbackIPv4;
2638
2875
  let loopbackIPv6;
@@ -2652,7 +2889,7 @@ class Server {
2652
2889
  }
2653
2890
 
2654
2891
  if (!isIP) {
2655
- server = prettyPrintURL(this.options.host);
2892
+ host = prettyPrintURL(this.options.host);
2656
2893
  }
2657
2894
  }
2658
2895
  }
@@ -2661,14 +2898,15 @@ class Server {
2661
2898
 
2662
2899
  if (parsedIP.range() === "unspecified") {
2663
2900
  localhost = prettyPrintURL("localhost");
2901
+ loopbackIPv6 = prettyPrintURL("::1");
2664
2902
 
2665
- const networkIPv4 = await Server.internalIP("v4");
2903
+ const networkIPv4 = Server.findIp("v4", false);
2666
2904
 
2667
2905
  if (networkIPv4) {
2668
2906
  networkUrlIPv4 = prettyPrintURL(networkIPv4);
2669
2907
  }
2670
2908
 
2671
- const networkIPv6 = await Server.internalIP("v6");
2909
+ const networkIPv6 = Server.findIp("v6", false);
2672
2910
 
2673
2911
  if (networkIPv6) {
2674
2912
  networkUrlIPv6 = prettyPrintURL(networkIPv6);
@@ -2697,8 +2935,8 @@ class Server {
2697
2935
 
2698
2936
  this.logger.info("Project is running at:");
2699
2937
 
2700
- if (server) {
2701
- this.logger.info(`Server: ${colors.info(useColor, server)}`);
2938
+ if (host) {
2939
+ this.logger.info(`Server: ${colors.info(useColor, host)}`);
2702
2940
  }
2703
2941
 
2704
2942
  if (localhost || loopbackIPv4 || loopbackIPv6) {
@@ -2770,11 +3008,7 @@ class Server {
2770
3008
  if (this.options.bonjour) {
2771
3009
  const bonjourProtocol =
2772
3010
  /** @type {BonjourOptions} */
2773
- (this.options.bonjour).type ||
2774
- /** @type {ServerConfiguration} */
2775
- (this.options.server).type === "http"
2776
- ? "http"
2777
- : "https";
3011
+ (this.options.bonjour).type || this.isTlsServer ? "https" : "http";
2778
3012
 
2779
3013
  this.logger.info(
2780
3014
  `Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`,
@@ -2796,8 +3030,8 @@ class Server {
2796
3030
  headers = headers(
2797
3031
  req,
2798
3032
  res,
2799
- /** @type {import("webpack-dev-middleware").API<IncomingMessage, ServerResponse>}*/
2800
- (this.middleware).context,
3033
+ // eslint-disable-next-line no-undefined
3034
+ this.middleware ? this.middleware.context : undefined,
2801
3035
  );
2802
3036
  }
2803
3037
 
@@ -2806,6 +3040,8 @@ class Server {
2806
3040
  */
2807
3041
  const allHeaders = [];
2808
3042
 
3043
+ allHeaders.push({ key: "X_TEST", value: "TEST" });
3044
+
2809
3045
  if (!Array.isArray(headers)) {
2810
3046
  // eslint-disable-next-line guard-for-in
2811
3047
  for (const name in headers) {
@@ -2816,14 +3052,9 @@ class Server {
2816
3052
  headers = allHeaders;
2817
3053
  }
2818
3054
 
2819
- headers.forEach(
2820
- /**
2821
- * @param {{key: string, value: any}} header
2822
- */
2823
- (header) => {
2824
- res.setHeader(header.key, header.value);
2825
- },
2826
- );
3055
+ for (const { key, value } of headers) {
3056
+ res.setHeader(key, value);
3057
+ }
2827
3058
  }
2828
3059
 
2829
3060
  next();
@@ -3097,7 +3328,7 @@ class Server {
3097
3328
 
3098
3329
  await /** @type {Promise<void>} */ (
3099
3330
  new Promise((resolve) => {
3100
- /** @type {import("http").Server} */
3331
+ /** @type {S} */
3101
3332
  (this.server).listen(listenOptions, () => {
3102
3333
  resolve();
3103
3334
  });
@@ -3183,10 +3414,10 @@ class Server {
3183
3414
  if (this.server) {
3184
3415
  await /** @type {Promise<void>} */ (
3185
3416
  new Promise((resolve) => {
3186
- /** @type {import("http").Server} */
3417
+ /** @type {S} */
3187
3418
  (this.server).close(() => {
3188
- this.server = null;
3189
-
3419
+ // eslint-disable-next-line no-undefined
3420
+ this.server = undefined;
3190
3421
  resolve();
3191
3422
  });
3192
3423
 
@@ -3205,7 +3436,6 @@ class Server {
3205
3436
  (this.middleware).close((error) => {
3206
3437
  if (error) {
3207
3438
  reject(error);
3208
-
3209
3439
  return;
3210
3440
  }
3211
3441
 
@@ -3214,7 +3444,8 @@ class Server {
3214
3444
  })
3215
3445
  );
3216
3446
 
3217
- this.middleware = null;
3447
+ // eslint-disable-next-line no-undefined
3448
+ this.middleware = undefined;
3218
3449
  }
3219
3450
  }
3220
3451