webpack-dev-server 5.0.4 → 5.2.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,60 @@ 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
+ // eslint-disable-next-line no-shadow
408
+ .flatMap((networks) => networks ?? [])
409
+ .filter((network) => {
410
+ if (!network || !network.address) {
411
+ return false;
412
+ }
413
+
414
+ if (network.family !== `IP${gatewayOrFamily}`) {
415
+ return false;
416
+ }
417
+
418
+ if (
419
+ typeof isInternal !== "undefined" &&
420
+ network.internal !== isInternal
421
+ ) {
422
+ return false;
423
+ }
424
+
425
+ if (gatewayOrFamily === "v6") {
426
+ const range = ipaddr.parse(network.address).range();
427
+
428
+ if (
429
+ range !== "ipv4Mapped" &&
430
+ range !== "uniqueLocal" &&
431
+ range !== "loopback"
432
+ ) {
433
+ return false;
434
+ }
435
+ }
436
+
437
+ return network.address;
438
+ });
439
+
440
+ for (const network of networks) {
441
+ host = network.address;
442
+
443
+ if (host.includes(":")) {
444
+ host = `[${host}]`;
445
+ }
446
+ }
447
+
448
+ return host;
449
+ }
450
+
451
+ const gatewayIp = ipaddr.parse(gatewayOrFamily);
332
452
 
333
453
  // Look for the matching interface in all local interfaces.
334
454
  for (const addresses of Object.values(os.networkInterfaces())) {
@@ -348,32 +468,22 @@ class Server {
348
468
  }
349
469
  }
350
470
 
471
+ // TODO remove me in the next major release, we have `findIp`
351
472
  /**
352
473
  * @param {"v4" | "v6"} family
353
474
  * @returns {Promise<string | undefined>}
354
475
  */
355
476
  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
- }
477
+ return Server.findIp(family, false);
363
478
  }
364
479
 
480
+ // TODO remove me in the next major release, we have `findIp`
365
481
  /**
366
482
  * @param {"v4" | "v6"} family
367
483
  * @returns {string | undefined}
368
484
  */
369
485
  static internalIPSync(family) {
370
- try {
371
- const { gateway } = require("default-gateway")[family].sync();
372
-
373
- return Server.findIp(gateway);
374
- } catch {
375
- // ignore
376
- }
486
+ return Server.findIp(family, false);
377
487
  }
378
488
 
379
489
  /**
@@ -383,14 +493,12 @@ class Server {
383
493
  static async getHostname(hostname) {
384
494
  if (hostname === "local-ip") {
385
495
  return (
386
- (await Server.internalIP("v4")) ||
387
- (await Server.internalIP("v6")) ||
388
- "0.0.0.0"
496
+ Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0"
389
497
  );
390
498
  } else if (hostname === "local-ipv4") {
391
- return (await Server.internalIP("v4")) || "0.0.0.0";
499
+ return Server.findIp("v4", false) || "0.0.0.0";
392
500
  } else if (hostname === "local-ipv6") {
393
- return (await Server.internalIP("v6")) || "::";
501
+ return Server.findIp("v6", false) || "::";
394
502
  }
395
503
 
396
504
  return hostname;
@@ -470,7 +578,11 @@ class Server {
470
578
  * @returns bool
471
579
  */
472
580
  static isWebTarget(compiler) {
473
- // TODO improve for the next major version - we should store `web` and other targets in `compiler.options.environment`
581
+ if (compiler.platform && compiler.platform.web) {
582
+ return compiler.platform.web;
583
+ }
584
+
585
+ // TODO improve for the next major version and keep only `webTargets` to fallback for old versions
474
586
  if (
475
587
  compiler.options.externalsPresets &&
476
588
  compiler.options.externalsPresets.web
@@ -490,6 +602,7 @@ class Server {
490
602
  "webworker",
491
603
  "electron-preload",
492
604
  "electron-renderer",
605
+ "nwjs",
493
606
  "node-webkit",
494
607
  // eslint-disable-next-line no-undefined
495
608
  undefined,
@@ -537,9 +650,7 @@ class Server {
537
650
  if (typeof webSocketURL.protocol !== "undefined") {
538
651
  protocol = webSocketURL.protocol;
539
652
  } else {
540
- protocol =
541
- /** @type {ServerConfiguration} */
542
- (this.options.server).type === "http" ? "ws:" : "wss:";
653
+ protocol = this.isTlsServer ? "wss:" : "ws:";
543
654
  }
544
655
 
545
656
  searchParams.set("protocol", protocol);
@@ -683,15 +794,12 @@ class Server {
683
794
  webSocketURLStr = searchParams.toString();
684
795
  }
685
796
 
686
- additionalEntries.push(
687
- `${require.resolve("../client/index.js")}?${webSocketURLStr}`,
688
- );
797
+ additionalEntries.push(`${this.getClientEntry()}?${webSocketURLStr}`);
689
798
  }
690
799
 
691
- if (this.options.hot === "only") {
692
- additionalEntries.push(require.resolve("webpack/hot/only-dev-server"));
693
- } else if (this.options.hot) {
694
- additionalEntries.push(require.resolve("webpack/hot/dev-server"));
800
+ const clientHotEntry = this.getClientHotEntry();
801
+ if (clientHotEntry) {
802
+ additionalEntries.push(clientHotEntry);
695
803
  }
696
804
 
697
805
  const webpack = compiler.webpack || require("webpack");
@@ -1007,39 +1115,41 @@ class Server {
1007
1115
  ? options.hot
1008
1116
  : true;
1009
1117
 
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
- };
1118
+ if (
1119
+ typeof options.server === "function" ||
1120
+ typeof options.server === "string"
1121
+ ) {
1122
+ options.server = {
1123
+ type: options.server,
1124
+ options: {},
1125
+ };
1126
+ } else {
1127
+ const serverOptions =
1128
+ /** @type {ServerConfiguration<A, S>} */
1129
+ (options.server || {});
1130
+
1131
+ options.server = {
1132
+ type: serverOptions.type || "http",
1133
+ options: { ...serverOptions.options },
1134
+ };
1135
+ }
1136
+
1137
+ const serverOptions = /** @type {ServerOptions} */ (options.server.options);
1023
1138
 
1024
1139
  if (
1025
1140
  options.server.type === "spdy" &&
1026
- typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
1027
- "undefined"
1141
+ typeof serverOptions.spdy === "undefined"
1028
1142
  ) {
1029
- /** @type {ServerOptions} */
1030
- (options.server.options).spdy = {
1031
- protocols: ["h2", "http/1.1"],
1032
- };
1143
+ serverOptions.spdy = { protocols: ["h2", "http/1.1"] };
1033
1144
  }
1034
1145
 
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;
1146
+ if (
1147
+ options.server.type === "https" ||
1148
+ options.server.type === "http2" ||
1149
+ options.server.type === "spdy"
1150
+ ) {
1151
+ if (typeof serverOptions.requestCert === "undefined") {
1152
+ serverOptions.requestCert = false;
1043
1153
  }
1044
1154
 
1045
1155
  const httpsProperties =
@@ -1047,19 +1157,13 @@ class Server {
1047
1157
  (["ca", "cert", "crl", "key", "pfx"]);
1048
1158
 
1049
1159
  for (const property of httpsProperties) {
1050
- if (
1051
- typeof (
1052
- /** @type {ServerOptions} */ (options.server.options)[property]
1053
- ) === "undefined"
1054
- ) {
1160
+ if (typeof serverOptions[property] === "undefined") {
1055
1161
  // eslint-disable-next-line no-continue
1056
1162
  continue;
1057
1163
  }
1058
1164
 
1059
1165
  /** @type {any} */
1060
- const value =
1061
- /** @type {ServerOptions} */
1062
- (options.server.options)[property];
1166
+ const value = serverOptions[property];
1063
1167
  /**
1064
1168
  * @param {string | Buffer | undefined} item
1065
1169
  * @returns {string | Buffer | undefined}
@@ -1087,17 +1191,14 @@ class Server {
1087
1191
  };
1088
1192
 
1089
1193
  /** @type {any} */
1090
- (options.server.options)[property] = Array.isArray(value)
1194
+ (serverOptions)[property] = Array.isArray(value)
1091
1195
  ? value.map((item) => readFile(item))
1092
1196
  : readFile(value);
1093
1197
  }
1094
1198
 
1095
1199
  let fakeCert;
1096
1200
 
1097
- if (
1098
- !(/** @type {ServerOptions} */ (options.server.options).key) ||
1099
- !(/** @type {ServerOptions} */ (options.server.options).cert)
1100
- ) {
1201
+ if (!serverOptions.key || !serverOptions.cert) {
1101
1202
  const certificateDir = Server.findCacheDir();
1102
1203
  const certificatePath = path.join(certificateDir, "server.pem");
1103
1204
  let certificateExists;
@@ -1116,13 +1217,11 @@ class Server {
1116
1217
 
1117
1218
  // cert is more than 30 days old, kill it with fire
1118
1219
  if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
1119
- const { rimraf } = require("rimraf");
1120
-
1121
1220
  this.logger.info(
1122
1221
  "SSL certificate is more than 30 days old. Removing...",
1123
1222
  );
1124
1223
 
1125
- await rimraf(certificatePath);
1224
+ await fs.promises.rm(certificatePath, { recursive: true });
1126
1225
 
1127
1226
  certificateExists = false;
1128
1227
  }
@@ -1131,7 +1230,6 @@ class Server {
1131
1230
  if (!certificateExists) {
1132
1231
  this.logger.info("Generating SSL certificate...");
1133
1232
 
1134
- // @ts-ignore
1135
1233
  const selfsigned = require("selfsigned");
1136
1234
  const attributes = [{ name: "commonName", value: "localhost" }];
1137
1235
  const pems = selfsigned.generate(attributes, {
@@ -1212,14 +1310,8 @@ class Server {
1212
1310
  this.logger.info(`SSL certificate: ${certificatePath}`);
1213
1311
  }
1214
1312
 
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;
1313
+ serverOptions.key = serverOptions.key || fakeCert;
1314
+ serverOptions.cert = serverOptions.cert || fakeCert;
1223
1315
  }
1224
1316
 
1225
1317
  if (typeof options.ipc === "boolean") {
@@ -1281,15 +1373,15 @@ class Server {
1281
1373
  */
1282
1374
  const result = [];
1283
1375
 
1284
- options.open.forEach((item) => {
1376
+ for (const item of options.open) {
1285
1377
  if (typeof item === "string") {
1286
1378
  result.push({ target: item, options: defaultOpenOptions });
1287
-
1288
- return;
1379
+ // eslint-disable-next-line no-continue
1380
+ continue;
1289
1381
  }
1290
1382
 
1291
1383
  result.push(...getOpenItemsFromObject(item));
1292
- });
1384
+ }
1293
1385
 
1294
1386
  /** @type {NormalizedOpen[]} */
1295
1387
  (options.open) = result;
@@ -1522,8 +1614,9 @@ class Server {
1522
1614
  }
1523
1615
 
1524
1616
  /**
1617
+ * @template T
1525
1618
  * @private
1526
- * @returns {string}
1619
+ * @returns {T}
1527
1620
  */
1528
1621
  getServerTransport() {
1529
1622
  let implementation;
@@ -1553,9 +1646,8 @@ class Server {
1553
1646
  try {
1554
1647
  // eslint-disable-next-line import/no-dynamic-require
1555
1648
  implementation = require(
1556
- /** @type {WebSocketServerConfiguration} */ (
1557
- this.options.webSocketServer
1558
- ).type,
1649
+ /** @type {WebSocketServerConfiguration} */
1650
+ (this.options.webSocketServer).type,
1559
1651
  );
1560
1652
  } catch (error) {
1561
1653
  implementationFound = false;
@@ -1563,9 +1655,9 @@ class Server {
1563
1655
  }
1564
1656
  break;
1565
1657
  case "function":
1566
- implementation = /** @type {WebSocketServerConfiguration} */ (
1567
- this.options.webSocketServer
1568
- ).type;
1658
+ implementation =
1659
+ /** @type {WebSocketServerConfiguration} */
1660
+ (this.options.webSocketServer).type;
1569
1661
  break;
1570
1662
  default:
1571
1663
  implementationFound = false;
@@ -1582,6 +1674,25 @@ class Server {
1582
1674
  return implementation;
1583
1675
  }
1584
1676
 
1677
+ /**
1678
+ * @returns {string}
1679
+ */
1680
+ // eslint-disable-next-line class-methods-use-this
1681
+ getClientEntry() {
1682
+ return require.resolve("../client/index.js");
1683
+ }
1684
+
1685
+ /**
1686
+ * @returns {string | void}
1687
+ */
1688
+ getClientHotEntry() {
1689
+ if (this.options.hot === "only") {
1690
+ return require.resolve("webpack/hot/only-dev-server");
1691
+ } else if (this.options.hot) {
1692
+ return require.resolve("webpack/hot/dev-server");
1693
+ }
1694
+ }
1695
+
1585
1696
  /**
1586
1697
  * @private
1587
1698
  * @returns {void}
@@ -1631,12 +1742,22 @@ class Server {
1631
1742
  * @returns {Promise<void>}
1632
1743
  */
1633
1744
  async initialize() {
1745
+ this.setupHooks();
1746
+
1747
+ await this.setupApp();
1748
+ await this.createServer();
1749
+
1634
1750
  if (this.options.webSocketServer) {
1635
1751
  const compilers =
1636
1752
  /** @type {MultiCompiler} */
1637
1753
  (this.compiler).compilers || [this.compiler];
1638
1754
 
1639
- compilers.forEach((compiler) => {
1755
+ for (const compiler of compilers) {
1756
+ if (compiler.options.devServer === false) {
1757
+ // eslint-disable-next-line no-continue
1758
+ continue;
1759
+ }
1760
+
1640
1761
  this.addAdditionalEntries(compiler);
1641
1762
 
1642
1763
  const webpack = compiler.webpack || require("webpack");
@@ -1661,7 +1782,7 @@ class Server {
1661
1782
  plugin.apply(compiler);
1662
1783
  }
1663
1784
  }
1664
- });
1785
+ }
1665
1786
 
1666
1787
  if (
1667
1788
  this.options.client &&
@@ -1671,16 +1792,9 @@ class Server {
1671
1792
  }
1672
1793
  }
1673
1794
 
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
1795
  this.setupWatchFiles();
1681
1796
  this.setupWatchStaticFiles();
1682
1797
  this.setupMiddlewares();
1683
- this.createServer();
1684
1798
 
1685
1799
  if (this.options.setupExitSignals) {
1686
1800
  const signals = ["SIGINT", "SIGTERM"];
@@ -1718,24 +1832,30 @@ class Server {
1718
1832
 
1719
1833
  // Proxy WebSocket without the initial http request
1720
1834
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1721
- /** @type {RequestHandler[]} */
1722
- (this.webSocketProxies).forEach((webSocketProxy) => {
1723
- /** @type {import("http").Server} */
1835
+ const webSocketProxies =
1836
+ /** @type {RequestHandler[]} */
1837
+ (this.webSocketProxies);
1838
+
1839
+ for (const webSocketProxy of webSocketProxies) {
1840
+ /** @type {S} */
1724
1841
  (this.server).on(
1725
1842
  "upgrade",
1726
1843
  /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
1727
1844
  (webSocketProxy).upgrade,
1728
1845
  );
1729
- }, this);
1846
+ }
1730
1847
  }
1731
1848
 
1732
1849
  /**
1733
1850
  * @private
1734
- * @returns {void}
1851
+ * @returns {Promise<void>}
1735
1852
  */
1736
- setupApp() {
1737
- /** @type {import("express").Application | undefined}*/
1738
- this.app = new /** @type {any} */ (getExpress())();
1853
+ async setupApp() {
1854
+ /** @type {A | undefined}*/
1855
+ this.app =
1856
+ typeof this.options.app === "function"
1857
+ ? await this.options.app()
1858
+ : getExpress()();
1739
1859
  }
1740
1860
 
1741
1861
  /**
@@ -1789,204 +1909,297 @@ class Server {
1789
1909
  * @private
1790
1910
  * @returns {void}
1791
1911
  */
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
- }
1912
+ setupWatchStaticFiles() {
1913
+ const watchFiles = /** @type {NormalizedStatic[]} */ (this.options.static);
1812
1914
 
1813
- res.send("Invalid Host header");
1814
- },
1815
- );
1915
+ if (watchFiles.length > 0) {
1916
+ for (const item of watchFiles) {
1917
+ if (item.watch) {
1918
+ this.watchFiles(item.directory, item.watch);
1919
+ }
1920
+ }
1921
+ }
1816
1922
  }
1817
1923
 
1818
1924
  /**
1819
1925
  * @private
1820
1926
  * @returns {void}
1821
1927
  */
1822
- setupDevMiddleware() {
1823
- const webpackDevMiddleware = require("webpack-dev-middleware");
1928
+ setupWatchFiles() {
1929
+ const watchFiles = /** @type {WatchFiles[]} */ (this.options.watchFiles);
1824
1930
 
1825
- // middleware for serving webpack bundle
1826
- this.middleware = webpackDevMiddleware(
1827
- this.compiler,
1828
- this.options.devMiddleware,
1829
- );
1931
+ if (watchFiles.length > 0) {
1932
+ for (const item of watchFiles) {
1933
+ this.watchFiles(item.paths, item.options);
1934
+ }
1935
+ }
1830
1936
  }
1831
1937
 
1832
1938
  /**
1833
1939
  * @private
1834
1940
  * @returns {void}
1835
1941
  */
1836
- setupBuiltInRoutes() {
1837
- const { app, middleware } = this;
1942
+ setupMiddlewares() {
1943
+ /**
1944
+ * @type {Array<Middleware>}
1945
+ */
1946
+ let middlewares = [];
1838
1947
 
1839
- /** @type {import("express").Application} */
1840
- (app).get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
1841
- res.setHeader("Content-Type", "application/javascript");
1948
+ // Register setup host header check for security
1949
+ middlewares.push({
1950
+ name: "host-header-check",
1951
+ /**
1952
+ * @param {Request} req
1953
+ * @param {Response} res
1954
+ * @param {NextFunction} next
1955
+ * @returns {void}
1956
+ */
1957
+ middleware: (req, res, next) => {
1958
+ const headers =
1959
+ /** @type {{ [key: string]: string | undefined }} */
1960
+ (req.headers);
1961
+ const headerName = headers[":authority"] ? ":authority" : "host";
1842
1962
 
1843
- const clientPath = path.join(__dirname, "..", "client");
1963
+ if (this.checkHeader(headers, headerName)) {
1964
+ next();
1965
+ return;
1966
+ }
1844
1967
 
1845
- res.sendFile(path.join(clientPath, "modules/sockjs-client/index.js"));
1968
+ res.statusCode = 403;
1969
+ res.end("Invalid Host header");
1970
+ },
1846
1971
  });
1847
1972
 
1848
- /** @type {import("express").Application} */
1849
- (app).get("/webpack-dev-server/invalidate", (_req, res) => {
1850
- this.invalidate();
1973
+ const isHTTP2 =
1974
+ /** @type {ServerConfiguration<A, S>} */ (this.options.server).type ===
1975
+ "http2";
1851
1976
 
1852
- res.end();
1853
- });
1977
+ if (isHTTP2) {
1978
+ // TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
1979
+ middlewares.push({
1980
+ name: "http2-status-message-patch",
1981
+ middleware:
1982
+ /** @type {NextHandleFunction} */
1983
+ (_req, res, next) => {
1984
+ Object.defineProperty(res, "statusMessage", {
1985
+ get() {
1986
+ return "";
1987
+ },
1988
+ set() {},
1989
+ });
1854
1990
 
1855
- /** @type {import("express").Application} */
1856
- (app).get("/webpack-dev-server/open-editor", (req, res) => {
1857
- const fileName = req.query.fileName;
1991
+ next();
1992
+ },
1993
+ });
1994
+ }
1858
1995
 
1859
- if (typeof fileName === "string") {
1860
- // @ts-ignore
1861
- const launchEditor = require("launch-editor");
1862
- launchEditor(fileName);
1863
- }
1996
+ // compress is placed last and uses unshift so that it will be the first middleware used
1997
+ if (this.options.compress && !isHTTP2) {
1998
+ const compression = require("compression");
1864
1999
 
1865
- res.end();
2000
+ middlewares.push({ name: "compression", middleware: compression() });
2001
+ }
2002
+
2003
+ if (typeof this.options.headers !== "undefined") {
2004
+ middlewares.push({
2005
+ name: "set-headers",
2006
+ middleware: this.setHeaders.bind(this),
2007
+ });
2008
+ }
2009
+
2010
+ middlewares.push({
2011
+ name: "webpack-dev-middleware",
2012
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
1866
2013
  });
1867
2014
 
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
2015
+ // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
2016
+ middlewares.push({
2017
+ name: "webpack-dev-server-sockjs-bundle",
2018
+ path: "/__webpack_dev_server__/sockjs.bundle.js",
2019
+ /**
2020
+ * @param {Request} req
2021
+ * @param {Response} res
2022
+ * @param {NextFunction} next
2023
+ * @returns {void}
2024
+ */
2025
+ middleware: (req, res, next) => {
2026
+ if (req.method !== "GET" && req.method !== "HEAD") {
2027
+ next();
2028
+ return;
2029
+ }
2030
+
2031
+ const clientPath = path.join(
2032
+ __dirname,
2033
+ "..",
2034
+ "client/modules/sockjs-client/index.js",
2035
+ );
2036
+
2037
+ // Express send Etag and other headers by default, so let's keep them for compatibility reasons
2038
+ if (typeof res.sendFile === "function") {
2039
+ res.sendFile(clientPath);
2040
+ return;
2041
+ }
2042
+
2043
+ let stats;
2044
+
2045
+ try {
2046
+ // TODO implement `inputFileSystem.createReadStream` in webpack
2047
+ stats = fs.statSync(clientPath);
2048
+ } catch (err) {
2049
+ next();
2050
+ return;
2051
+ }
2052
+
2053
+ res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
2054
+ res.setHeader("Content-Length", stats.size);
2055
+
1874
2056
  if (req.method === "HEAD") {
1875
2057
  res.end();
1876
2058
  return;
1877
2059
  }
1878
- res.write(
1879
- '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
1880
- );
1881
2060
 
1882
- const statsForPrint =
1883
- typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
1884
- ? /** @type {MultiStats} */ (stats).toJson().children
1885
- : [/** @type {Stats} */ (stats).toJson()];
2061
+ fs.createReadStream(clientPath).pipe(res);
2062
+ },
2063
+ });
1886
2064
 
1887
- res.write(`<h1>Assets Report:</h1>`);
2065
+ middlewares.push({
2066
+ name: "webpack-dev-server-invalidate",
2067
+ path: "/webpack-dev-server/invalidate",
2068
+ /**
2069
+ * @param {Request} req
2070
+ * @param {Response} res
2071
+ * @param {NextFunction} next
2072
+ * @returns {void}
2073
+ */
2074
+ middleware: (req, res, next) => {
2075
+ if (req.method !== "GET" && req.method !== "HEAD") {
2076
+ next();
2077
+ return;
2078
+ }
1888
2079
 
1889
- /**
1890
- * @type {StatsCompilation[]}
1891
- */
1892
- (statsForPrint).forEach((item, index) => {
1893
- res.write("<div>");
2080
+ this.invalidate();
1894
2081
 
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";
2082
+ res.end();
2083
+ },
2084
+ });
1902
2085
 
1903
- res.write(`<h2>Compilation: ${name}</h2>`);
1904
- res.write("<ul>");
2086
+ middlewares.push({
2087
+ name: "webpack-dev-server-open-editor",
2088
+ path: "/webpack-dev-server/open-editor",
2089
+ /**
2090
+ * @param {Request} req
2091
+ * @param {Response} res
2092
+ * @param {NextFunction} next
2093
+ * @returns {void}
2094
+ */
2095
+ middleware: (req, res, next) => {
2096
+ if (req.method !== "GET" && req.method !== "HEAD") {
2097
+ next();
2098
+ return;
2099
+ }
1905
2100
 
1906
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2101
+ if (!req.url) {
2102
+ next();
2103
+ return;
2104
+ }
1907
2105
 
1908
- for (const asset of /** @type {NonNullable<StatsCompilation["assets"]>} */ (
1909
- item.assets
1910
- )) {
1911
- const assetName = asset.name;
1912
- const assetURL = `${publicPath}${assetName}`;
2106
+ const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
2107
+ const params = new URLSearchParams(resolveUrl.search);
2108
+ const fileName = params.get("fileName");
1913
2109
 
1914
- res.write(
1915
- `<li>
1916
- <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1917
- </li>`,
1918
- );
1919
- }
2110
+ if (typeof fileName === "string") {
2111
+ // @ts-ignore
2112
+ const launchEditor = require("launch-editor");
1920
2113
 
1921
- res.write("</ul>");
1922
- res.write("</div>");
1923
- });
2114
+ launchEditor(fileName);
2115
+ }
1924
2116
 
1925
- res.end("</body></html>");
1926
- });
2117
+ res.end();
2118
+ },
1927
2119
  });
1928
- }
1929
2120
 
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);
2121
+ middlewares.push({
2122
+ name: "webpack-dev-server-assets",
2123
+ path: "/webpack-dev-server",
2124
+ /**
2125
+ * @param {Request} req
2126
+ * @param {Response} res
2127
+ * @param {NextFunction} next
2128
+ * @returns {void}
2129
+ */
2130
+ middleware: (req, res, next) => {
2131
+ if (req.method !== "GET" && req.method !== "HEAD") {
2132
+ next();
2133
+ return;
1940
2134
  }
1941
- });
1942
- }
1943
- }
1944
2135
 
1945
- /**
1946
- * @private
1947
- * @returns {void}
1948
- */
1949
- setupWatchFiles() {
1950
- const { watchFiles } = this.options;
2136
+ if (!this.middleware) {
2137
+ next();
2138
+ return;
2139
+ }
1951
2140
 
1952
- if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
1953
- /** @type {WatchFiles[]} */
1954
- (watchFiles).forEach((item) => {
1955
- this.watchFiles(item.paths, item.options);
1956
- });
1957
- }
1958
- }
2141
+ this.middleware.waitUntilValid((stats) => {
2142
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1959
2143
 
1960
- /**
1961
- * @private
1962
- * @returns {void}
1963
- */
1964
- setupMiddlewares() {
1965
- /**
1966
- * @type {Array<Middleware>}
1967
- */
1968
- let middlewares = [];
2144
+ // HEAD requests should not return body content
2145
+ if (req.method === "HEAD") {
2146
+ res.end();
2147
+ return;
2148
+ }
1969
2149
 
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");
2150
+ res.write(
2151
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
2152
+ );
1973
2153
 
1974
- middlewares.push({ name: "compression", middleware: compression() });
1975
- }
2154
+ /**
2155
+ * @type {StatsCompilation[]}
2156
+ */
2157
+ const statsForPrint =
2158
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2159
+ ? /** @type {NonNullable<StatsCompilation["children"]>} */
2160
+ (/** @type {MultiStats} */ (stats).toJson().children)
2161
+ : [/** @type {Stats} */ (stats).toJson()];
2162
+
2163
+ res.write(`<h1>Assets Report:</h1>`);
2164
+
2165
+ for (const [index, item] of statsForPrint.entries()) {
2166
+ res.write("<div>");
2167
+
2168
+ const name =
2169
+ // eslint-disable-next-line no-nested-ternary
2170
+ typeof item.name !== "undefined"
2171
+ ? item.name
2172
+ : /** @type {MultiStats} */ (stats).stats
2173
+ ? `unnamed[${index}]`
2174
+ : "unnamed";
2175
+
2176
+ res.write(`<h2>Compilation: ${name}</h2>`);
2177
+ res.write("<ul>");
2178
+
2179
+ const publicPath =
2180
+ item.publicPath === "auto" ? "" : item.publicPath;
2181
+ const assets =
2182
+ /** @type {NonNullable<StatsCompilation["assets"]>} */
2183
+ (item.assets);
2184
+
2185
+ for (const asset of assets) {
2186
+ const assetName = asset.name;
2187
+ const assetURL = `${publicPath}${assetName}`;
2188
+
2189
+ res.write(
2190
+ `<li>
2191
+ <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
2192
+ </li>`,
2193
+ );
2194
+ }
1976
2195
 
1977
- if (typeof this.options.headers !== "undefined") {
1978
- middlewares.push({
1979
- name: "set-headers",
1980
- path: "*",
1981
- middleware: this.setHeaders.bind(this),
1982
- });
1983
- }
2196
+ res.write("</ul>");
2197
+ res.write("</div>");
2198
+ }
1984
2199
 
1985
- middlewares.push({
1986
- name: "webpack-dev-middleware",
1987
- middleware:
1988
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
1989
- (this.middleware),
2200
+ res.end("</body></html>");
2201
+ });
2202
+ },
1990
2203
  });
1991
2204
 
1992
2205
  if (this.options.proxy) {
@@ -2106,8 +2319,8 @@ class Server {
2106
2319
 
2107
2320
  if (typeof bypassUrl === "boolean") {
2108
2321
  // skip the proxy
2109
- // @ts-ignore
2110
- req.url = null;
2322
+ res.statusCode = 404;
2323
+ req.url = "";
2111
2324
  next();
2112
2325
  } else if (typeof bypassUrl === "string") {
2113
2326
  // byPass to that url
@@ -2124,6 +2337,7 @@ class Server {
2124
2337
  name: "http-proxy-middleware",
2125
2338
  middleware: handler,
2126
2339
  });
2340
+
2127
2341
  // Also forward error requests to the proxy so it can handle them.
2128
2342
  middlewares.push({
2129
2343
  name: "http-proxy-middleware-error-handler",
@@ -2141,16 +2355,17 @@ class Server {
2141
2355
 
2142
2356
  middlewares.push({
2143
2357
  name: "webpack-dev-middleware",
2144
- middleware:
2145
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2146
- (this.middleware),
2358
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2147
2359
  });
2148
2360
  }
2149
2361
 
2150
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2362
+ const staticOptions =
2151
2363
  /** @type {NormalizedStatic[]} */
2152
- (this.options.static).forEach((staticOption) => {
2153
- staticOption.publicPath.forEach((publicPath) => {
2364
+ (this.options.static);
2365
+
2366
+ if (staticOptions.length > 0) {
2367
+ for (const staticOption of staticOptions) {
2368
+ for (const publicPath of staticOption.publicPath) {
2154
2369
  middlewares.push({
2155
2370
  name: "express-static",
2156
2371
  path: publicPath,
@@ -2159,8 +2374,8 @@ class Server {
2159
2374
  staticOption.staticOptions,
2160
2375
  ),
2161
2376
  });
2162
- });
2163
- });
2377
+ }
2378
+ }
2164
2379
  }
2165
2380
 
2166
2381
  if (this.options.historyApiFallback) {
@@ -2197,15 +2412,12 @@ class Server {
2197
2412
  // it is able to handle '/index.html' request after redirect
2198
2413
  middlewares.push({
2199
2414
  name: "webpack-dev-middleware",
2200
- middleware:
2201
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2202
- (this.middleware),
2415
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2203
2416
  });
2204
2417
 
2205
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2206
- /** @type {NormalizedStatic[]} */
2207
- (this.options.static).forEach((staticOption) => {
2208
- staticOption.publicPath.forEach((publicPath) => {
2418
+ if (staticOptions.length > 0) {
2419
+ for (const staticOption of staticOptions) {
2420
+ for (const publicPath of staticOption.publicPath) {
2209
2421
  middlewares.push({
2210
2422
  name: "express-static",
2211
2423
  path: publicPath,
@@ -2214,17 +2426,16 @@ class Server {
2214
2426
  staticOption.staticOptions,
2215
2427
  ),
2216
2428
  });
2217
- });
2218
- });
2429
+ }
2430
+ }
2219
2431
  }
2220
2432
  }
2221
2433
 
2222
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2434
+ if (staticOptions.length > 0) {
2223
2435
  const serveIndex = require("serve-index");
2224
2436
 
2225
- /** @type {NormalizedStatic[]} */
2226
- (this.options.static).forEach((staticOption) => {
2227
- staticOption.publicPath.forEach((publicPath) => {
2437
+ for (const staticOption of staticOptions) {
2438
+ for (const publicPath of staticOption.publicPath) {
2228
2439
  if (staticOption.serveIndex) {
2229
2440
  middlewares.push({
2230
2441
  name: "serve-index",
@@ -2249,15 +2460,14 @@ class Server {
2249
2460
  },
2250
2461
  });
2251
2462
  }
2252
- });
2253
- });
2463
+ }
2464
+ }
2254
2465
  }
2255
2466
 
2256
2467
  // Register this middleware always as the last one so that it's only used as a
2257
2468
  // fallback when no other middleware responses.
2258
2469
  middlewares.push({
2259
2470
  name: "options-middleware",
2260
- path: "*",
2261
2471
  /**
2262
2472
  * @param {Request} req
2263
2473
  * @param {Response} res
@@ -2279,37 +2489,93 @@ class Server {
2279
2489
  middlewares = this.options.setupMiddlewares(middlewares, this);
2280
2490
  }
2281
2491
 
2282
- middlewares.forEach((middleware) => {
2492
+ // Lazy init webpack dev middleware
2493
+ const lazyInitDevMiddleware = () => {
2494
+ if (!this.middleware) {
2495
+ const webpackDevMiddleware = require("webpack-dev-middleware");
2496
+
2497
+ // middleware for serving webpack bundle
2498
+ /** @type {import("webpack-dev-middleware").API<Request, Response>} */
2499
+ this.middleware = webpackDevMiddleware(
2500
+ this.compiler,
2501
+ this.options.devMiddleware,
2502
+ );
2503
+ }
2504
+
2505
+ return this.middleware;
2506
+ };
2507
+
2508
+ for (const i of middlewares) {
2509
+ if (i.name === "webpack-dev-middleware") {
2510
+ const item = /** @type {MiddlewareObject} */ (i);
2511
+
2512
+ if (typeof item.middleware === "undefined") {
2513
+ item.middleware = lazyInitDevMiddleware();
2514
+ }
2515
+ }
2516
+ }
2517
+
2518
+ for (const middleware of middlewares) {
2283
2519
  if (typeof middleware === "function") {
2284
- /** @type {import("express").Application} */
2285
- (this.app).use(middleware);
2520
+ /** @type {A} */
2521
+ (this.app).use(
2522
+ /** @type {NextHandleFunction | HandleFunction} */
2523
+ (middleware),
2524
+ );
2286
2525
  } else if (typeof middleware.path !== "undefined") {
2287
- /** @type {import("express").Application} */
2288
- (this.app).use(middleware.path, middleware.middleware);
2526
+ /** @type {A} */
2527
+ (this.app).use(
2528
+ middleware.path,
2529
+ /** @type {SimpleHandleFunction | NextHandleFunction} */
2530
+ (middleware.middleware),
2531
+ );
2289
2532
  } else {
2290
- /** @type {import("express").Application} */
2291
- (this.app).use(middleware.middleware);
2533
+ /** @type {A} */
2534
+ (this.app).use(
2535
+ /** @type {NextHandleFunction | HandleFunction} */
2536
+ (middleware.middleware),
2537
+ );
2292
2538
  }
2293
- });
2539
+ }
2294
2540
  }
2295
2541
 
2296
2542
  /**
2297
2543
  * @private
2298
- * @returns {void}
2544
+ * @returns {Promise<void>}
2299
2545
  */
2300
- createServer() {
2301
- const { type, options } = /** @type {ServerConfiguration} */ (
2302
- this.options.server
2303
- );
2546
+ async createServer() {
2547
+ const { type, options } =
2548
+ /** @type {ServerConfiguration<A, S>} */
2549
+ (this.options.server);
2550
+
2551
+ if (typeof type === "function") {
2552
+ /** @type {S | undefined}*/
2553
+ this.server = await type(
2554
+ /** @type {ServerOptions} */
2555
+ (options),
2556
+ /** @type {A} */
2557
+ (this.app),
2558
+ );
2559
+ } else {
2560
+ // eslint-disable-next-line import/no-dynamic-require
2561
+ const serverType = require(/** @type {string} */ (type));
2304
2562
 
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
- );
2563
+ /** @type {S | undefined}*/
2564
+ this.server =
2565
+ type === "http2"
2566
+ ? serverType.createSecureServer(
2567
+ { ...options, allowHTTP1: true },
2568
+ this.app,
2569
+ )
2570
+ : serverType.createServer(options, this.app);
2571
+ }
2572
+
2573
+ this.isTlsServer =
2574
+ typeof (
2575
+ /** @type {import("tls").Server} */ (this.server).setSecureContext
2576
+ ) !== "undefined";
2311
2577
 
2312
- /** @type {import("http").Server} */
2578
+ /** @type {S} */
2313
2579
  (this.server).on(
2314
2580
  "connection",
2315
2581
  /**
@@ -2326,7 +2592,7 @@ class Server {
2326
2592
  },
2327
2593
  );
2328
2594
 
2329
- /** @type {import("http").Server} */
2595
+ /** @type {S} */
2330
2596
  (this.server).on(
2331
2597
  "error",
2332
2598
  /**
@@ -2344,9 +2610,8 @@ class Server {
2344
2610
  */
2345
2611
  createWebSocketServer() {
2346
2612
  /** @type {WebSocketServerImplementation | undefined | null} */
2347
- this.webSocketServer = new /** @type {any} */ (this.getServerTransport())(
2348
- this,
2349
- );
2613
+ this.webSocketServer = new (this.getServerTransport())(this);
2614
+
2350
2615
  /** @type {WebSocketServerImplementation} */
2351
2616
  (this.webSocketServer).implementation.on(
2352
2617
  "connection",
@@ -2513,22 +2778,19 @@ class Server {
2513
2778
  */
2514
2779
  runBonjour() {
2515
2780
  const { Bonjour } = require("bonjour-service");
2781
+ const type = this.isTlsServer ? "https" : "http";
2782
+
2516
2783
  /**
2517
2784
  * @private
2518
2785
  * @type {Bonjour | undefined}
2519
2786
  */
2520
2787
  this.bonjour = new Bonjour();
2521
2788
  this.bonjour.publish({
2522
- // @ts-expect-error
2523
2789
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
2524
- // @ts-expect-error
2525
2790
  port: /** @type {number} */ (this.options.port),
2526
- // @ts-expect-error
2527
- type:
2528
- /** @type {ServerConfiguration} */
2529
- (this.options.server).type === "http" ? "http" : "https",
2791
+ type,
2530
2792
  subtypes: ["webpack"],
2531
- .../** @type {BonjourOptions} */ (this.options.bonjour),
2793
+ .../** @type {Partial<BonjourOptions>} */ (this.options.bonjour),
2532
2794
  });
2533
2795
  }
2534
2796
 
@@ -2608,23 +2870,15 @@ class Server {
2608
2870
  };
2609
2871
  const useColor = getColorsOption(this.getCompilerOptions());
2610
2872
 
2873
+ const server = /** @type {S} */ (this.server);
2874
+
2611
2875
  if (this.options.ipc) {
2612
- this.logger.info(
2613
- `Project is running at: "${
2614
- /** @type {import("http").Server} */
2615
- (this.server).address()
2616
- }"`,
2617
- );
2876
+ this.logger.info(`Project is running at: "${server.address()}"`);
2618
2877
  } else {
2619
- const protocol =
2620
- /** @type {ServerConfiguration} */
2621
- (this.options.server).type === "http" ? "http" : "https";
2878
+ const protocol = this.isTlsServer ? "https" : "http";
2622
2879
  const { address, port } =
2623
2880
  /** @type {import("net").AddressInfo} */
2624
- (
2625
- /** @type {import("http").Server} */
2626
- (this.server).address()
2627
- );
2881
+ (server.address());
2628
2882
  /**
2629
2883
  * @param {string} newHostname
2630
2884
  * @returns {string}
@@ -2632,7 +2886,7 @@ class Server {
2632
2886
  const prettyPrintURL = (newHostname) =>
2633
2887
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
2634
2888
 
2635
- let server;
2889
+ let host;
2636
2890
  let localhost;
2637
2891
  let loopbackIPv4;
2638
2892
  let loopbackIPv6;
@@ -2652,7 +2906,7 @@ class Server {
2652
2906
  }
2653
2907
 
2654
2908
  if (!isIP) {
2655
- server = prettyPrintURL(this.options.host);
2909
+ host = prettyPrintURL(this.options.host);
2656
2910
  }
2657
2911
  }
2658
2912
  }
@@ -2661,14 +2915,15 @@ class Server {
2661
2915
 
2662
2916
  if (parsedIP.range() === "unspecified") {
2663
2917
  localhost = prettyPrintURL("localhost");
2918
+ loopbackIPv6 = prettyPrintURL("::1");
2664
2919
 
2665
- const networkIPv4 = await Server.internalIP("v4");
2920
+ const networkIPv4 = Server.findIp("v4", false);
2666
2921
 
2667
2922
  if (networkIPv4) {
2668
2923
  networkUrlIPv4 = prettyPrintURL(networkIPv4);
2669
2924
  }
2670
2925
 
2671
- const networkIPv6 = await Server.internalIP("v6");
2926
+ const networkIPv6 = Server.findIp("v6", false);
2672
2927
 
2673
2928
  if (networkIPv6) {
2674
2929
  networkUrlIPv6 = prettyPrintURL(networkIPv6);
@@ -2697,8 +2952,8 @@ class Server {
2697
2952
 
2698
2953
  this.logger.info("Project is running at:");
2699
2954
 
2700
- if (server) {
2701
- this.logger.info(`Server: ${colors.info(useColor, server)}`);
2955
+ if (host) {
2956
+ this.logger.info(`Server: ${colors.info(useColor, host)}`);
2702
2957
  }
2703
2958
 
2704
2959
  if (localhost || loopbackIPv4 || loopbackIPv6) {
@@ -2770,11 +3025,7 @@ class Server {
2770
3025
  if (this.options.bonjour) {
2771
3026
  const bonjourProtocol =
2772
3027
  /** @type {BonjourOptions} */
2773
- (this.options.bonjour).type ||
2774
- /** @type {ServerConfiguration} */
2775
- (this.options.server).type === "http"
2776
- ? "http"
2777
- : "https";
3028
+ (this.options.bonjour).type || this.isTlsServer ? "https" : "http";
2778
3029
 
2779
3030
  this.logger.info(
2780
3031
  `Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`,
@@ -2796,8 +3047,8 @@ class Server {
2796
3047
  headers = headers(
2797
3048
  req,
2798
3049
  res,
2799
- /** @type {import("webpack-dev-middleware").API<IncomingMessage, ServerResponse>}*/
2800
- (this.middleware).context,
3050
+ // eslint-disable-next-line no-undefined
3051
+ this.middleware ? this.middleware.context : undefined,
2801
3052
  );
2802
3053
  }
2803
3054
 
@@ -2806,6 +3057,8 @@ class Server {
2806
3057
  */
2807
3058
  const allHeaders = [];
2808
3059
 
3060
+ allHeaders.push({ key: "X_TEST", value: "TEST" });
3061
+
2809
3062
  if (!Array.isArray(headers)) {
2810
3063
  // eslint-disable-next-line guard-for-in
2811
3064
  for (const name in headers) {
@@ -2816,14 +3069,9 @@ class Server {
2816
3069
  headers = allHeaders;
2817
3070
  }
2818
3071
 
2819
- headers.forEach(
2820
- /**
2821
- * @param {{key: string, value: any}} header
2822
- */
2823
- (header) => {
2824
- res.setHeader(header.key, header.value);
2825
- },
2826
- );
3072
+ for (const { key, value } of headers) {
3073
+ res.setHeader(key, value);
3074
+ }
2827
3075
  }
2828
3076
 
2829
3077
  next();
@@ -3097,7 +3345,7 @@ class Server {
3097
3345
 
3098
3346
  await /** @type {Promise<void>} */ (
3099
3347
  new Promise((resolve) => {
3100
- /** @type {import("http").Server} */
3348
+ /** @type {S} */
3101
3349
  (this.server).listen(listenOptions, () => {
3102
3350
  resolve();
3103
3351
  });
@@ -3183,10 +3431,10 @@ class Server {
3183
3431
  if (this.server) {
3184
3432
  await /** @type {Promise<void>} */ (
3185
3433
  new Promise((resolve) => {
3186
- /** @type {import("http").Server} */
3434
+ /** @type {S} */
3187
3435
  (this.server).close(() => {
3188
- this.server = null;
3189
-
3436
+ // eslint-disable-next-line no-undefined
3437
+ this.server = undefined;
3190
3438
  resolve();
3191
3439
  });
3192
3440
 
@@ -3205,7 +3453,6 @@ class Server {
3205
3453
  (this.middleware).close((error) => {
3206
3454
  if (error) {
3207
3455
  reject(error);
3208
-
3209
3456
  return;
3210
3457
  }
3211
3458
 
@@ -3214,7 +3461,8 @@ class Server {
3214
3461
  })
3215
3462
  );
3216
3463
 
3217
- this.middleware = null;
3464
+ // eslint-disable-next-line no-undefined
3465
+ this.middleware = undefined;
3218
3466
  }
3219
3467
  }
3220
3468