webpack-dev-server 4.6.0 → 4.7.3

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
@@ -11,14 +11,214 @@ const express = require("express");
11
11
  const { validate } = require("schema-utils");
12
12
  const schema = require("./options.json");
13
13
 
14
+ /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
15
+ /** @typedef {import("webpack").Compiler} Compiler */
16
+ /** @typedef {import("webpack").MultiCompiler} MultiCompiler */
17
+ /** @typedef {import("webpack").Configuration} WebpackConfiguration */
18
+ /** @typedef {import("webpack").StatsOptions} StatsOptions */
19
+ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */
20
+ /** @typedef {import("webpack").Stats} Stats */
21
+ /** @typedef {import("webpack").MultiStats} MultiStats */
22
+ /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
23
+ /** @typedef {import("express").Request} Request */
24
+ /** @typedef {import("express").Response} Response */
25
+ /** @typedef {import("express").NextFunction} NextFunction */
26
+ /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
27
+ /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
28
+ /** @typedef {import("chokidar").WatchOptions} WatchOptions */
29
+ /** @typedef {import("chokidar").FSWatcher} FSWatcher */
30
+ /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
31
+ /** @typedef {import("bonjour").Bonjour} Bonjour */
32
+ /** @typedef {import("bonjour").BonjourOptions} BonjourOptions */
33
+ /** @typedef {import("http-proxy-middleware").RequestHandler} RequestHandler */
34
+ /** @typedef {import("http-proxy-middleware").Options} HttpProxyMiddlewareOptions */
35
+ /** @typedef {import("http-proxy-middleware").Filter} HttpProxyMiddlewareOptionsFilter */
36
+ /** @typedef {import("serve-index").Options} ServeIndexOptions */
37
+ /** @typedef {import("serve-static").ServeStaticOptions} ServeStaticOptions */
38
+ /** @typedef {import("ipaddr.js").IPv4} IPv4 */
39
+ /** @typedef {import("ipaddr.js").IPv6} IPv6 */
40
+ /** @typedef {import("net").Socket} Socket */
41
+ /** @typedef {import("http").IncomingMessage} IncomingMessage */
42
+ /** @typedef {import("open").Options} OpenOptions */
43
+
44
+ /** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
45
+
46
+ /**
47
+ * @template Request, Response
48
+ * @typedef {import("webpack-dev-middleware").Options<Request, Response>} DevMiddlewareOptions
49
+ */
50
+
51
+ /**
52
+ * @template Request, Response
53
+ * @typedef {import("webpack-dev-middleware").Context<Request, Response>} DevMiddlewareContext
54
+ */
55
+
56
+ /**
57
+ * @typedef {"local-ip" | "local-ipv4" | "local-ipv6" | string} Host
58
+ */
59
+
60
+ /**
61
+ * @typedef {number | string | "auto"} Port
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} WatchFiles
66
+ * @property {string | string[]} paths
67
+ * @property {WatchOptions & { aggregateTimeout?: number, ignored?: string | RegExp | string[], poll?: number | boolean }} [options]
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} Static
72
+ * @property {string} [directory]
73
+ * @property {string | string[]} [publicPath]
74
+ * @property {boolean | ServeIndexOptions} [serveIndex]
75
+ * @property {ServeStaticOptions} [staticOptions]
76
+ * @property {boolean | WatchOptions & { aggregateTimeout?: number, ignored?: string | RegExp | string[], poll?: number | boolean }} [watch]
77
+ */
78
+
79
+ /**
80
+ * @typedef {Object} NormalizedStatic
81
+ * @property {string} directory
82
+ * @property {string[]} publicPath
83
+ * @property {false | ServeIndexOptions} serveIndex
84
+ * @property {ServeStaticOptions} staticOptions
85
+ * @property {false | WatchOptions} watch
86
+ */
87
+
88
+ /**
89
+ * @typedef {Object} ServerConfiguration
90
+ * @property {"http" | "https" | "spdy" | string} [type]
91
+ * @property {ServerOptions} [options]
92
+ */
93
+
94
+ /**
95
+ * @typedef {Object} WebSocketServerConfiguration
96
+ * @property {"sockjs" | "ws" | string | Function} [type]
97
+ * @property {Record<string, any>} [options]
98
+ */
99
+
100
+ /**
101
+ * @typedef {(import("ws").WebSocket | import("sockjs").Connection & { send: import("ws").WebSocket["send"], terminate: import("ws").WebSocket["terminate"], ping: import("ws").WebSocket["ping"] }) & { isAlive?: boolean }} ClientConnection
102
+ */
103
+
104
+ /**
105
+ * @typedef {import("ws").WebSocketServer | import("sockjs").Server & { close: import("ws").WebSocketServer["close"] }} WebSocketServer
106
+ */
107
+
108
+ /**
109
+ * @typedef {{ implementation: WebSocketServer, clients: ClientConnection[] }} WebSocketServerImplementation
110
+ */
111
+
112
+ /**
113
+ * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap
114
+ */
115
+
116
+ /**
117
+ * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray
118
+ */
119
+
120
+ /**
121
+ * @callback ByPass
122
+ * @param {Request} req
123
+ * @param {Response} res
124
+ * @param {ProxyConfigArray} proxyConfig
125
+ */
126
+
127
+ /**
128
+ * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray
129
+ */
130
+
131
+ /**
132
+ * @typedef {Object} OpenApp
133
+ * @property {string} [name]
134
+ * @property {string[]} [arguments]
135
+ */
136
+
137
+ /**
138
+ * @typedef {Object} Open
139
+ * @property {string | string[] | OpenApp} [app]
140
+ * @property {string | string[]} [target]
141
+ */
142
+
143
+ /**
144
+ * @typedef {Object} NormalizedOpen
145
+ * @property {string} target
146
+ * @property {import("open").Options} options
147
+ */
148
+
149
+ /**
150
+ * @typedef {Object} WebSocketURL
151
+ * @property {string} [hostname]
152
+ * @property {string} [password]
153
+ * @property {string} [pathname]
154
+ * @property {number | string} [port]
155
+ * @property {string} [protocol]
156
+ * @property {string} [username]
157
+ */
158
+
159
+ /**
160
+ * @typedef {Object} ClientConfiguration
161
+ * @property {"log" | "info" | "warn" | "error" | "none" | "verbose"} [logging]
162
+ * @property {boolean | { warnings?: boolean, errors?: boolean }} [overlay]
163
+ * @property {boolean} [progress]
164
+ * @property {boolean | number} [reconnect]
165
+ * @property {"ws" | "sockjs" | string} [webSocketTransport]
166
+ * @property {string | WebSocketURL} [webSocketURL]
167
+ */
168
+
169
+ /**
170
+ * @typedef {Array<{ key: string; value: string }> | Record<string, string | string[]>} Headers
171
+ */
172
+
173
+ /**
174
+ * @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler} Middleware
175
+ */
176
+
177
+ /**
178
+ * @typedef {Object} Configuration
179
+ * @property {boolean | string} [ipc]
180
+ * @property {Host} [host]
181
+ * @property {Port} [port]
182
+ * @property {boolean | "only"} [hot]
183
+ * @property {boolean} [liveReload]
184
+ * @property {DevMiddlewareOptions<Request, Response>} [devMiddleware]
185
+ * @property {boolean} [compress]
186
+ * @property {boolean} [magicHtml]
187
+ * @property {"auto" | "all" | string | string[]} [allowedHosts]
188
+ * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback]
189
+ * @property {boolean} [setupExitSignals]
190
+ * @property {boolean | BonjourOptions} [bonjour]
191
+ * @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
192
+ * @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]
196
+ * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
197
+ * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy]
198
+ * @property {boolean | string | Open | Array<string | Open>} [open]
199
+ * @property {boolean} [setupExitSignals]
200
+ * @property {boolean | ClientConfiguration} [client]
201
+ * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
202
+ * @property {(devServer: Server) => void} [onAfterSetupMiddleware]
203
+ * @property {(devServer: Server) => void} [onBeforeSetupMiddleware]
204
+ * @property {(devServer: Server) => void} [onListening]
205
+ * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
206
+ */
207
+
14
208
  if (!process.env.WEBPACK_SERVE) {
209
+ // TODO fix me in the next major release
210
+ // @ts-ignore
15
211
  process.env.WEBPACK_SERVE = true;
16
212
  }
17
213
 
18
214
  class Server {
215
+ /**
216
+ * @param {Configuration | Compiler | MultiCompiler} options
217
+ * @param {Compiler | MultiCompiler | Configuration} compiler
218
+ */
19
219
  constructor(options = {}, compiler) {
20
220
  // TODO: remove this after plugin support is published
21
- if (options.hooks) {
221
+ if (/** @type {Compiler | MultiCompiler} */ (options).hooks) {
22
222
  util.deprecate(
23
223
  () => {},
24
224
  "Using 'compiler' as the first argument is deprecated. Please use 'options' as the first argument and 'compiler' as the second argument.",
@@ -28,18 +228,65 @@ class Server {
28
228
  [options = {}, compiler] = [compiler, options];
29
229
  }
30
230
 
31
- validate(schema, options, "webpack Dev Server");
231
+ validate(/** @type {Schema} */ (schema), options, {
232
+ name: "Dev Server",
233
+ baseDataPath: "options",
234
+ });
32
235
 
33
- this.options = options;
236
+ this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler);
237
+ /**
238
+ * @type {ReturnType<Compiler["getInfrastructureLogger"]>}
239
+ * */
240
+ this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
241
+ this.options = /** @type {Configuration} */ (options);
242
+ /**
243
+ * @type {FSWatcher[]}
244
+ */
34
245
  this.staticWatchers = [];
246
+ /**
247
+ * @private
248
+ * @type {{ name: string | symbol, listener: (...args: any[]) => void}[] }}
249
+ */
35
250
  this.listeners = [];
36
251
  // Keep track of websocket proxies for external websocket upgrade.
252
+ /**
253
+ * @private
254
+ * @type {RequestHandler[]}
255
+ */
37
256
  this.webSocketProxies = [];
257
+ /**
258
+ * @type {Socket[]}
259
+ */
38
260
  this.sockets = [];
39
- this.compiler = compiler;
40
- this.currentHash = null;
261
+ /**
262
+ * @private
263
+ * @type {string | undefined}
264
+ */
265
+ // eslint-disable-next-line no-undefined
266
+ this.currentHash = undefined;
267
+ }
268
+
269
+ // TODO compatibility with webpack v4, remove it after drop
270
+ static get cli() {
271
+ return {
272
+ get getArguments() {
273
+ return () => require("../bin/cli-flags");
274
+ },
275
+ get processArguments() {
276
+ return require("../bin/process-arguments");
277
+ },
278
+ };
279
+ }
280
+
281
+ static get schema() {
282
+ return schema;
41
283
  }
42
284
 
285
+ /**
286
+ * @private
287
+ * @returns {StatsOptions}
288
+ * @constructor
289
+ */
43
290
  static get DEFAULT_STATS() {
44
291
  return {
45
292
  all: false,
@@ -50,6 +297,10 @@ class Server {
50
297
  };
51
298
  }
52
299
 
300
+ /**
301
+ * @param {string} URL
302
+ * @returns {boolean}
303
+ */
53
304
  static isAbsoluteURL(URL) {
54
305
  // Don't match Windows paths `c:\`
55
306
  if (/^[a-zA-Z]:\\/.test(URL)) {
@@ -61,13 +312,19 @@ class Server {
61
312
  return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
62
313
  }
63
314
 
315
+ /**
316
+ * @param {string} gateway
317
+ * @returns {string | undefined}
318
+ */
64
319
  static findIp(gateway) {
65
320
  const gatewayIp = ipaddr.parse(gateway);
66
321
 
67
322
  // Look for the matching interface in all local interfaces.
68
323
  for (const addresses of Object.values(os.networkInterfaces())) {
69
- for (const { cidr } of addresses) {
70
- const net = ipaddr.parseCIDR(cidr);
324
+ for (const { cidr } of /** @type {NetworkInterfaceInfo[]} */ (
325
+ addresses
326
+ )) {
327
+ const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));
71
328
 
72
329
  if (
73
330
  net[0] &&
@@ -80,6 +337,10 @@ class Server {
80
337
  }
81
338
  }
82
339
 
340
+ /**
341
+ * @param {"v4" | "v6"} family
342
+ * @returns {Promise<string | undefined>}
343
+ */
83
344
  static async internalIP(family) {
84
345
  try {
85
346
  const { gateway } = await defaultGateway[family]();
@@ -89,6 +350,10 @@ class Server {
89
350
  }
90
351
  }
91
352
 
353
+ /**
354
+ * @param {"v4" | "v6"} family
355
+ * @returns {string | undefined}
356
+ */
92
357
  static internalIPSync(family) {
93
358
  try {
94
359
  const { gateway } = defaultGateway[family].sync();
@@ -98,6 +363,10 @@ class Server {
98
363
  }
99
364
  }
100
365
 
366
+ /**
367
+ * @param {Host} hostname
368
+ * @returns {Promise<string>}
369
+ */
101
370
  static async getHostname(hostname) {
102
371
  if (hostname === "local-ip") {
103
372
  return (
@@ -114,6 +383,10 @@ class Server {
114
383
  return hostname;
115
384
  }
116
385
 
386
+ /**
387
+ * @param {Port} port
388
+ * @returns {Promise<number | string>}
389
+ */
117
390
  static async getFreePort(port) {
118
391
  if (typeof port !== "undefined" && port !== null && port !== "auto") {
119
392
  return port;
@@ -122,21 +395,32 @@ class Server {
122
395
  const pRetry = require("p-retry");
123
396
  const portfinder = require("portfinder");
124
397
 
125
- portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
398
+ portfinder.basePort =
399
+ typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined"
400
+ ? parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10)
401
+ : 8080;
126
402
 
127
403
  // Try to find unused port and listen on it for 3 times,
128
404
  // if port is not specified in options.
129
405
  const defaultPortRetry =
130
- parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;
406
+ typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined"
407
+ ? parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10)
408
+ : 3;
131
409
 
132
410
  return pRetry(() => portfinder.getPortPromise(), {
133
411
  retries: defaultPortRetry,
134
412
  });
135
413
  }
136
414
 
415
+ /**
416
+ * @returns {string}
417
+ */
137
418
  static findCacheDir() {
138
419
  const cwd = process.cwd();
139
420
 
421
+ /**
422
+ * @type {string | undefined}
423
+ */
140
424
  let dir = cwd;
141
425
 
142
426
  for (;;) {
@@ -167,7 +451,14 @@ class Server {
167
451
  return path.resolve(dir, "node_modules/.cache/webpack-dev-server");
168
452
  }
169
453
 
454
+ /**
455
+ * @private
456
+ * @param {Compiler} compiler
457
+ */
170
458
  addAdditionalEntries(compiler) {
459
+ /**
460
+ * @type {string[]}
461
+ */
171
462
  const additionalEntries = [];
172
463
 
173
464
  const isWebTarget = compiler.options.externalsPresets
@@ -181,38 +472,44 @@ class Server {
181
472
  // eslint-disable-next-line no-undefined
182
473
  undefined,
183
474
  null,
184
- ].includes(compiler.options.target);
475
+ ].includes(/** @type {string} */ (compiler.options.target));
185
476
 
186
477
  // TODO maybe empty empty client
187
478
  if (this.options.client && isWebTarget) {
188
- let webSocketURL = "";
479
+ let webSocketURLStr = "";
480
+
189
481
  if (this.options.webSocketServer) {
482
+ const webSocketURL =
483
+ /** @type {WebSocketURL} */
484
+ (
485
+ /** @type {ClientConfiguration} */
486
+ (this.options.client).webSocketURL
487
+ );
488
+ const webSocketServer =
489
+ /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
490
+ (this.options.webSocketServer);
190
491
  const searchParams = new URLSearchParams();
191
492
 
192
- /** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
493
+ /** @type {string} */
193
494
  let protocol;
194
495
 
195
496
  // We are proxying dev server and need to specify custom `hostname`
196
- if (typeof this.options.client.webSocketURL.protocol !== "undefined") {
197
- protocol = this.options.client.webSocketURL.protocol;
497
+ if (typeof webSocketURL.protocol !== "undefined") {
498
+ protocol = webSocketURL.protocol;
198
499
  } else {
199
- protocol = this.options.server.type === "http" ? "ws:" : "wss:";
500
+ protocol =
501
+ /** @type {ServerConfiguration} */
502
+ (this.options.server).type === "http" ? "ws:" : "wss:";
200
503
  }
201
504
 
202
505
  searchParams.set("protocol", protocol);
203
506
 
204
- if (typeof this.options.client.webSocketURL.username !== "undefined") {
205
- searchParams.set(
206
- "username",
207
- this.options.client.webSocketURL.username
208
- );
507
+ if (typeof webSocketURL.username !== "undefined") {
508
+ searchParams.set("username", webSocketURL.username);
209
509
  }
210
510
 
211
- if (typeof this.options.client.webSocketURL.password !== "undefined") {
212
- searchParams.set(
213
- "password",
214
- this.options.client.webSocketURL.password
215
- );
511
+ if (typeof webSocketURL.password !== "undefined") {
512
+ searchParams.set("password", webSocketURL.password);
216
513
  }
217
514
 
218
515
  /** @type {string} */
@@ -220,18 +517,18 @@ class Server {
220
517
 
221
518
  // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
222
519
  // TODO show warning about this
223
- const isSockJSType = this.options.webSocketServer.type === "sockjs";
520
+ const isSockJSType = webSocketServer.type === "sockjs";
224
521
 
225
522
  // We are proxying dev server and need to specify custom `hostname`
226
- if (typeof this.options.client.webSocketURL.hostname !== "undefined") {
227
- hostname = this.options.client.webSocketURL.hostname;
523
+ if (typeof webSocketURL.hostname !== "undefined") {
524
+ hostname = webSocketURL.hostname;
228
525
  }
229
526
  // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
230
527
  else if (
231
- typeof this.options.webSocketServer.options.host !== "undefined" &&
528
+ typeof webSocketServer.options.host !== "undefined" &&
232
529
  !isSockJSType
233
530
  ) {
234
- hostname = this.options.webSocketServer.options.host;
531
+ hostname = webSocketServer.options.host;
235
532
  }
236
533
  // The `host` option is specified
237
534
  else if (typeof this.options.host !== "undefined") {
@@ -248,15 +545,15 @@ class Server {
248
545
  let port;
249
546
 
250
547
  // We are proxying dev server and need to specify custom `port`
251
- if (typeof this.options.client.webSocketURL.port !== "undefined") {
252
- port = this.options.client.webSocketURL.port;
548
+ if (typeof webSocketURL.port !== "undefined") {
549
+ port = webSocketURL.port;
253
550
  }
254
551
  // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
255
552
  else if (
256
- typeof this.options.webSocketServer.options.port !== "undefined" &&
553
+ typeof webSocketServer.options.port !== "undefined" &&
257
554
  !isSockJSType
258
555
  ) {
259
- port = this.options.webSocketServer.options.port;
556
+ port = webSocketServer.options.port;
260
557
  }
261
558
  // The `port` option is specified
262
559
  else if (typeof this.options.port === "number") {
@@ -280,47 +577,47 @@ class Server {
280
577
  let pathname = "";
281
578
 
282
579
  // We are proxying dev server and need to specify custom `pathname`
283
- if (typeof this.options.client.webSocketURL.pathname !== "undefined") {
284
- pathname = this.options.client.webSocketURL.pathname;
580
+ if (typeof webSocketURL.pathname !== "undefined") {
581
+ pathname = webSocketURL.pathname;
285
582
  }
286
583
  // Web socket server works on custom `path`
287
584
  else if (
288
- typeof this.options.webSocketServer.options.prefix !== "undefined" ||
289
- typeof this.options.webSocketServer.options.path !== "undefined"
585
+ typeof webSocketServer.options.prefix !== "undefined" ||
586
+ typeof webSocketServer.options.path !== "undefined"
290
587
  ) {
291
588
  pathname =
292
- this.options.webSocketServer.options.prefix ||
293
- this.options.webSocketServer.options.path;
589
+ webSocketServer.options.prefix || webSocketServer.options.path;
294
590
  }
295
591
 
296
592
  searchParams.set("pathname", pathname);
297
593
 
298
- if (typeof this.options.client.logging !== "undefined") {
299
- searchParams.set("logging", this.options.client.logging);
594
+ const client = /** @type {ClientConfiguration} */ (this.options.client);
595
+
596
+ if (typeof client.logging !== "undefined") {
597
+ searchParams.set("logging", client.logging);
300
598
  }
301
599
 
302
- if (typeof this.options.client.reconnect !== "undefined") {
303
- searchParams.set("reconnect", this.options.client.reconnect);
600
+ if (typeof client.reconnect !== "undefined") {
601
+ searchParams.set(
602
+ "reconnect",
603
+ typeof client.reconnect === "number"
604
+ ? String(client.reconnect)
605
+ : "10"
606
+ );
304
607
  }
305
608
 
306
- webSocketURL = searchParams.toString();
609
+ webSocketURLStr = searchParams.toString();
307
610
  }
308
611
 
309
612
  additionalEntries.push(
310
- `${require.resolve("../client/index.js")}?${webSocketURL}`
613
+ `${require.resolve("../client/index.js")}?${webSocketURLStr}`
311
614
  );
312
615
  }
313
616
 
314
- if (this.options.hot) {
315
- let hotEntry;
316
-
317
- if (this.options.hot === "only") {
318
- hotEntry = require.resolve("webpack/hot/only-dev-server");
319
- } else if (this.options.hot) {
320
- hotEntry = require.resolve("webpack/hot/dev-server");
321
- }
322
-
323
- additionalEntries.push(hotEntry);
617
+ if (this.options.hot === "only") {
618
+ additionalEntries.push(require.resolve("webpack/hot/only-dev-server"));
619
+ } else if (this.options.hot) {
620
+ additionalEntries.push(require.resolve("webpack/hot/dev-server"));
324
621
  }
325
622
 
326
623
  const webpack = compiler.webpack || require("webpack");
@@ -338,9 +635,9 @@ class Server {
338
635
  else {
339
636
  /**
340
637
  * prependEntry Method for webpack 4
341
- * @param {Entry} originalEntry
342
- * @param {Entry} newAdditionalEntries
343
- * @returns {Entry}
638
+ * @param {any} originalEntry
639
+ * @param {any} newAdditionalEntries
640
+ * @returns {any}
344
641
  */
345
642
  const prependEntry = (originalEntry, newAdditionalEntries) => {
346
643
  if (typeof originalEntry === "function") {
@@ -369,7 +666,7 @@ class Server {
369
666
 
370
667
  // in this case, entry is a string or an array.
371
668
  // make sure that we do not add duplicates.
372
- /** @type {Entry} */
669
+ /** @type {any} */
373
670
  const entriesClone = additionalEntries.slice(0);
374
671
 
375
672
  [].concat(originalEntry).forEach((newEntry) => {
@@ -386,65 +683,80 @@ class Server {
386
683
  additionalEntries
387
684
  );
388
685
  compiler.hooks.entryOption.call(
389
- compiler.options.context,
686
+ /** @type {string} */ (compiler.options.context),
390
687
  compiler.options.entry
391
688
  );
392
689
  }
393
690
  }
394
691
 
692
+ /**
693
+ * @private
694
+ * @returns {Compiler["options"]}
695
+ */
395
696
  getCompilerOptions() {
396
- if (typeof this.compiler.compilers !== "undefined") {
397
- if (this.compiler.compilers.length === 1) {
398
- return this.compiler.compilers[0].options;
697
+ if (
698
+ typeof (/** @type {MultiCompiler} */ (this.compiler).compilers) !==
699
+ "undefined"
700
+ ) {
701
+ if (/** @type {MultiCompiler} */ (this.compiler).compilers.length === 1) {
702
+ return (
703
+ /** @type {MultiCompiler} */
704
+ (this.compiler).compilers[0].options
705
+ );
399
706
  }
400
707
 
401
708
  // Configuration with the `devServer` options
402
- const compilerWithDevServer = this.compiler.compilers.find(
403
- (config) => config.options.devServer
404
- );
709
+ const compilerWithDevServer =
710
+ /** @type {MultiCompiler} */
711
+ (this.compiler).compilers.find((config) => config.options.devServer);
405
712
 
406
713
  if (compilerWithDevServer) {
407
714
  return compilerWithDevServer.options;
408
715
  }
409
716
 
410
717
  // Configuration with `web` preset
411
- const compilerWithWebPreset = this.compiler.compilers.find(
412
- (config) =>
413
- (config.options.externalsPresets &&
414
- config.options.externalsPresets.web) ||
415
- [
416
- "web",
417
- "webworker",
418
- "electron-preload",
419
- "electron-renderer",
420
- "node-webkit",
421
- // eslint-disable-next-line no-undefined
422
- undefined,
423
- null,
424
- ].includes(config.options.target)
425
- );
718
+ const compilerWithWebPreset =
719
+ /** @type {MultiCompiler} */
720
+ (this.compiler).compilers.find(
721
+ (config) =>
722
+ (config.options.externalsPresets &&
723
+ config.options.externalsPresets.web) ||
724
+ [
725
+ "web",
726
+ "webworker",
727
+ "electron-preload",
728
+ "electron-renderer",
729
+ "node-webkit",
730
+ // eslint-disable-next-line no-undefined
731
+ undefined,
732
+ null,
733
+ ].includes(/** @type {string} */ (config.options.target))
734
+ );
426
735
 
427
736
  if (compilerWithWebPreset) {
428
737
  return compilerWithWebPreset.options;
429
738
  }
430
739
 
431
740
  // Fallback
432
- return this.compiler.compilers[0].options;
741
+ return /** @type {MultiCompiler} */ (this.compiler).compilers[0].options;
433
742
  }
434
743
 
435
- return this.compiler.options;
744
+ return /** @type {Compiler} */ (this.compiler).options;
436
745
  }
437
746
 
747
+ /**
748
+ * @private
749
+ * @returns {Promise<void>}
750
+ */
438
751
  async normalizeOptions() {
439
752
  const { options } = this;
440
-
441
- if (!this.logger) {
442
- this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
443
- }
444
-
445
753
  const compilerOptions = this.getCompilerOptions();
446
754
  // TODO remove `{}` after drop webpack v4 support
447
755
  const compilerWatchOptions = compilerOptions.watchOptions || {};
756
+ /**
757
+ * @param {WatchOptions & WebpackConfiguration["watchOptions"]} watchOptions
758
+ * @returns {WatchOptions}
759
+ */
448
760
  const getWatchOptions = (watchOptions = {}) => {
449
761
  const getPolling = () => {
450
762
  if (typeof watchOptions.usePolling !== "undefined") {
@@ -494,6 +806,10 @@ class Server {
494
806
  ...rest,
495
807
  };
496
808
  };
809
+ /**
810
+ * @param {string | Static | undefined} [optionsForStatic]
811
+ * @returns {NormalizedStatic}
812
+ */
497
813
  const getStaticItem = (optionsForStatic) => {
498
814
  const getDefaultStaticOptions = () => {
499
815
  return {
@@ -505,6 +821,7 @@ class Server {
505
821
  };
506
822
  };
507
823
 
824
+ /** @type {NormalizedStatic} */
508
825
  let item;
509
826
 
510
827
  if (typeof optionsForStatic === "undefined") {
@@ -528,8 +845,11 @@ class Server {
528
845
  ? optionsForStatic.staticOptions
529
846
  : def.staticOptions,
530
847
  publicPath:
848
+ // eslint-disable-next-line no-nested-ternary
531
849
  typeof optionsForStatic.publicPath !== "undefined"
532
- ? optionsForStatic.publicPath
850
+ ? Array.isArray(optionsForStatic.publicPath)
851
+ ? optionsForStatic.publicPath
852
+ : [optionsForStatic.publicPath]
533
853
  : def.publicPath,
534
854
  // TODO: do merge in the next major release
535
855
  serveIndex:
@@ -557,11 +877,6 @@ class Server {
557
877
  throw new Error("Using a URL as static.directory is not supported");
558
878
  }
559
879
 
560
- // ensure that publicPath is an array
561
- if (typeof item.publicPath === "string") {
562
- item.publicPath = [item.publicPath];
563
- }
564
-
565
880
  return item;
566
881
  };
567
882
 
@@ -674,14 +989,21 @@ class Server {
674
989
  const isHTTPs = Boolean(options.https);
675
990
  const isSPDY = Boolean(options.http2);
676
991
 
677
- if (isHTTPs || isSPDY) {
992
+ if (isHTTPs) {
993
+ // TODO: remove in the next major release
994
+ util.deprecate(
995
+ () => {},
996
+ "'https' option is deprecated. Please use the 'server' option.",
997
+ "DEP_WEBPACK_DEV_SERVER_HTTPS"
998
+ )();
999
+ }
1000
+
1001
+ if (isSPDY) {
678
1002
  // TODO: remove in the next major release
679
1003
  util.deprecate(
680
1004
  () => {},
681
- `'${
682
- isHTTPs ? "https" : "http2"
683
- }' option is deprecated. Please use the 'server' option.`,
684
- `DEP_WEBPACK_DEV_SERVER_${isHTTPs ? "HTTPS" : "HTTP2"}`
1005
+ "'http2' option is deprecated. Please use the 'server' option.",
1006
+ "DEP_WEBPACK_DEV_SERVER_HTTP2"
685
1007
  )();
686
1008
  }
687
1009
 
@@ -692,7 +1014,7 @@ class Server {
692
1014
  ? options.server
693
1015
  : // eslint-disable-next-line no-nested-ternary
694
1016
  typeof (options.server || {}).type === "string"
695
- ? options.server.type
1017
+ ? /** @type {ServerConfiguration} */ (options.server).type || "http"
696
1018
  : // eslint-disable-next-line no-nested-ternary
697
1019
  isSPDY
698
1020
  ? "spdy"
@@ -700,33 +1022,64 @@ class Server {
700
1022
  ? "https"
701
1023
  : "http",
702
1024
  options: {
703
- ...options.https,
704
- ...(options.server || {}).options,
1025
+ .../** @type {ServerOptions} */ (options.https),
1026
+ .../** @type {ServerConfiguration} */ (options.server || {}).options,
705
1027
  },
706
1028
  };
707
1029
 
708
1030
  if (
709
1031
  options.server.type === "spdy" &&
710
- typeof options.server.options.spdy === "undefined"
1032
+ typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
1033
+ "undefined"
711
1034
  ) {
712
- options.server.options.spdy = {
1035
+ /** @type {ServerOptions} */
1036
+ (options.server.options).spdy = {
713
1037
  protocols: ["h2", "http/1.1"],
714
1038
  };
715
1039
  }
716
1040
 
717
1041
  if (options.server.type === "https" || options.server.type === "spdy") {
718
- if (typeof options.server.options.requestCert === "undefined") {
719
- options.server.options.requestCert = false;
1042
+ if (
1043
+ typeof (
1044
+ /** @type {ServerOptions} */ (options.server.options).requestCert
1045
+ ) === "undefined"
1046
+ ) {
1047
+ /** @type {ServerOptions} */
1048
+ (options.server.options).requestCert = false;
720
1049
  }
721
1050
 
722
- // TODO remove the `cacert` option in favor `ca` in the next major release
723
- for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) {
724
- if (typeof options.server.options[property] === "undefined") {
1051
+ const httpsProperties =
1052
+ /** @type {Array<keyof ServerOptions>} */
1053
+ (["cacert", "ca", "cert", "crl", "key", "pfx"]);
1054
+
1055
+ for (const property of httpsProperties) {
1056
+ if (
1057
+ typeof (
1058
+ /** @type {ServerOptions} */ (options.server.options)[property]
1059
+ ) === "undefined"
1060
+ ) {
725
1061
  // eslint-disable-next-line no-continue
726
1062
  continue;
727
1063
  }
728
1064
 
729
- const value = options.server.options[property];
1065
+ // @ts-ignore
1066
+ if (property === "cacert") {
1067
+ // TODO remove the `cacert` option in favor `ca` in the next major release
1068
+ util.deprecate(
1069
+ () => {},
1070
+ "The 'cacert' option is deprecated. Please use the 'ca' option.",
1071
+ "DEP_WEBPACK_DEV_SERVER_CACERT"
1072
+ )();
1073
+ }
1074
+
1075
+ /** @type {any} */
1076
+ const value =
1077
+ /** @type {ServerOptions} */
1078
+ (options.server.options)[property];
1079
+ /**
1080
+ * @param {string | Buffer | undefined} item
1081
+ * @returns {string | Buffer | undefined}
1082
+ */
730
1083
  const readFile = (item) => {
731
1084
  if (
732
1085
  Buffer.isBuffer(item) ||
@@ -749,14 +1102,18 @@ class Server {
749
1102
  }
750
1103
  };
751
1104
 
752
- options.server.options[property] = Array.isArray(value)
1105
+ /** @type {any} */
1106
+ (options.server.options)[property] = Array.isArray(value)
753
1107
  ? value.map((item) => readFile(item))
754
1108
  : readFile(value);
755
1109
  }
756
1110
 
757
1111
  let fakeCert;
758
1112
 
759
- if (!options.server.options.key || !options.server.options.cert) {
1113
+ if (
1114
+ !(/** @type {ServerOptions} */ (options.server.options).key) ||
1115
+ /** @type {ServerOptions} */ (!options.server.options).cert
1116
+ ) {
760
1117
  const certificateDir = Server.findCacheDir();
761
1118
  const certificatePath = path.join(certificateDir, "server.pem");
762
1119
  let certificateExists;
@@ -771,11 +1128,10 @@ class Server {
771
1128
  if (certificateExists) {
772
1129
  const certificateTtl = 1000 * 60 * 60 * 24;
773
1130
  const certificateStat = await fs.promises.stat(certificatePath);
774
-
775
- const now = new Date();
1131
+ const now = Number(new Date());
776
1132
 
777
1133
  // cert is more than 30 days old, kill it with fire
778
- if ((now - certificateStat.ctime) / certificateTtl > 30) {
1134
+ if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
779
1135
  const del = require("del");
780
1136
 
781
1137
  this.logger.info(
@@ -791,6 +1147,7 @@ class Server {
791
1147
  if (!certificateExists) {
792
1148
  this.logger.info("Generating SSL certificate...");
793
1149
 
1150
+ // @ts-ignore
794
1151
  const selfsigned = require("selfsigned");
795
1152
  const attributes = [{ name: "commonName", value: "localhost" }];
796
1153
  const pems = selfsigned.generate(attributes, {
@@ -871,20 +1228,37 @@ class Server {
871
1228
  this.logger.info(`SSL certificate: ${certificatePath}`);
872
1229
  }
873
1230
 
874
- if (options.server.options.cacert) {
875
- if (options.server.options.ca) {
1231
+ if (
1232
+ /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ (
1233
+ options.server.options
1234
+ ).cacert
1235
+ ) {
1236
+ if (/** @type {ServerOptions} */ (options.server.options).ca) {
876
1237
  this.logger.warn(
877
1238
  "Do not specify 'ca' and 'cacert' options together, the 'ca' option will be used."
878
1239
  );
879
1240
  } else {
880
- options.server.options.ca = options.server.options.cacert;
1241
+ /** @type {ServerOptions} */
1242
+ (options.server.options).ca =
1243
+ /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */
1244
+ (options.server.options).cacert;
881
1245
  }
882
1246
 
883
- delete options.server.options.cacert;
1247
+ delete (
1248
+ /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ (
1249
+ options.server.options
1250
+ ).cacert
1251
+ );
884
1252
  }
885
1253
 
886
- options.server.options.key = options.server.options.key || fakeCert;
887
- options.server.options.cert = options.server.options.cert || fakeCert;
1254
+ /** @type {ServerOptions} */
1255
+ (options.server.options).key =
1256
+ /** @type {ServerOptions} */
1257
+ (options.server.options).key || fakeCert;
1258
+ /** @type {ServerOptions} */
1259
+ (options.server.options).cert =
1260
+ /** @type {ServerOptions} */
1261
+ (options.server.options).cert || fakeCert;
888
1262
  }
889
1263
 
890
1264
  if (typeof options.ipc === "boolean") {
@@ -903,6 +1277,11 @@ class Server {
903
1277
 
904
1278
  // https://github.com/webpack/webpack-dev-server/issues/1990
905
1279
  const defaultOpenOptions = { wait: false };
1280
+ /**
1281
+ * @param {any} target
1282
+ * @returns {NormalizedOpen[]}
1283
+ */
1284
+ // TODO: remove --open-app in favor of --open-app-name
906
1285
  const getOpenItemsFromObject = ({ target, ...rest }) => {
907
1286
  const normalizedOptions = { ...defaultOpenOptions, ...rest };
908
1287
 
@@ -924,14 +1303,25 @@ class Server {
924
1303
  };
925
1304
 
926
1305
  if (typeof options.open === "undefined") {
927
- options.open = [];
1306
+ /** @type {NormalizedOpen[]} */
1307
+ (options.open) = [];
928
1308
  } else if (typeof options.open === "boolean") {
929
- options.open = options.open
930
- ? [{ target: "<url>", options: defaultOpenOptions }]
1309
+ /** @type {NormalizedOpen[]} */
1310
+ (options.open) = options.open
1311
+ ? [
1312
+ {
1313
+ target: "<url>",
1314
+ options: /** @type {OpenOptions} */ (defaultOpenOptions),
1315
+ },
1316
+ ]
931
1317
  : [];
932
1318
  } else if (typeof options.open === "string") {
933
- options.open = [{ target: options.open, options: defaultOpenOptions }];
1319
+ /** @type {NormalizedOpen[]} */
1320
+ (options.open) = [{ target: options.open, options: defaultOpenOptions }];
934
1321
  } else if (Array.isArray(options.open)) {
1322
+ /**
1323
+ * @type {NormalizedOpen[]}
1324
+ */
935
1325
  const result = [];
936
1326
 
937
1327
  options.open.forEach((item) => {
@@ -944,9 +1334,29 @@ class Server {
944
1334
  result.push(...getOpenItemsFromObject(item));
945
1335
  });
946
1336
 
947
- options.open = result;
1337
+ /** @type {NormalizedOpen[]} */
1338
+ (options.open) = result;
948
1339
  } else {
949
- options.open = [...getOpenItemsFromObject(options.open)];
1340
+ /** @type {NormalizedOpen[]} */
1341
+ (options.open) = [...getOpenItemsFromObject(options.open)];
1342
+ }
1343
+
1344
+ if (options.onAfterSetupMiddleware) {
1345
+ // TODO: remove in the next major release
1346
+ util.deprecate(
1347
+ () => {},
1348
+ "'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.",
1349
+ `DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE`
1350
+ )();
1351
+ }
1352
+
1353
+ if (options.onBeforeSetupMiddleware) {
1354
+ // TODO: remove in the next major release
1355
+ util.deprecate(
1356
+ () => {},
1357
+ "'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.",
1358
+ `DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE`
1359
+ )();
950
1360
  }
951
1361
 
952
1362
  if (typeof options.port === "string" && options.port !== "auto") {
@@ -970,61 +1380,91 @@ class Server {
970
1380
  Object.prototype.hasOwnProperty.call(options.proxy, "target") ||
971
1381
  Object.prototype.hasOwnProperty.call(options.proxy, "router")
972
1382
  ) {
973
- options.proxy = [options.proxy];
1383
+ /** @type {ProxyArray} */
1384
+ (options.proxy) = [/** @type {ProxyConfigMap} */ (options.proxy)];
974
1385
  } else {
975
- options.proxy = Object.keys(options.proxy).map((context) => {
976
- let proxyOptions;
977
- // For backwards compatibility reasons.
978
- const correctedContext = context
979
- .replace(/^\*$/, "**")
980
- .replace(/\/\*$/, "");
981
-
982
- if (typeof options.proxy[context] === "string") {
983
- proxyOptions = {
984
- context: correctedContext,
985
- target: options.proxy[context],
986
- };
987
- } else {
988
- proxyOptions = { ...options.proxy[context] };
989
- proxyOptions.context = correctedContext;
1386
+ /** @type {ProxyArray} */
1387
+ (options.proxy) = Object.keys(options.proxy).map(
1388
+ /**
1389
+ * @param {string} context
1390
+ * @returns {HttpProxyMiddlewareOptions}
1391
+ */
1392
+ (context) => {
1393
+ let proxyOptions;
1394
+ // For backwards compatibility reasons.
1395
+ const correctedContext = context
1396
+ .replace(/^\*$/, "**")
1397
+ .replace(/\/\*$/, "");
1398
+
1399
+ if (
1400
+ typeof (
1401
+ /** @type {ProxyConfigMap} */ (options.proxy)[context]
1402
+ ) === "string"
1403
+ ) {
1404
+ proxyOptions = {
1405
+ context: correctedContext,
1406
+ target:
1407
+ /** @type {ProxyConfigMap} */
1408
+ (options.proxy)[context],
1409
+ };
1410
+ } else {
1411
+ proxyOptions = {
1412
+ // @ts-ignore
1413
+ .../** @type {ProxyConfigMap} */ (options.proxy)[context],
1414
+ };
1415
+ proxyOptions.context = correctedContext;
1416
+ }
1417
+
1418
+ return proxyOptions;
990
1419
  }
991
-
992
- return proxyOptions;
993
- });
1420
+ );
994
1421
  }
995
1422
  }
996
1423
 
997
- options.proxy = options.proxy.map((item) => {
998
- const getLogLevelForProxy = (level) => {
999
- if (level === "none") {
1000
- return "silent";
1001
- }
1424
+ /** @type {ProxyArray} */
1425
+ (options.proxy) =
1426
+ /** @type {ProxyArray} */
1427
+ (options.proxy).map(
1428
+ /**
1429
+ * @param {HttpProxyMiddlewareOptions} item
1430
+ * @returns {HttpProxyMiddlewareOptions}
1431
+ */
1432
+ (item) => {
1433
+ /**
1434
+ * @param {"info" | "warn" | "error" | "debug" | "silent" | undefined | "none" | "log" | "verbose"} level
1435
+ * @returns {"info" | "warn" | "error" | "debug" | "silent" | undefined}
1436
+ */
1437
+ const getLogLevelForProxy = (level) => {
1438
+ if (level === "none") {
1439
+ return "silent";
1440
+ }
1441
+
1442
+ if (level === "log") {
1443
+ return "info";
1444
+ }
1445
+
1446
+ if (level === "verbose") {
1447
+ return "debug";
1448
+ }
1449
+
1450
+ return level;
1451
+ };
1452
+
1453
+ if (typeof item.logLevel === "undefined") {
1454
+ item.logLevel = getLogLevelForProxy(
1455
+ compilerOptions.infrastructureLogging
1456
+ ? compilerOptions.infrastructureLogging.level
1457
+ : "info"
1458
+ );
1459
+ }
1002
1460
 
1003
- if (level === "log") {
1004
- return "info";
1005
- }
1461
+ if (typeof item.logProvider === "undefined") {
1462
+ item.logProvider = () => this.logger;
1463
+ }
1006
1464
 
1007
- if (level === "verbose") {
1008
- return "debug";
1465
+ return item;
1009
1466
  }
1010
-
1011
- return level;
1012
- };
1013
-
1014
- if (typeof item.logLevel === "undefined") {
1015
- item.logLevel = getLogLevelForProxy(
1016
- compilerOptions.infrastructureLogging
1017
- ? compilerOptions.infrastructureLogging.level
1018
- : "info"
1019
- );
1020
- }
1021
-
1022
- if (typeof item.logProvider === "undefined") {
1023
- item.logProvider = () => this.logger;
1024
- }
1025
-
1026
- return item;
1027
- });
1467
+ );
1028
1468
  }
1029
1469
 
1030
1470
  if (typeof options.setupExitSignals === "undefined") {
@@ -1038,13 +1478,7 @@ class Server {
1038
1478
  } else if (typeof options.static === "string") {
1039
1479
  options.static = [getStaticItem(options.static)];
1040
1480
  } else if (Array.isArray(options.static)) {
1041
- options.static = options.static.map((item) => {
1042
- if (typeof item === "string") {
1043
- return getStaticItem(item);
1044
- }
1045
-
1046
- return getStaticItem(item);
1047
- });
1481
+ options.static = options.static.map((item) => getStaticItem(item));
1048
1482
  } else {
1049
1483
  options.static = [getStaticItem(options.static)];
1050
1484
  }
@@ -1102,38 +1536,61 @@ class Server {
1102
1536
  };
1103
1537
  } else {
1104
1538
  options.webSocketServer = {
1105
- type: options.webSocketServer.type || defaultWebSocketServerType,
1539
+ type:
1540
+ /** @type {WebSocketServerConfiguration} */
1541
+ (options.webSocketServer).type || defaultWebSocketServerType,
1106
1542
  options: {
1107
1543
  ...defaultWebSocketServerOptions,
1108
- ...options.webSocketServer.options,
1544
+ .../** @type {WebSocketServerConfiguration} */
1545
+ (options.webSocketServer).options,
1109
1546
  },
1110
1547
  };
1111
1548
 
1112
- if (typeof options.webSocketServer.options.port === "string") {
1113
- options.webSocketServer.options.port = Number(
1114
- options.webSocketServer.options.port
1115
- );
1549
+ const webSocketServer =
1550
+ /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
1551
+ (options.webSocketServer);
1552
+
1553
+ if (typeof webSocketServer.options.port === "string") {
1554
+ webSocketServer.options.port = Number(webSocketServer.options.port);
1116
1555
  }
1117
1556
  }
1118
1557
  }
1119
1558
 
1559
+ /**
1560
+ * @private
1561
+ * @returns {string}
1562
+ */
1120
1563
  getClientTransport() {
1121
- let ClientImplementation;
1564
+ let clientImplementation;
1122
1565
  let clientImplementationFound = true;
1123
1566
 
1124
1567
  const isKnownWebSocketServerImplementation =
1125
1568
  this.options.webSocketServer &&
1126
- typeof this.options.webSocketServer.type === "string" &&
1569
+ typeof (
1570
+ /** @type {WebSocketServerConfiguration} */
1571
+ (this.options.webSocketServer).type
1572
+ ) === "string" &&
1573
+ // @ts-ignore
1127
1574
  (this.options.webSocketServer.type === "ws" ||
1128
- this.options.webSocketServer.type === "sockjs");
1575
+ /** @type {WebSocketServerConfiguration} */
1576
+ (this.options.webSocketServer).type === "sockjs");
1129
1577
 
1130
1578
  let clientTransport;
1131
1579
 
1132
1580
  if (this.options.client) {
1133
- if (typeof this.options.client.webSocketTransport !== "undefined") {
1134
- clientTransport = this.options.client.webSocketTransport;
1581
+ if (
1582
+ typeof (
1583
+ /** @type {ClientConfiguration} */
1584
+ (this.options.client).webSocketTransport
1585
+ ) !== "undefined"
1586
+ ) {
1587
+ clientTransport =
1588
+ /** @type {ClientConfiguration} */
1589
+ (this.options.client).webSocketTransport;
1135
1590
  } else if (isKnownWebSocketServerImplementation) {
1136
- clientTransport = this.options.webSocketServer.type;
1591
+ clientTransport =
1592
+ /** @type {WebSocketServerConfiguration} */
1593
+ (this.options.webSocketServer).type;
1137
1594
  } else {
1138
1595
  clientTransport = "ws";
1139
1596
  }
@@ -1145,17 +1602,16 @@ class Server {
1145
1602
  case "string":
1146
1603
  // could be 'sockjs', 'ws', or a path that should be required
1147
1604
  if (clientTransport === "sockjs") {
1148
- ClientImplementation = require.resolve(
1605
+ clientImplementation = require.resolve(
1149
1606
  "../client/clients/SockJSClient"
1150
1607
  );
1151
1608
  } else if (clientTransport === "ws") {
1152
- ClientImplementation = require.resolve(
1609
+ clientImplementation = require.resolve(
1153
1610
  "../client/clients/WebSocketClient"
1154
1611
  );
1155
1612
  } else {
1156
1613
  try {
1157
- // eslint-disable-next-line import/no-dynamic-require
1158
- ClientImplementation = require.resolve(clientTransport);
1614
+ clientImplementation = require.resolve(clientTransport);
1159
1615
  } catch (e) {
1160
1616
  clientImplementationFound = false;
1161
1617
  }
@@ -1175,31 +1631,52 @@ class Server {
1175
1631
  );
1176
1632
  }
1177
1633
 
1178
- return ClientImplementation;
1634
+ return /** @type {string} */ (clientImplementation);
1179
1635
  }
1180
1636
 
1637
+ /**
1638
+ * @private
1639
+ * @returns {string}
1640
+ */
1181
1641
  getServerTransport() {
1182
1642
  let implementation;
1183
1643
  let implementationFound = true;
1184
1644
 
1185
- switch (typeof this.options.webSocketServer.type) {
1645
+ switch (
1646
+ typeof (
1647
+ /** @type {WebSocketServerConfiguration} */
1648
+ (this.options.webSocketServer).type
1649
+ )
1650
+ ) {
1186
1651
  case "string":
1187
1652
  // Could be 'sockjs', in the future 'ws', or a path that should be required
1188
- if (this.options.webSocketServer.type === "sockjs") {
1653
+ if (
1654
+ /** @type {WebSocketServerConfiguration} */ (
1655
+ this.options.webSocketServer
1656
+ ).type === "sockjs"
1657
+ ) {
1189
1658
  implementation = require("./servers/SockJSServer");
1190
- } else if (this.options.webSocketServer.type === "ws") {
1659
+ } else if (
1660
+ /** @type {WebSocketServerConfiguration} */ (
1661
+ this.options.webSocketServer
1662
+ ).type === "ws"
1663
+ ) {
1191
1664
  implementation = require("./servers/WebsocketServer");
1192
1665
  } else {
1193
1666
  try {
1194
1667
  // eslint-disable-next-line import/no-dynamic-require
1195
- implementation = require(this.options.webSocketServer.type);
1668
+ implementation = require(/** @type {WebSocketServerConfiguration} */ (
1669
+ this.options.webSocketServer
1670
+ ).type);
1196
1671
  } catch (error) {
1197
1672
  implementationFound = false;
1198
1673
  }
1199
1674
  }
1200
1675
  break;
1201
1676
  case "function":
1202
- implementation = this.options.webSocketServer.type;
1677
+ implementation = /** @type {WebSocketServerConfiguration} */ (
1678
+ this.options.webSocketServer
1679
+ ).type;
1203
1680
  break;
1204
1681
  default:
1205
1682
  implementationFound = false;
@@ -1216,37 +1693,61 @@ class Server {
1216
1693
  return implementation;
1217
1694
  }
1218
1695
 
1696
+ /**
1697
+ * @private
1698
+ * @returns {void}
1699
+ */
1219
1700
  setupProgressPlugin() {
1220
- const { ProgressPlugin } = this.compiler.webpack || require("webpack");
1221
-
1222
- new ProgressPlugin((percent, msg, addInfo, pluginName) => {
1223
- percent = Math.floor(percent * 100);
1701
+ const { ProgressPlugin } =
1702
+ /** @type {MultiCompiler}*/
1703
+ (this.compiler).compilers
1704
+ ? /** @type {MultiCompiler}*/ (this.compiler).compilers[0].webpack
1705
+ : /** @type {Compiler}*/ (this.compiler).webpack ||
1706
+ // TODO remove me after drop webpack v4
1707
+ require("webpack");
1708
+
1709
+ new ProgressPlugin(
1710
+ /**
1711
+ * @param {number} percent
1712
+ * @param {string} msg
1713
+ * @param {string} addInfo
1714
+ * @param {string} pluginName
1715
+ */
1716
+ (percent, msg, addInfo, pluginName) => {
1717
+ percent = Math.floor(percent * 100);
1224
1718
 
1225
- if (percent === 100) {
1226
- msg = "Compilation completed";
1227
- }
1719
+ if (percent === 100) {
1720
+ msg = "Compilation completed";
1721
+ }
1228
1722
 
1229
- if (addInfo) {
1230
- msg = `${msg} (${addInfo})`;
1231
- }
1723
+ if (addInfo) {
1724
+ msg = `${msg} (${addInfo})`;
1725
+ }
1232
1726
 
1233
- if (this.webSocketServer) {
1234
- this.sendMessage(this.webSocketServer.clients, "progress-update", {
1235
- percent,
1236
- msg,
1237
- pluginName,
1238
- });
1239
- }
1727
+ if (this.webSocketServer) {
1728
+ this.sendMessage(this.webSocketServer.clients, "progress-update", {
1729
+ percent,
1730
+ msg,
1731
+ pluginName,
1732
+ });
1733
+ }
1240
1734
 
1241
- if (this.server) {
1242
- this.server.emit("progress-update", { percent, msg, pluginName });
1735
+ if (this.server) {
1736
+ this.server.emit("progress-update", { percent, msg, pluginName });
1737
+ }
1243
1738
  }
1244
- }).apply(this.compiler);
1739
+ ).apply(this.compiler);
1245
1740
  }
1246
1741
 
1742
+ /**
1743
+ * @private
1744
+ * @returns {Promise<void>}
1745
+ */
1247
1746
  async initialize() {
1248
1747
  if (this.options.webSocketServer) {
1249
- const compilers = this.compiler.compilers || [this.compiler];
1748
+ const compilers =
1749
+ /** @type {MultiCompiler} */
1750
+ (this.compiler).compilers || [this.compiler];
1250
1751
 
1251
1752
  compilers.forEach((compiler) => {
1252
1753
  this.addAdditionalEntries(compiler);
@@ -1278,7 +1779,10 @@ class Server {
1278
1779
  }
1279
1780
  });
1280
1781
 
1281
- if (this.options.client && this.options.client.progress) {
1782
+ if (
1783
+ this.options.client &&
1784
+ /** @type {ClientConfiguration} */ (this.options.client).progress
1785
+ ) {
1282
1786
  this.setupProgressPlugin();
1283
1787
  }
1284
1788
  }
@@ -1290,7 +1794,8 @@ class Server {
1290
1794
  // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1291
1795
  this.setupBuiltInRoutes();
1292
1796
  this.setupWatchFiles();
1293
- this.setupFeatures();
1797
+ this.setupWatchStaticFiles();
1798
+ this.setupMiddlewares();
1294
1799
  this.createServer();
1295
1800
 
1296
1801
  if (this.options.setupExitSignals) {
@@ -1301,7 +1806,6 @@ class Server {
1301
1806
  signals.forEach((signal) => {
1302
1807
  const listener = () => {
1303
1808
  if (needForceShutdown) {
1304
- // eslint-disable-next-line no-process-exit
1305
1809
  process.exit();
1306
1810
  }
1307
1811
 
@@ -1314,11 +1818,9 @@ class Server {
1314
1818
  this.stopCallback(() => {
1315
1819
  if (typeof this.compiler.close === "function") {
1316
1820
  this.compiler.close(() => {
1317
- // eslint-disable-next-line no-process-exit
1318
1821
  process.exit();
1319
1822
  });
1320
1823
  } else {
1321
- // eslint-disable-next-line no-process-exit
1322
1824
  process.exit();
1323
1825
  }
1324
1826
  });
@@ -1332,53 +1834,108 @@ class Server {
1332
1834
 
1333
1835
  // Proxy WebSocket without the initial http request
1334
1836
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1335
- this.webSocketProxies.forEach((webSocketProxy) => {
1336
- this.server.on("upgrade", webSocketProxy.upgrade);
1837
+ /** @type {RequestHandler[]} */
1838
+ (this.webSocketProxies).forEach((webSocketProxy) => {
1839
+ /** @type {import("http").Server} */
1840
+ (this.server).on(
1841
+ "upgrade",
1842
+ /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
1843
+ (webSocketProxy).upgrade
1844
+ );
1337
1845
  }, this);
1338
1846
  }
1339
1847
 
1848
+ /**
1849
+ * @private
1850
+ * @returns {void}
1851
+ */
1340
1852
  setupApp() {
1341
- // Init express server
1853
+ /** @type {import("express").Application | undefined}*/
1342
1854
  // eslint-disable-next-line new-cap
1343
- this.app = new express();
1855
+ this.app = new /** @type {any} */ (express)();
1344
1856
  }
1345
1857
 
1858
+ /**
1859
+ * @private
1860
+ * @param {Stats | MultiStats} statsObj
1861
+ * @returns {StatsCompilation}
1862
+ */
1346
1863
  getStats(statsObj) {
1347
1864
  const stats = Server.DEFAULT_STATS;
1348
1865
  const compilerOptions = this.getCompilerOptions();
1349
1866
 
1867
+ // @ts-ignore
1350
1868
  if (compilerOptions.stats && compilerOptions.stats.warningsFilter) {
1869
+ // @ts-ignore
1351
1870
  stats.warningsFilter = compilerOptions.stats.warningsFilter;
1352
1871
  }
1353
1872
 
1354
1873
  return statsObj.toJson(stats);
1355
1874
  }
1356
1875
 
1876
+ /**
1877
+ * @private
1878
+ * @returns {void}
1879
+ */
1357
1880
  setupHooks() {
1358
1881
  this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
1359
1882
  if (this.webSocketServer) {
1360
1883
  this.sendMessage(this.webSocketServer.clients, "invalid");
1361
1884
  }
1362
1885
  });
1363
- this.compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1364
- if (this.webSocketServer) {
1365
- this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1366
- }
1886
+ this.compiler.hooks.done.tap(
1887
+ "webpack-dev-server",
1888
+ /**
1889
+ * @param {Stats | MultiStats} stats
1890
+ */
1891
+ (stats) => {
1892
+ if (this.webSocketServer) {
1893
+ this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1894
+ }
1367
1895
 
1368
- this.stats = stats;
1369
- });
1896
+ /**
1897
+ * @private
1898
+ * @type {Stats | MultiStats}
1899
+ */
1900
+ this.stats = stats;
1901
+ }
1902
+ );
1370
1903
  }
1371
1904
 
1905
+ /**
1906
+ * @private
1907
+ * @returns {void}
1908
+ */
1372
1909
  setupHostHeaderCheck() {
1373
- this.app.all("*", (req, res, next) => {
1374
- if (this.checkHeader(req.headers, "host")) {
1375
- return next();
1376
- }
1910
+ /** @type {import("express").Application} */
1911
+ (this.app).all(
1912
+ "*",
1913
+ /**
1914
+ * @param {Request} req
1915
+ * @param {Response} res
1916
+ * @param {NextFunction} next
1917
+ * @returns {void}
1918
+ */
1919
+ (req, res, next) => {
1920
+ if (
1921
+ this.checkHeader(
1922
+ /** @type {{ [key: string]: string | undefined }} */
1923
+ (req.headers),
1924
+ "host"
1925
+ )
1926
+ ) {
1927
+ return next();
1928
+ }
1377
1929
 
1378
- res.send("Invalid Host header");
1379
- });
1930
+ res.send("Invalid Host header");
1931
+ }
1932
+ );
1380
1933
  }
1381
1934
 
1935
+ /**
1936
+ * @private
1937
+ * @returns {void}
1938
+ */
1382
1939
  setupDevMiddleware() {
1383
1940
  const webpackDevMiddleware = require("webpack-dev-middleware");
1384
1941
 
@@ -1389,435 +1946,616 @@ class Server {
1389
1946
  );
1390
1947
  }
1391
1948
 
1949
+ /**
1950
+ * @private
1951
+ * @returns {void}
1952
+ */
1392
1953
  setupBuiltInRoutes() {
1393
1954
  const { app, middleware } = this;
1394
1955
 
1395
- app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
1396
- res.setHeader("Content-Type", "application/javascript");
1397
-
1398
- const { createReadStream } = fs;
1399
- const clientPath = path.join(__dirname, "..", "client");
1400
-
1401
- createReadStream(
1402
- path.join(clientPath, "modules/sockjs-client/index.js")
1403
- ).pipe(res);
1404
- });
1405
-
1406
- app.get("/webpack-dev-server/invalidate", (_req, res) => {
1407
- this.invalidate();
1408
-
1409
- res.end();
1410
- });
1956
+ /** @type {import("express").Application} */
1957
+ (app).get(
1958
+ "/__webpack_dev_server__/sockjs.bundle.js",
1959
+ /**
1960
+ * @param {Request} req
1961
+ * @param {Response} res
1962
+ * @returns {void}
1963
+ */
1964
+ (req, res) => {
1965
+ res.setHeader("Content-Type", "application/javascript");
1411
1966
 
1412
- app.get("/webpack-dev-server", (req, res) => {
1413
- middleware.waitUntilValid((stats) => {
1414
- res.setHeader("Content-Type", "text/html");
1415
- res.write(
1416
- '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
1417
- );
1967
+ const { createReadStream } = fs;
1968
+ const clientPath = path.join(__dirname, "..", "client");
1418
1969
 
1419
- const statsForPrint =
1420
- typeof stats.stats !== "undefined"
1421
- ? stats.toJson().children
1422
- : [stats.toJson()];
1970
+ createReadStream(
1971
+ path.join(clientPath, "modules/sockjs-client/index.js")
1972
+ ).pipe(res);
1973
+ }
1974
+ );
1423
1975
 
1424
- res.write(`<h1>Assets Report:</h1>`);
1976
+ /** @type {import("express").Application} */
1977
+ (app).get(
1978
+ "/webpack-dev-server/invalidate",
1979
+ /**
1980
+ * @param {Request} _req
1981
+ * @param {Response} res
1982
+ * @returns {void}
1983
+ */
1984
+ (_req, res) => {
1985
+ this.invalidate();
1425
1986
 
1426
- statsForPrint.forEach((item, index) => {
1427
- res.write("<div>");
1987
+ res.end();
1988
+ }
1989
+ );
1428
1990
 
1429
- const name =
1430
- item.name || (stats.stats ? `unnamed[${index}]` : "unnamed");
1991
+ /** @type {import("express").Application} */
1992
+ (app).get(
1993
+ "/webpack-dev-server",
1994
+ /**
1995
+ * @param {Request} req
1996
+ * @param {Response} res
1997
+ * @returns {void}
1998
+ */
1999
+ (req, res) => {
2000
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
2001
+ (middleware).waitUntilValid((stats) => {
2002
+ res.setHeader("Content-Type", "text/html");
2003
+ res.write(
2004
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
2005
+ );
1431
2006
 
1432
- res.write(`<h2>Compilation: ${name}</h2>`);
1433
- res.write("<ul>");
2007
+ const statsForPrint =
2008
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2009
+ ? /** @type {MultiStats} */ (stats).toJson().children
2010
+ : [/** @type {Stats} */ (stats).toJson()];
1434
2011
 
1435
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2012
+ res.write(`<h1>Assets Report:</h1>`);
1436
2013
 
1437
- for (const asset of item.assets) {
1438
- const assetName = asset.name;
1439
- const assetURL = `${publicPath}${assetName}`;
2014
+ /**
2015
+ * @type {StatsCompilation[]}
2016
+ */
2017
+ (statsForPrint).forEach((item, index) => {
2018
+ res.write("<div>");
1440
2019
 
1441
- res.write(
1442
- `<li>
2020
+ const name =
2021
+ // eslint-disable-next-line no-nested-ternary
2022
+ typeof item.name !== "undefined"
2023
+ ? item.name
2024
+ : /** @type {MultiStats} */ (stats).stats
2025
+ ? `unnamed[${index}]`
2026
+ : "unnamed";
2027
+
2028
+ res.write(`<h2>Compilation: ${name}</h2>`);
2029
+ res.write("<ul>");
2030
+
2031
+ const publicPath =
2032
+ item.publicPath === "auto" ? "" : item.publicPath;
2033
+
2034
+ for (const asset of /** @type {NonNullable<StatsCompilation["assets"]>} */ (
2035
+ item.assets
2036
+ )) {
2037
+ const assetName = asset.name;
2038
+ const assetURL = `${publicPath}${assetName}`;
2039
+
2040
+ res.write(
2041
+ `<li>
1443
2042
  <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1444
2043
  </li>`
1445
- );
1446
- }
2044
+ );
2045
+ }
2046
+
2047
+ res.write("</ul>");
2048
+ res.write("</div>");
2049
+ });
1447
2050
 
1448
- res.write("</ul>");
1449
- res.write("</div>");
2051
+ res.end("</body></html>");
1450
2052
  });
2053
+ }
2054
+ );
2055
+ }
1451
2056
 
1452
- res.end("</body></html>");
2057
+ /**
2058
+ * @private
2059
+ * @returns {void}
2060
+ */
2061
+ setupWatchStaticFiles() {
2062
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2063
+ /** @type {NormalizedStatic[]} */
2064
+ (this.options.static).forEach((staticOption) => {
2065
+ if (staticOption.watch) {
2066
+ this.watchFiles(staticOption.directory, staticOption.watch);
2067
+ }
1453
2068
  });
1454
- });
2069
+ }
1455
2070
  }
1456
2071
 
1457
- setupCompressFeature() {
1458
- const compress = require("compression");
2072
+ /**
2073
+ * @private
2074
+ * @returns {void}
2075
+ */
2076
+ setupWatchFiles() {
2077
+ const { watchFiles } = this.options;
1459
2078
 
1460
- this.app.use(compress());
2079
+ if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
2080
+ /** @type {WatchFiles[]} */
2081
+ (watchFiles).forEach((item) => {
2082
+ this.watchFiles(item.paths, item.options);
2083
+ });
2084
+ }
1461
2085
  }
1462
2086
 
1463
- setupProxyFeature() {
1464
- const { createProxyMiddleware } = require("http-proxy-middleware");
1465
-
1466
- const getProxyMiddleware = (proxyConfig) => {
1467
- // It is possible to use the `bypass` method without a `target` or `router`.
1468
- // However, the proxy middleware has no use in this case, and will fail to instantiate.
1469
- if (proxyConfig.target) {
1470
- const context = proxyConfig.context || proxyConfig.path;
1471
-
1472
- return createProxyMiddleware(context, proxyConfig);
1473
- }
1474
-
1475
- if (proxyConfig.router) {
1476
- return createProxyMiddleware(proxyConfig);
1477
- }
1478
- };
2087
+ /**
2088
+ * @private
2089
+ * @returns {void}
2090
+ */
2091
+ setupMiddlewares() {
1479
2092
  /**
1480
- * Assume a proxy configuration specified as:
1481
- * proxy: [
1482
- * {
1483
- * context: "value",
1484
- * ...options,
1485
- * },
1486
- * // or:
1487
- * function() {
1488
- * return {
1489
- * context: "context",
1490
- * ...options,
1491
- * };
1492
- * }
1493
- * ]
2093
+ * @type {Array<Middleware>}
1494
2094
  */
1495
- this.options.proxy.forEach((proxyConfigOrCallback) => {
1496
- let proxyMiddleware;
1497
-
1498
- let proxyConfig =
1499
- typeof proxyConfigOrCallback === "function"
1500
- ? proxyConfigOrCallback()
1501
- : proxyConfigOrCallback;
2095
+ let middlewares = [];
1502
2096
 
1503
- proxyMiddleware = getProxyMiddleware(proxyConfig);
1504
-
1505
- if (proxyConfig.ws) {
1506
- this.webSocketProxies.push(proxyMiddleware);
1507
- }
2097
+ // compress is placed last and uses unshift so that it will be the first middleware used
2098
+ if (this.options.compress) {
2099
+ const compression = require("compression");
1508
2100
 
1509
- const handle = async (req, res, next) => {
1510
- if (typeof proxyConfigOrCallback === "function") {
1511
- const newProxyConfig = proxyConfigOrCallback(req, res, next);
2101
+ middlewares.push({ name: "compression", middleware: compression() });
2102
+ }
1512
2103
 
1513
- if (newProxyConfig !== proxyConfig) {
1514
- proxyConfig = newProxyConfig;
1515
- proxyMiddleware = getProxyMiddleware(proxyConfig);
1516
- }
1517
- }
2104
+ if (typeof this.options.onBeforeSetupMiddleware === "function") {
2105
+ this.options.onBeforeSetupMiddleware(this);
2106
+ }
1518
2107
 
1519
- // - Check if we have a bypass function defined
1520
- // - In case the bypass function is defined we'll retrieve the
1521
- // bypassUrl from it otherwise bypassUrl would be null
1522
- // TODO remove in the next major in favor `context` and `router` options
1523
- const isByPassFuncDefined = typeof proxyConfig.bypass === "function";
1524
- const bypassUrl = isByPassFuncDefined
1525
- ? await proxyConfig.bypass(req, res, proxyConfig)
1526
- : null;
1527
-
1528
- if (typeof bypassUrl === "boolean") {
1529
- // skip the proxy
1530
- req.url = null;
1531
- next();
1532
- } else if (typeof bypassUrl === "string") {
1533
- // byPass to that url
1534
- req.url = bypassUrl;
1535
- next();
1536
- } else if (proxyMiddleware) {
1537
- return proxyMiddleware(req, res, next);
1538
- } else {
1539
- next();
1540
- }
1541
- };
2108
+ if (typeof this.options.headers !== "undefined") {
2109
+ middlewares.push({
2110
+ name: "set-headers",
2111
+ path: "*",
2112
+ middleware: this.setHeaders.bind(this),
2113
+ });
2114
+ }
1542
2115
 
1543
- this.app.use(handle);
1544
- // Also forward error requests to the proxy so it can handle them.
1545
- this.app.use((error, req, res, next) => handle(req, res, next));
2116
+ middlewares.push({
2117
+ name: "webpack-dev-middleware",
2118
+ middleware:
2119
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2120
+ (this.middleware),
1546
2121
  });
1547
- }
1548
2122
 
1549
- setupHistoryApiFallbackFeature() {
1550
- const { historyApiFallback } = this.options;
2123
+ if (this.options.proxy) {
2124
+ const { createProxyMiddleware } = require("http-proxy-middleware");
1551
2125
 
1552
- if (
1553
- typeof historyApiFallback.logger === "undefined" &&
1554
- !historyApiFallback.verbose
1555
- ) {
1556
- historyApiFallback.logger = this.logger.log.bind(
1557
- this.logger,
1558
- "[connect-history-api-fallback]"
1559
- );
1560
- }
2126
+ /**
2127
+ * @param {ProxyConfigArray} proxyConfig
2128
+ * @returns {RequestHandler | undefined}
2129
+ */
2130
+ const getProxyMiddleware = (proxyConfig) => {
2131
+ // It is possible to use the `bypass` method without a `target` or `router`.
2132
+ // However, the proxy middleware has no use in this case, and will fail to instantiate.
2133
+ if (proxyConfig.target) {
2134
+ const context = proxyConfig.context || proxyConfig.path;
2135
+
2136
+ return createProxyMiddleware(
2137
+ /** @type {string} */ (context),
2138
+ proxyConfig
2139
+ );
2140
+ }
1561
2141
 
1562
- // Fall back to /index.html if nothing else matches.
1563
- this.app.use(require("connect-history-api-fallback")(historyApiFallback));
1564
- }
2142
+ if (proxyConfig.router) {
2143
+ return createProxyMiddleware(proxyConfig);
2144
+ }
2145
+ };
1565
2146
 
1566
- setupStaticFeature() {
1567
- this.options.static.forEach((staticOption) => {
1568
- staticOption.publicPath.forEach((publicPath) => {
1569
- this.app.use(
1570
- publicPath,
1571
- express.static(staticOption.directory, staticOption.staticOptions)
1572
- );
1573
- });
1574
- });
1575
- }
2147
+ /**
2148
+ * Assume a proxy configuration specified as:
2149
+ * proxy: [
2150
+ * {
2151
+ * context: "value",
2152
+ * ...options,
2153
+ * },
2154
+ * // or:
2155
+ * function() {
2156
+ * return {
2157
+ * context: "context",
2158
+ * ...options,
2159
+ * };
2160
+ * }
2161
+ * ]
2162
+ */
2163
+ /** @type {ProxyArray} */
2164
+ (this.options.proxy).forEach(
2165
+ /**
2166
+ * @param {any} proxyConfigOrCallback
2167
+ */
2168
+ (proxyConfigOrCallback) => {
2169
+ /**
2170
+ * @type {RequestHandler}
2171
+ */
2172
+ let proxyMiddleware;
2173
+
2174
+ let proxyConfig =
2175
+ typeof proxyConfigOrCallback === "function"
2176
+ ? proxyConfigOrCallback()
2177
+ : proxyConfigOrCallback;
2178
+
2179
+ proxyMiddleware =
2180
+ /** @type {RequestHandler} */
2181
+ (getProxyMiddleware(proxyConfig));
2182
+
2183
+ if (proxyConfig.ws) {
2184
+ this.webSocketProxies.push(proxyMiddleware);
2185
+ }
1576
2186
 
1577
- setupStaticServeIndexFeature() {
1578
- const serveIndex = require("serve-index");
2187
+ /**
2188
+ * @param {Request} req
2189
+ * @param {Response} res
2190
+ * @param {NextFunction} next
2191
+ * @returns {Promise<void>}
2192
+ */
2193
+ const handler = async (req, res, next) => {
2194
+ if (typeof proxyConfigOrCallback === "function") {
2195
+ const newProxyConfig = proxyConfigOrCallback(req, res, next);
2196
+
2197
+ if (newProxyConfig !== proxyConfig) {
2198
+ proxyConfig = newProxyConfig;
2199
+ proxyMiddleware =
2200
+ /** @type {RequestHandler} */
2201
+ (getProxyMiddleware(proxyConfig));
2202
+ }
2203
+ }
1579
2204
 
1580
- this.options.static.forEach((staticOption) => {
1581
- staticOption.publicPath.forEach((publicPath) => {
1582
- if (staticOption.serveIndex) {
1583
- this.app.use(publicPath, (req, res, next) => {
1584
- // serve-index doesn't fallthrough non-get/head request to next middleware
1585
- if (req.method !== "GET" && req.method !== "HEAD") {
1586
- return next();
2205
+ // - Check if we have a bypass function defined
2206
+ // - In case the bypass function is defined we'll retrieve the
2207
+ // bypassUrl from it otherwise bypassUrl would be null
2208
+ // TODO remove in the next major in favor `context` and `router` options
2209
+ const isByPassFuncDefined =
2210
+ typeof proxyConfig.bypass === "function";
2211
+ const bypassUrl = isByPassFuncDefined
2212
+ ? await proxyConfig.bypass(req, res, proxyConfig)
2213
+ : null;
2214
+
2215
+ if (typeof bypassUrl === "boolean") {
2216
+ // skip the proxy
2217
+ // @ts-ignore
2218
+ req.url = null;
2219
+ next();
2220
+ } else if (typeof bypassUrl === "string") {
2221
+ // byPass to that url
2222
+ req.url = bypassUrl;
2223
+ next();
2224
+ } else if (proxyMiddleware) {
2225
+ return proxyMiddleware(req, res, next);
2226
+ } else {
2227
+ next();
1587
2228
  }
2229
+ };
1588
2230
 
1589
- serveIndex(staticOption.directory, staticOption.serveIndex)(
1590
- req,
1591
- res,
1592
- next
1593
- );
2231
+ middlewares.push({
2232
+ name: "http-proxy-middleware",
2233
+ middleware: handler,
2234
+ });
2235
+ // Also forward error requests to the proxy so it can handle them.
2236
+ middlewares.push({
2237
+ name: "http-proxy-middleware-error-handler",
2238
+ middleware:
2239
+ /**
2240
+ * @param {Error} error
2241
+ * @param {Request} req
2242
+ * @param {Response} res
2243
+ * @param {NextFunction} next
2244
+ * @returns {any}
2245
+ */
2246
+ (error, req, res, next) => handler(req, res, next),
1594
2247
  });
1595
2248
  }
1596
- });
1597
- });
1598
- }
1599
-
1600
- setupStaticWatchFeature() {
1601
- this.options.static.forEach((staticOption) => {
1602
- if (staticOption.watch) {
1603
- this.watchFiles(staticOption.directory, staticOption.watch);
1604
- }
1605
- });
1606
- }
1607
-
1608
- setupOnBeforeSetupMiddlewareFeature() {
1609
- this.options.onBeforeSetupMiddleware(this);
1610
- }
1611
-
1612
- setupWatchFiles() {
1613
- const { watchFiles } = this.options;
2249
+ );
1614
2250
 
1615
- if (watchFiles.length > 0) {
1616
- watchFiles.forEach((item) => {
1617
- this.watchFiles(item.paths, item.options);
2251
+ middlewares.push({
2252
+ name: "webpack-dev-middleware",
2253
+ middleware:
2254
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2255
+ (this.middleware),
1618
2256
  });
1619
2257
  }
1620
- }
1621
-
1622
- setupMiddleware() {
1623
- this.app.use(this.middleware);
1624
- }
1625
-
1626
- setupOnAfterSetupMiddlewareFeature() {
1627
- this.options.onAfterSetupMiddleware(this);
1628
- }
1629
-
1630
- setupHeadersFeature() {
1631
- this.app.all("*", this.setHeaders.bind(this));
1632
- }
1633
-
1634
- setupMagicHtmlFeature() {
1635
- this.app.get("*", this.serveMagicHtml.bind(this));
1636
- }
1637
-
1638
- setupFeatures() {
1639
- const features = {
1640
- compress: () => {
1641
- if (this.options.compress) {
1642
- this.setupCompressFeature();
1643
- }
1644
- },
1645
- proxy: () => {
1646
- if (this.options.proxy) {
1647
- this.setupProxyFeature();
1648
- }
1649
- },
1650
- historyApiFallback: () => {
1651
- if (this.options.historyApiFallback) {
1652
- this.setupHistoryApiFallbackFeature();
1653
- }
1654
- },
1655
- static: () => {
1656
- this.setupStaticFeature();
1657
- },
1658
- staticServeIndex: () => {
1659
- this.setupStaticServeIndexFeature();
1660
- },
1661
- staticWatch: () => {
1662
- this.setupStaticWatchFeature();
1663
- },
1664
- onBeforeSetupMiddleware: () => {
1665
- if (typeof this.options.onBeforeSetupMiddleware === "function") {
1666
- this.setupOnBeforeSetupMiddlewareFeature();
1667
- }
1668
- },
1669
- onAfterSetupMiddleware: () => {
1670
- if (typeof this.options.onAfterSetupMiddleware === "function") {
1671
- this.setupOnAfterSetupMiddlewareFeature();
1672
- }
1673
- },
1674
- middleware: () => {
1675
- // include our middleware to ensure
1676
- // it is able to handle '/index.html' request after redirect
1677
- this.setupMiddleware();
1678
- },
1679
- headers: () => {
1680
- this.setupHeadersFeature();
1681
- },
1682
- magicHtml: () => {
1683
- this.setupMagicHtmlFeature();
1684
- },
1685
- };
1686
2258
 
1687
- const runnableFeatures = [];
1688
-
1689
- // compress is placed last and uses unshift so that it will be the first middleware used
1690
- if (this.options.compress) {
1691
- runnableFeatures.push("compress");
1692
- }
1693
-
1694
- if (this.options.onBeforeSetupMiddleware) {
1695
- runnableFeatures.push("onBeforeSetupMiddleware");
2259
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2260
+ /** @type {NormalizedStatic[]} */
2261
+ (this.options.static).forEach((staticOption) => {
2262
+ staticOption.publicPath.forEach((publicPath) => {
2263
+ middlewares.push({
2264
+ name: "express-static",
2265
+ path: publicPath,
2266
+ middleware: express.static(
2267
+ staticOption.directory,
2268
+ staticOption.staticOptions
2269
+ ),
2270
+ });
2271
+ });
2272
+ });
1696
2273
  }
1697
2274
 
1698
- runnableFeatures.push("headers", "middleware");
2275
+ if (this.options.historyApiFallback) {
2276
+ const connectHistoryApiFallback = require("connect-history-api-fallback");
2277
+ const { historyApiFallback } = this.options;
1699
2278
 
1700
- if (this.options.proxy) {
1701
- runnableFeatures.push("proxy", "middleware");
1702
- }
2279
+ if (
2280
+ typeof (
2281
+ /** @type {ConnectHistoryApiFallbackOptions} */
2282
+ (historyApiFallback).logger
2283
+ ) === "undefined" &&
2284
+ !(
2285
+ /** @type {ConnectHistoryApiFallbackOptions} */
2286
+ (historyApiFallback).verbose
2287
+ )
2288
+ ) {
2289
+ // @ts-ignore
2290
+ historyApiFallback.logger = this.logger.log.bind(
2291
+ this.logger,
2292
+ "[connect-history-api-fallback]"
2293
+ );
2294
+ }
1703
2295
 
1704
- if (this.options.static) {
1705
- runnableFeatures.push("static");
1706
- }
2296
+ // Fall back to /index.html if nothing else matches.
2297
+ middlewares.push({
2298
+ name: "connect-history-api-fallback",
2299
+ middleware: connectHistoryApiFallback(
2300
+ /** @type {ConnectHistoryApiFallbackOptions} */
2301
+ (historyApiFallback)
2302
+ ),
2303
+ });
1707
2304
 
1708
- if (this.options.historyApiFallback) {
1709
- runnableFeatures.push("historyApiFallback", "middleware");
2305
+ // include our middleware to ensure
2306
+ // it is able to handle '/index.html' request after redirect
2307
+ middlewares.push({
2308
+ name: "webpack-dev-middleware",
2309
+ middleware:
2310
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2311
+ (this.middleware),
2312
+ });
1710
2313
 
1711
- if (this.options.static) {
1712
- runnableFeatures.push("static");
2314
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2315
+ /** @type {NormalizedStatic[]} */
2316
+ (this.options.static).forEach((staticOption) => {
2317
+ staticOption.publicPath.forEach((publicPath) => {
2318
+ middlewares.push({
2319
+ name: "express-static",
2320
+ path: publicPath,
2321
+ middleware: express.static(
2322
+ staticOption.directory,
2323
+ staticOption.staticOptions
2324
+ ),
2325
+ });
2326
+ });
2327
+ });
1713
2328
  }
1714
2329
  }
1715
2330
 
1716
- if (this.options.static) {
1717
- runnableFeatures.push("staticServeIndex", "staticWatch");
2331
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2332
+ const serveIndex = require("serve-index");
2333
+
2334
+ /** @type {NormalizedStatic[]} */
2335
+ (this.options.static).forEach((staticOption) => {
2336
+ staticOption.publicPath.forEach((publicPath) => {
2337
+ if (staticOption.serveIndex) {
2338
+ middlewares.push({
2339
+ name: "serve-index",
2340
+ path: publicPath,
2341
+ /**
2342
+ * @param {Request} req
2343
+ * @param {Response} res
2344
+ * @param {NextFunction} next
2345
+ * @returns {void}
2346
+ */
2347
+ middleware: (req, res, next) => {
2348
+ // serve-index doesn't fallthrough non-get/head request to next middleware
2349
+ if (req.method !== "GET" && req.method !== "HEAD") {
2350
+ return next();
2351
+ }
2352
+
2353
+ serveIndex(
2354
+ staticOption.directory,
2355
+ /** @type {ServeIndexOptions} */
2356
+ (staticOption.serveIndex)
2357
+ )(req, res, next);
2358
+ },
2359
+ });
2360
+ }
2361
+ });
2362
+ });
1718
2363
  }
1719
2364
 
1720
2365
  if (this.options.magicHtml) {
1721
- runnableFeatures.push("magicHtml");
2366
+ middlewares.push({
2367
+ name: "serve-magic-html",
2368
+ middleware: this.serveMagicHtml.bind(this),
2369
+ });
1722
2370
  }
1723
2371
 
1724
- if (this.options.onAfterSetupMiddleware) {
1725
- runnableFeatures.push("onAfterSetupMiddleware");
2372
+ if (typeof this.options.setupMiddlewares === "function") {
2373
+ middlewares = this.options.setupMiddlewares(middlewares, this);
1726
2374
  }
1727
2375
 
1728
- runnableFeatures.forEach((feature) => {
1729
- features[feature]();
2376
+ middlewares.forEach((middleware) => {
2377
+ if (typeof middleware === "function") {
2378
+ /** @type {import("express").Application} */
2379
+ (this.app).use(middleware);
2380
+ } else if (typeof middleware.path !== "undefined") {
2381
+ /** @type {import("express").Application} */
2382
+ (this.app).use(middleware.path, middleware.middleware);
2383
+ } else {
2384
+ /** @type {import("express").Application} */
2385
+ (this.app).use(middleware.middleware);
2386
+ }
1730
2387
  });
2388
+
2389
+ if (typeof this.options.onAfterSetupMiddleware === "function") {
2390
+ this.options.onAfterSetupMiddleware(this);
2391
+ }
1731
2392
  }
1732
2393
 
2394
+ /**
2395
+ * @private
2396
+ * @returns {void}
2397
+ */
1733
2398
  createServer() {
2399
+ const { type, options } = /** @type {ServerConfiguration} */ (
2400
+ this.options.server
2401
+ );
2402
+
2403
+ /** @type {import("http").Server | undefined | null} */
1734
2404
  // eslint-disable-next-line import/no-dynamic-require
1735
- this.server = require(this.options.server.type).createServer(
1736
- this.options.server.options,
2405
+ this.server = require(/** @type {string} */ (type)).createServer(
2406
+ options,
1737
2407
  this.app
1738
2408
  );
1739
2409
 
1740
- this.server.on("connection", (socket) => {
1741
- // Add socket to list
1742
- this.sockets.push(socket);
2410
+ /** @type {import("http").Server} */
2411
+ (this.server).on(
2412
+ "connection",
2413
+ /**
2414
+ * @param {Socket} socket
2415
+ */
2416
+ (socket) => {
2417
+ // Add socket to list
2418
+ this.sockets.push(socket);
1743
2419
 
1744
- socket.once("close", () => {
1745
- // Remove socket from list
1746
- this.sockets.splice(this.sockets.indexOf(socket), 1);
1747
- });
1748
- });
2420
+ socket.once("close", () => {
2421
+ // Remove socket from list
2422
+ this.sockets.splice(this.sockets.indexOf(socket), 1);
2423
+ });
2424
+ }
2425
+ );
1749
2426
 
1750
- this.server.on("error", (error) => {
1751
- throw error;
1752
- });
2427
+ /** @type {import("http").Server} */
2428
+ (this.server).on(
2429
+ "error",
2430
+ /**
2431
+ * @param {Error} error
2432
+ */
2433
+ (error) => {
2434
+ throw error;
2435
+ }
2436
+ );
1753
2437
  }
1754
2438
 
2439
+ /**
2440
+ * @private
2441
+ * @returns {void}
2442
+ */
1755
2443
  // TODO: remove `--web-socket-server` in favor of `--web-socket-server-type`
1756
2444
  createWebSocketServer() {
1757
- this.webSocketServer = new (this.getServerTransport())(this);
1758
- this.webSocketServer.implementation.on("connection", (client, request) => {
1759
- const headers =
1760
- // eslint-disable-next-line no-nested-ternary
1761
- typeof request !== "undefined"
1762
- ? request.headers
1763
- : typeof client.headers !== "undefined"
1764
- ? client.headers
1765
- : // eslint-disable-next-line no-undefined
1766
- undefined;
1767
-
1768
- if (!headers) {
1769
- this.logger.warn(
1770
- 'webSocketServer implementation must pass headers for the "connection" event'
1771
- );
1772
- }
2445
+ /** @type {WebSocketServerImplementation | undefined | null} */
2446
+ this.webSocketServer = new /** @type {any} */ (this.getServerTransport())(
2447
+ this
2448
+ );
2449
+ /** @type {WebSocketServerImplementation} */
2450
+ (this.webSocketServer).implementation.on(
2451
+ "connection",
2452
+ /**
2453
+ * @param {ClientConnection} client
2454
+ * @param {IncomingMessage} request
2455
+ */
2456
+ (client, request) => {
2457
+ /** @type {{ [key: string]: string | undefined } | undefined} */
2458
+ const headers =
2459
+ // eslint-disable-next-line no-nested-ternary
2460
+ typeof request !== "undefined"
2461
+ ? /** @type {{ [key: string]: string | undefined }} */
2462
+ (request.headers)
2463
+ : typeof (
2464
+ /** @type {import("sockjs").Connection} */ (client).headers
2465
+ ) !== "undefined"
2466
+ ? /** @type {import("sockjs").Connection} */ (client).headers
2467
+ : // eslint-disable-next-line no-undefined
2468
+ undefined;
2469
+
2470
+ if (!headers) {
2471
+ this.logger.warn(
2472
+ 'webSocketServer implementation must pass headers for the "connection" event'
2473
+ );
2474
+ }
1773
2475
 
1774
- if (
1775
- !headers ||
1776
- !this.checkHeader(headers, "host") ||
1777
- !this.checkHeader(headers, "origin")
1778
- ) {
1779
- this.sendMessage([client], "error", "Invalid Host/Origin header");
2476
+ if (
2477
+ !headers ||
2478
+ !this.checkHeader(headers, "host") ||
2479
+ !this.checkHeader(headers, "origin")
2480
+ ) {
2481
+ this.sendMessage([client], "error", "Invalid Host/Origin header");
1780
2482
 
1781
- // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
1782
- // Terminate would prevent it sending, so use close to allow it to be sent
1783
- client.close();
2483
+ // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
2484
+ // Terminate would prevent it sending, so use close to allow it to be sent
2485
+ client.close();
1784
2486
 
1785
- return;
1786
- }
2487
+ return;
2488
+ }
1787
2489
 
1788
- if (this.options.hot === true || this.options.hot === "only") {
1789
- this.sendMessage([client], "hot");
1790
- }
2490
+ if (this.options.hot === true || this.options.hot === "only") {
2491
+ this.sendMessage([client], "hot");
2492
+ }
1791
2493
 
1792
- if (this.options.liveReload) {
1793
- this.sendMessage([client], "liveReload");
1794
- }
2494
+ if (this.options.liveReload) {
2495
+ this.sendMessage([client], "liveReload");
2496
+ }
1795
2497
 
1796
- if (this.options.client && this.options.client.progress) {
1797
- this.sendMessage([client], "progress", this.options.client.progress);
1798
- }
2498
+ if (
2499
+ this.options.client &&
2500
+ /** @type {ClientConfiguration} */
2501
+ (this.options.client).progress
2502
+ ) {
2503
+ this.sendMessage(
2504
+ [client],
2505
+ "progress",
2506
+ /** @type {ClientConfiguration} */
2507
+ (this.options.client).progress
2508
+ );
2509
+ }
1799
2510
 
1800
- if (this.options.client && this.options.client.reconnect) {
1801
- this.sendMessage([client], "reconnect", this.options.client.reconnect);
1802
- }
2511
+ if (
2512
+ this.options.client &&
2513
+ /** @type {ClientConfiguration} */ (this.options.client).reconnect
2514
+ ) {
2515
+ this.sendMessage(
2516
+ [client],
2517
+ "reconnect",
2518
+ /** @type {ClientConfiguration} */
2519
+ (this.options.client).reconnect
2520
+ );
2521
+ }
1803
2522
 
1804
- if (this.options.client && this.options.client.overlay) {
1805
- this.sendMessage([client], "overlay", this.options.client.overlay);
1806
- }
2523
+ if (
2524
+ this.options.client &&
2525
+ /** @type {ClientConfiguration} */
2526
+ (this.options.client).overlay
2527
+ ) {
2528
+ this.sendMessage(
2529
+ [client],
2530
+ "overlay",
2531
+ /** @type {ClientConfiguration} */
2532
+ (this.options.client).overlay
2533
+ );
2534
+ }
1807
2535
 
1808
- if (!this.stats) {
1809
- return;
1810
- }
2536
+ if (!this.stats) {
2537
+ return;
2538
+ }
1811
2539
 
1812
- this.sendStats([client], this.getStats(this.stats), true);
1813
- });
2540
+ this.sendStats([client], this.getStats(this.stats), true);
2541
+ }
2542
+ );
1814
2543
  }
1815
2544
 
2545
+ /**
2546
+ * @private
2547
+ * @param {string} defaultOpenTarget
2548
+ * @returns {void}
2549
+ */
1816
2550
  openBrowser(defaultOpenTarget) {
1817
2551
  const open = require("open");
1818
2552
 
1819
2553
  Promise.all(
1820
- this.options.open.map((item) => {
2554
+ /** @type {NormalizedOpen[]} */
2555
+ (this.options.open).map((item) => {
2556
+ /**
2557
+ * @type {string}
2558
+ */
1821
2559
  let openTarget;
1822
2560
 
1823
2561
  if (item.target === "<url>") {
@@ -1831,13 +2569,17 @@ class Server {
1831
2569
  return open(openTarget, item.options).catch(() => {
1832
2570
  this.logger.warn(
1833
2571
  `Unable to open "${openTarget}" page${
1834
- // eslint-disable-next-line no-nested-ternary
1835
2572
  item.options.app
1836
- ? ` in "${item.options.app.name}" app${
1837
- item.options.app.arguments
1838
- ? ` with "${item.options.app.arguments.join(
1839
- " "
1840
- )}" arguments`
2573
+ ? ` in "${
2574
+ /** @type {import("open").App} */
2575
+ (item.options.app).name
2576
+ }" app${
2577
+ /** @type {import("open").App} */
2578
+ (item.options.app).arguments
2579
+ ? ` with "${
2580
+ /** @type {import("open").App} */
2581
+ (item.options.app).arguments.join(" ")
2582
+ }" arguments`
1841
2583
  : ""
1842
2584
  }`
1843
2585
  : ""
@@ -1848,38 +2590,68 @@ class Server {
1848
2590
  );
1849
2591
  }
1850
2592
 
1851
- stopBonjour(callback = () => {}) {
1852
- this.bonjour.unpublishAll(() => {
1853
- this.bonjour.destroy();
1854
-
1855
- if (callback) {
1856
- callback();
1857
- }
1858
- });
1859
- }
1860
-
2593
+ /**
2594
+ * @private
2595
+ * @returns {void}
2596
+ */
1861
2597
  runBonjour() {
2598
+ /**
2599
+ * @private
2600
+ * @type {import("bonjour").Bonjour | undefined}
2601
+ */
1862
2602
  this.bonjour = require("bonjour")();
1863
2603
  this.bonjour.publish({
1864
2604
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
1865
- port: this.options.port,
1866
- type: this.options.server.type === "http" ? "http" : "https",
2605
+ port: /** @type {number} */ (this.options.port),
2606
+ type:
2607
+ /** @type {ServerConfiguration} */
2608
+ (this.options.server).type === "http" ? "http" : "https",
1867
2609
  subtypes: ["webpack"],
1868
- ...this.options.bonjour,
2610
+ .../** @type {BonjourOptions} */ (this.options.bonjour),
2611
+ });
2612
+ }
2613
+
2614
+ /**
2615
+ * @private
2616
+ * @returns {void}
2617
+ */
2618
+ stopBonjour(callback = () => {}) {
2619
+ /** @type {Bonjour} */
2620
+ (this.bonjour).unpublishAll(() => {
2621
+ /** @type {Bonjour} */
2622
+ (this.bonjour).destroy();
2623
+
2624
+ if (callback) {
2625
+ callback();
2626
+ }
1869
2627
  });
1870
2628
  }
1871
2629
 
2630
+ /**
2631
+ * @private
2632
+ * @returns {void}
2633
+ */
1872
2634
  logStatus() {
1873
2635
  const { isColorSupported, cyan, red } = require("colorette");
1874
2636
 
2637
+ /**
2638
+ * @param {Compiler["options"]} compilerOptions
2639
+ * @returns {boolean}
2640
+ */
1875
2641
  const getColorsOption = (compilerOptions) => {
2642
+ /**
2643
+ * @type {boolean}
2644
+ */
1876
2645
  let colorsEnabled;
1877
2646
 
1878
2647
  if (
1879
2648
  compilerOptions.stats &&
1880
- typeof compilerOptions.stats.colors !== "undefined"
2649
+ typeof (/** @type {StatsOptions} */ (compilerOptions.stats).colors) !==
2650
+ "undefined"
1881
2651
  ) {
1882
- colorsEnabled = compilerOptions.stats;
2652
+ colorsEnabled =
2653
+ /** @type {boolean} */
2654
+ (/** @type {StatsOptions} */ (compilerOptions.stats).colors);
1883
2655
  } else {
1884
2656
  colorsEnabled = isColorSupported;
1885
2657
  }
@@ -1888,6 +2660,11 @@ class Server {
1888
2660
  };
1889
2661
 
1890
2662
  const colors = {
2663
+ /**
2664
+ * @param {boolean} useColor
2665
+ * @param {string} msg
2666
+ * @returns {string}
2667
+ */
1891
2668
  info(useColor, msg) {
1892
2669
  if (useColor) {
1893
2670
  return cyan(msg);
@@ -1895,6 +2672,11 @@ class Server {
1895
2672
 
1896
2673
  return msg;
1897
2674
  },
2675
+ /**
2676
+ * @param {boolean} useColor
2677
+ * @param {string} msg
2678
+ * @returns {string}
2679
+ */
1898
2680
  error(useColor, msg) {
1899
2681
  if (useColor) {
1900
2682
  return red(msg);
@@ -1906,10 +2688,26 @@ class Server {
1906
2688
  const useColor = getColorsOption(this.getCompilerOptions());
1907
2689
 
1908
2690
  if (this.options.ipc) {
1909
- this.logger.info(`Project is running at: "${this.server.address()}"`);
2691
+ this.logger.info(
2692
+ `Project is running at: "${
2693
+ /** @type {import("http").Server} */
2694
+ (this.server).address()
2695
+ }"`
2696
+ );
1910
2697
  } else {
1911
- const protocol = this.options.server.type === "http" ? "http" : "https";
1912
- const { address, port } = this.server.address();
2698
+ const protocol =
2699
+ /** @type {ServerConfiguration} */
2700
+ (this.options.server).type === "http" ? "http" : "https";
2701
+ const { address, port } =
2702
+ /** @type {import("net").AddressInfo} */
2703
+ (
2704
+ /** @type {import("http").Server} */
2705
+ (this.server).address()
2706
+ );
2707
+ /**
2708
+ * @param {string} newHostname
2709
+ * @returns {string}
2710
+ */
1913
2711
  const prettyPrintURL = (newHostname) =>
1914
2712
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
1915
2713
 
@@ -1962,8 +2760,13 @@ class Server {
1962
2760
  }
1963
2761
  } else {
1964
2762
  networkUrlIPv4 =
1965
- parsedIP.kind() === "ipv6" && parsedIP.isIPv4MappedAddress()
1966
- ? prettyPrintURL(parsedIP.toIPv4Address().toString())
2763
+ parsedIP.kind() === "ipv6" &&
2764
+ /** @type {IPv6} */
2765
+ (parsedIP).isIPv4MappedAddress()
2766
+ ? prettyPrintURL(
2767
+ /** @type {IPv6} */
2768
+ (parsedIP).toIPv4Address().toString()
2769
+ )
1967
2770
  : prettyPrintURL(address);
1968
2771
 
1969
2772
  if (parsedIP.kind() === "ipv6") {
@@ -1978,10 +2781,19 @@ class Server {
1978
2781
  }
1979
2782
 
1980
2783
  if (localhost || loopbackIPv4 || loopbackIPv6) {
1981
- const loopbacks = []
1982
- .concat(localhost ? [colors.info(useColor, localhost)] : [])
1983
- .concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
1984
- .concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
2784
+ const loopbacks = [];
2785
+
2786
+ if (localhost) {
2787
+ loopbacks.push([colors.info(useColor, localhost)]);
2788
+ }
2789
+
2790
+ if (loopbackIPv4) {
2791
+ loopbacks.push([colors.info(useColor, loopbackIPv4)]);
2792
+ }
2793
+
2794
+ if (loopbackIPv6) {
2795
+ loopbacks.push([colors.info(useColor, loopbackIPv6)]);
2796
+ }
1985
2797
 
1986
2798
  this.logger.info(`Loopback: ${loopbacks.join(", ")}`);
1987
2799
  }
@@ -1998,18 +2810,19 @@ class Server {
1998
2810
  );
1999
2811
  }
2000
2812
 
2001
- if (this.options.open.length > 0) {
2813
+ if (/** @type {NormalizedOpen[]} */ (this.options.open).length > 0) {
2002
2814
  const openTarget = prettyPrintURL(this.options.host || "localhost");
2003
2815
 
2004
2816
  this.openBrowser(openTarget);
2005
2817
  }
2006
2818
  }
2007
2819
 
2008
- if (this.options.static && this.options.static.length > 0) {
2820
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2009
2821
  this.logger.info(
2010
2822
  `Content not from webpack is served from '${colors.info(
2011
2823
  useColor,
2012
- this.options.static
2824
+ /** @type {NormalizedStatic[]} */
2825
+ (this.options.static)
2013
2826
  .map((staticOption) => staticOption.directory)
2014
2827
  .join(", ")
2015
2828
  )}' directory`
@@ -2020,14 +2833,19 @@ class Server {
2020
2833
  this.logger.info(
2021
2834
  `404s will fallback to '${colors.info(
2022
2835
  useColor,
2023
- this.options.historyApiFallback.index || "/index.html"
2836
+ /** @type {ConnectHistoryApiFallbackOptions} */ (
2837
+ this.options.historyApiFallback
2838
+ ).index || "/index.html"
2024
2839
  )}'`
2025
2840
  );
2026
2841
  }
2027
2842
 
2028
2843
  if (this.options.bonjour) {
2029
2844
  const bonjourProtocol =
2030
- this.options.bonjour.type || this.options.server.type === "http"
2845
+ /** @type {BonjourOptions} */
2846
+ (this.options.bonjour).type ||
2847
+ /** @type {ServerConfiguration} */
2848
+ (this.options.server).type === "http"
2031
2849
  ? "http"
2032
2850
  : "https";
2033
2851
 
@@ -2037,32 +2855,59 @@ class Server {
2037
2855
  }
2038
2856
  }
2039
2857
 
2858
+ /**
2859
+ * @private
2860
+ * @param {Request} req
2861
+ * @param {Response} res
2862
+ * @param {NextFunction} next
2863
+ */
2040
2864
  setHeaders(req, res, next) {
2041
2865
  let { headers } = this.options;
2042
2866
 
2043
2867
  if (headers) {
2044
2868
  if (typeof headers === "function") {
2045
- headers = headers(req, res, this.middleware.context);
2869
+ headers = headers(
2870
+ req,
2871
+ res,
2872
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
2873
+ (this.middleware).context
2874
+ );
2046
2875
  }
2047
2876
 
2877
+ /**
2878
+ * @type {{key: string, value: string}[]}
2879
+ */
2048
2880
  const allHeaders = [];
2049
2881
 
2050
2882
  if (!Array.isArray(headers)) {
2051
2883
  // eslint-disable-next-line guard-for-in
2052
2884
  for (const name in headers) {
2885
+ // @ts-ignore
2053
2886
  allHeaders.push({ key: name, value: headers[name] });
2054
2887
  }
2888
+
2055
2889
  headers = allHeaders;
2056
2890
  }
2057
2891
 
2058
- headers.forEach((header) => {
2059
- res.setHeader(header.key, header.value);
2060
- });
2892
+ headers.forEach(
2893
+ /**
2894
+ * @param {{key: string, value: any}} header
2895
+ */
2896
+ (header) => {
2897
+ res.setHeader(header.key, header.value);
2898
+ }
2899
+ );
2061
2900
  }
2062
2901
 
2063
2902
  next();
2064
2903
  }
2065
2904
 
2905
+ /**
2906
+ * @private
2907
+ * @param {{ [key: string]: string | undefined }} headers
2908
+ * @param {string} headerToCheck
2909
+ * @returns {boolean}
2910
+ */
2066
2911
  checkHeader(headers, headerToCheck) {
2067
2912
  // allow user to opt out of this security check, at their own risk
2068
2913
  // by explicitly enabling allowedHosts
@@ -2099,8 +2944,8 @@ class Server {
2099
2944
  // always allow localhost host, for convenience (hostname === 'localhost')
2100
2945
  // allow hostname of listening address (hostname === this.options.host)
2101
2946
  const isValidHostname =
2102
- ipaddr.IPv4.isValid(hostname) ||
2103
- ipaddr.IPv6.isValid(hostname) ||
2947
+ (hostname !== null && ipaddr.IPv4.isValid(hostname)) ||
2948
+ (hostname !== null && ipaddr.IPv6.isValid(hostname)) ||
2104
2949
  hostname === "localhost" ||
2105
2950
  hostname === this.options.host;
2106
2951
 
@@ -2127,7 +2972,7 @@ class Server {
2127
2972
  // "*.example.com" (hostname.endsWith(allowedHost))
2128
2973
  if (
2129
2974
  hostname === allowedHost.substring(1) ||
2130
- hostname.endsWith(allowedHost)
2975
+ /** @type {string} */ (hostname).endsWith(allowedHost)
2131
2976
  ) {
2132
2977
  return true;
2133
2978
  }
@@ -2138,15 +2983,27 @@ class Server {
2138
2983
  // Also allow if `client.webSocketURL.hostname` provided
2139
2984
  if (
2140
2985
  this.options.client &&
2141
- typeof this.options.client.webSocketURL !== "undefined"
2986
+ typeof (
2987
+ /** @type {ClientConfiguration} */ (this.options.client).webSocketURL
2988
+ ) !== "undefined"
2142
2989
  ) {
2143
- return this.options.client.webSocketURL.hostname === hostname;
2990
+ return (
2991
+ /** @type {WebSocketURL} */
2992
+ (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
2993
+ .hostname === hostname
2994
+ );
2144
2995
  }
2145
2996
 
2146
2997
  // disallow
2147
2998
  return false;
2148
2999
  }
2149
3000
 
3001
+ /**
3002
+ * @param {ClientConnection[]} clients
3003
+ * @param {string} type
3004
+ * @param {any} [data]
3005
+ * @param {any} [params]
3006
+ */
2150
3007
  // eslint-disable-next-line class-methods-use-this
2151
3008
  sendMessage(clients, type, data, params) {
2152
3009
  for (const client of clients) {
@@ -2158,21 +3015,41 @@ class Server {
2158
3015
  }
2159
3016
  }
2160
3017
 
3018
+ /**
3019
+ * @private
3020
+ * @param {Request} req
3021
+ * @param {Response} res
3022
+ * @param {NextFunction} next
3023
+ * @returns {void}
3024
+ */
2161
3025
  serveMagicHtml(req, res, next) {
2162
- this.middleware.waitUntilValid(() => {
3026
+ if (req.method !== "GET" && req.method !== "HEAD") {
3027
+ return next();
3028
+ }
3029
+
3030
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3031
+ (this.middleware).waitUntilValid(() => {
2163
3032
  const _path = req.path;
2164
3033
 
2165
3034
  try {
2166
- const filename = this.middleware.getFilenameFromUrl(`${_path}.js`);
2167
- const isFile = this.middleware.context.outputFileSystem
2168
- .statSync(filename)
2169
- .isFile();
3035
+ const filename =
3036
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3037
+ (this.middleware).getFilenameFromUrl(`${_path}.js`);
3038
+ const isFile =
3039
+ /** @type {Compiler["outputFileSystem"] & { statSync: import("fs").StatSyncFn }}*/
3040
+ (
3041
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3042
+ (this.middleware).context.outputFileSystem
3043
+ )
3044
+ .statSync(/** @type {import("fs").PathLike} */ (filename))
3045
+ .isFile();
2170
3046
 
2171
3047
  if (!isFile) {
2172
3048
  return next();
2173
3049
  }
2174
3050
 
2175
3051
  // Serve a page that executes the javascript
3052
+ // @ts-ignore
2176
3053
  const queries = req._parsedUrl.search || "";
2177
3054
  const responsePage = `<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="${_path}.js${queries}"></script></body></html>`;
2178
3055
 
@@ -2184,6 +3061,12 @@ class Server {
2184
3061
  }
2185
3062
 
2186
3063
  // Send stats to a socket or multiple sockets
3064
+ /**
3065
+ * @private
3066
+ * @param {ClientConnection[]} clients
3067
+ * @param {StatsCompilation} stats
3068
+ * @param {boolean} [force]
3069
+ */
2187
3070
  sendStats(clients, stats, force) {
2188
3071
  const shouldEmit =
2189
3072
  !force &&
@@ -2201,10 +3084,20 @@ class Server {
2201
3084
  this.currentHash = stats.hash;
2202
3085
  this.sendMessage(clients, "hash", stats.hash);
2203
3086
 
2204
- if (stats.errors.length > 0 || stats.warnings.length > 0) {
2205
- const hasErrors = stats.errors.length > 0;
3087
+ if (
3088
+ /** @type {NonNullable<StatsCompilation["errors"]>} */
3089
+ (stats.errors).length > 0 ||
3090
+ /** @type {NonNullable<StatsCompilation["warnings"]>} */
3091
+ (stats.warnings).length > 0
3092
+ ) {
3093
+ const hasErrors =
3094
+ /** @type {NonNullable<StatsCompilation["errors"]>} */
3095
+ (stats.errors).length > 0;
2206
3096
 
2207
- if (stats.warnings.length > 0) {
3097
+ if (
3098
+ /** @type {NonNullable<StatsCompilation["warnings"]>} */
3099
+ (stats.warnings).length > 0
3100
+ ) {
2208
3101
  let params;
2209
3102
 
2210
3103
  if (hasErrors) {
@@ -2214,7 +3107,10 @@ class Server {
2214
3107
  this.sendMessage(clients, "warnings", stats.warnings, params);
2215
3108
  }
2216
3109
 
2217
- if (stats.errors.length > 0) {
3110
+ if (
3111
+ /** @type {NonNullable<StatsCompilation["errors"]>} */ (stats.errors)
3112
+ .length > 0
3113
+ ) {
2218
3114
  this.sendMessage(clients, "errors", stats.errors);
2219
3115
  }
2220
3116
  } else {
@@ -2222,6 +3118,10 @@ class Server {
2222
3118
  }
2223
3119
  }
2224
3120
 
3121
+ /**
3122
+ * @param {string | string[]} watchPath
3123
+ * @param {WatchOptions} [watchOptions]
3124
+ */
2225
3125
  watchFiles(watchPath, watchOptions) {
2226
3126
  const chokidar = require("chokidar");
2227
3127
  const watcher = chokidar.watch(watchPath, watchOptions);
@@ -2242,44 +3142,65 @@ class Server {
2242
3142
  this.staticWatchers.push(watcher);
2243
3143
  }
2244
3144
 
2245
- invalidate(callback) {
3145
+ /**
3146
+ * @param {import("webpack-dev-middleware").Callback} [callback]
3147
+ */
3148
+ invalidate(callback = () => {}) {
2246
3149
  if (this.middleware) {
2247
3150
  this.middleware.invalidate(callback);
2248
3151
  }
2249
3152
  }
2250
3153
 
3154
+ /**
3155
+ * @returns {Promise<void>}
3156
+ */
2251
3157
  async start() {
2252
3158
  await this.normalizeOptions();
2253
3159
 
2254
3160
  if (this.options.ipc) {
2255
- await new Promise((resolve, reject) => {
2256
- const net = require("net");
2257
- const socket = new net.Socket();
3161
+ await /** @type {Promise<void>} */ (
3162
+ new Promise((resolve, reject) => {
3163
+ const net = require("net");
3164
+ const socket = new net.Socket();
3165
+
3166
+ socket.on(
3167
+ "error",
3168
+ /**
3169
+ * @param {Error & { code?: string }} error
3170
+ */
3171
+ (error) => {
3172
+ if (error.code === "ECONNREFUSED") {
3173
+ // No other server listening on this socket so it can be safely removed
3174
+ fs.unlinkSync(/** @type {string} */ (this.options.ipc));
3175
+
3176
+ resolve();
3177
+
3178
+ return;
3179
+ } else if (error.code === "ENOENT") {
3180
+ resolve();
3181
+
3182
+ return;
3183
+ }
2258
3184
 
2259
- socket.on("error", (error) => {
2260
- if (error.code === "ECONNREFUSED") {
2261
- // No other server listening on this socket so it can be safely removed
2262
- fs.unlinkSync(this.options.ipc);
2263
-
2264
- resolve();
2265
-
2266
- return;
2267
- } else if (error.code === "ENOENT") {
2268
- resolve();
2269
-
2270
- return;
2271
- }
2272
-
2273
- reject(error);
2274
- });
3185
+ reject(error);
3186
+ }
3187
+ );
2275
3188
 
2276
- socket.connect({ path: this.options.ipc }, () => {
2277
- throw new Error(`IPC "${this.options.ipc}" is already used`);
2278
- });
2279
- });
3189
+ socket.connect(
3190
+ { path: /** @type {string} */ (this.options.ipc) },
3191
+ () => {
3192
+ throw new Error(`IPC "${this.options.ipc}" is already used`);
3193
+ }
3194
+ );
3195
+ })
3196
+ );
2280
3197
  } else {
2281
- this.options.host = await Server.getHostname(this.options.host);
2282
- this.options.port = await Server.getFreePort(this.options.port);
3198
+ this.options.host = await Server.getHostname(
3199
+ /** @type {Host} */ (this.options.host)
3200
+ );
3201
+ this.options.port = await Server.getFreePort(
3202
+ /** @type {Port} */ (this.options.port)
3203
+ );
2283
3204
  }
2284
3205
 
2285
3206
  await this.initialize();
@@ -2288,17 +3209,23 @@ class Server {
2288
3209
  ? { path: this.options.ipc }
2289
3210
  : { host: this.options.host, port: this.options.port };
2290
3211
 
2291
- await new Promise((resolve) => {
2292
- this.server.listen(listenOptions, () => {
2293
- resolve();
2294
- });
2295
- });
3212
+ await /** @type {Promise<void>} */ (
3213
+ new Promise((resolve) => {
3214
+ /** @type {import("http").Server} */
3215
+ (this.server).listen(listenOptions, () => {
3216
+ resolve();
3217
+ });
3218
+ })
3219
+ );
2296
3220
 
2297
3221
  if (this.options.ipc) {
2298
3222
  // chmod 666 (rw rw rw)
2299
3223
  const READ_WRITE = 438;
2300
3224
 
2301
- await fs.promises.chmod(this.options.ipc, READ_WRITE);
3225
+ await fs.promises.chmod(
3226
+ /** @type {string} */ (this.options.ipc),
3227
+ READ_WRITE
3228
+ );
2302
3229
  }
2303
3230
 
2304
3231
  if (this.options.webSocketServer) {
@@ -2316,19 +3243,27 @@ class Server {
2316
3243
  }
2317
3244
  }
2318
3245
 
3246
+ /**
3247
+ * @param {(err?: Error) => void} [callback]
3248
+ */
2319
3249
  startCallback(callback = () => {}) {
2320
3250
  this.start()
2321
- .then(() => callback(null), callback)
3251
+ .then(() => callback(), callback)
2322
3252
  .catch(callback);
2323
3253
  }
2324
3254
 
3255
+ /**
3256
+ * @returns {Promise<void>}
3257
+ */
2325
3258
  async stop() {
2326
3259
  if (this.bonjour) {
2327
- await new Promise((resolve) => {
2328
- this.stopBonjour(() => {
2329
- resolve();
2330
- });
2331
- });
3260
+ await /** @type {Promise<void>} */ (
3261
+ new Promise((resolve) => {
3262
+ this.stopBonjour(() => {
3263
+ resolve();
3264
+ });
3265
+ })
3266
+ );
2332
3267
  }
2333
3268
 
2334
3269
  this.webSocketProxies = [];
@@ -2338,48 +3273,60 @@ class Server {
2338
3273
  this.staticWatchers = [];
2339
3274
 
2340
3275
  if (this.webSocketServer) {
2341
- await new Promise((resolve) => {
2342
- this.webSocketServer.implementation.close(() => {
2343
- this.webSocketServer = null;
3276
+ await /** @type {Promise<void>} */ (
3277
+ new Promise((resolve) => {
3278
+ /** @type {WebSocketServerImplementation} */
3279
+ (this.webSocketServer).implementation.close(() => {
3280
+ this.webSocketServer = null;
2344
3281
 
2345
- resolve();
2346
- });
3282
+ resolve();
3283
+ });
2347
3284
 
2348
- for (const client of this.webSocketServer.clients) {
2349
- client.terminate();
2350
- }
3285
+ for (const client of /** @type {WebSocketServerImplementation} */ (
3286
+ this.webSocketServer
3287
+ ).clients) {
3288
+ client.terminate();
3289
+ }
2351
3290
 
2352
- this.webSocketServer.clients = [];
2353
- });
3291
+ /** @type {WebSocketServerImplementation} */
3292
+ (this.webSocketServer).clients = [];
3293
+ })
3294
+ );
2354
3295
  }
2355
3296
 
2356
3297
  if (this.server) {
2357
- await new Promise((resolve) => {
2358
- this.server.close(() => {
2359
- this.server = null;
3298
+ await /** @type {Promise<void>} */ (
3299
+ new Promise((resolve) => {
3300
+ /** @type {import("http").Server} */
3301
+ (this.server).close(() => {
3302
+ this.server = null;
2360
3303
 
2361
- resolve();
2362
- });
3304
+ resolve();
3305
+ });
2363
3306
 
2364
- for (const socket of this.sockets) {
2365
- socket.destroy();
2366
- }
3307
+ for (const socket of this.sockets) {
3308
+ socket.destroy();
3309
+ }
2367
3310
 
2368
- this.sockets = [];
2369
- });
3311
+ this.sockets = [];
3312
+ })
3313
+ );
2370
3314
 
2371
3315
  if (this.middleware) {
2372
- await new Promise((resolve, reject) => {
2373
- this.middleware.close((error) => {
2374
- if (error) {
2375
- reject(error);
2376
-
2377
- return;
2378
- }
2379
-
2380
- resolve();
2381
- });
2382
- });
3316
+ await /** @type {Promise<void>} */ (
3317
+ new Promise((resolve, reject) => {
3318
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3319
+ (this.middleware).close((error) => {
3320
+ if (error) {
3321
+ reject(error);
3322
+
3323
+ return;
3324
+ }
3325
+
3326
+ resolve();
3327
+ });
3328
+ })
3329
+ );
2383
3330
 
2384
3331
  this.middleware = null;
2385
3332
  }
@@ -2392,22 +3339,29 @@ class Server {
2392
3339
  }
2393
3340
  }
2394
3341
 
3342
+ /**
3343
+ * @param {(err?: Error) => void} [callback]
3344
+ */
2395
3345
  stopCallback(callback = () => {}) {
2396
3346
  this.stop()
2397
- .then(() => callback(null), callback)
3347
+ .then(() => callback(), callback)
2398
3348
  .catch(callback);
2399
3349
  }
2400
3350
 
2401
3351
  // TODO remove in the next major release
3352
+ /**
3353
+ * @param {Port} port
3354
+ * @param {Host} hostname
3355
+ * @param {(err?: Error) => void} fn
3356
+ * @returns {void}
3357
+ */
2402
3358
  listen(port, hostname, fn) {
2403
3359
  util.deprecate(
2404
3360
  () => {},
2405
- "'listen' is deprecated. Please use async 'start' or 'startCallback' methods.",
3361
+ "'listen' is deprecated. Please use the async 'start' or 'startCallback' method.",
2406
3362
  "DEP_WEBPACK_DEV_SERVER_LISTEN"
2407
3363
  )();
2408
3364
 
2409
- this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
2410
-
2411
3365
  if (typeof port === "function") {
2412
3366
  fn = port;
2413
3367
  }
@@ -2444,7 +3398,7 @@ class Server {
2444
3398
  this.options.host = hostname;
2445
3399
  }
2446
3400
 
2447
- return this.start()
3401
+ this.start()
2448
3402
  .then(() => {
2449
3403
  if (fn) {
2450
3404
  fn.call(this.server);
@@ -2458,18 +3412,22 @@ class Server {
2458
3412
  });
2459
3413
  }
2460
3414
 
3415
+ /**
3416
+ * @param {(err?: Error) => void} [callback]
3417
+ * @returns {void}
3418
+ */
2461
3419
  // TODO remove in the next major release
2462
3420
  close(callback) {
2463
3421
  util.deprecate(
2464
3422
  () => {},
2465
- "'close' is deprecated. Please use async 'stop' or 'stopCallback' methods.",
3423
+ "'close' is deprecated. Please use the async 'stop' or 'stopCallback' method.",
2466
3424
  "DEP_WEBPACK_DEV_SERVER_CLOSE"
2467
3425
  )();
2468
3426
 
2469
- return this.stop()
3427
+ this.stop()
2470
3428
  .then(() => {
2471
3429
  if (callback) {
2472
- callback(null);
3430
+ callback();
2473
3431
  }
2474
3432
  })
2475
3433
  .catch((error) => {
@@ -2480,48 +3438,4 @@ class Server {
2480
3438
  }
2481
3439
  }
2482
3440
 
2483
- const mergeExports = (obj, exports) => {
2484
- const descriptors = Object.getOwnPropertyDescriptors(exports);
2485
-
2486
- for (const name of Object.keys(descriptors)) {
2487
- const descriptor = descriptors[name];
2488
-
2489
- if (descriptor.get) {
2490
- const fn = descriptor.get;
2491
-
2492
- Object.defineProperty(obj, name, {
2493
- configurable: false,
2494
- enumerable: true,
2495
- get: fn,
2496
- });
2497
- } else if (typeof descriptor.value === "object") {
2498
- Object.defineProperty(obj, name, {
2499
- configurable: false,
2500
- enumerable: true,
2501
- writable: false,
2502
- value: mergeExports({}, descriptor.value),
2503
- });
2504
- } else {
2505
- throw new Error(
2506
- "Exposed values must be either a getter or an nested object"
2507
- );
2508
- }
2509
- }
2510
-
2511
- return Object.freeze(obj);
2512
- };
2513
-
2514
- module.exports = mergeExports(Server, {
2515
- get schema() {
2516
- return schema;
2517
- },
2518
- // TODO compatibility with webpack v4, remove it after drop
2519
- cli: {
2520
- get getArguments() {
2521
- return () => require("../bin/cli-flags");
2522
- },
2523
- get processArguments() {
2524
- return require("../bin/process-arguments");
2525
- },
2526
- },
2527
- });
3441
+ module.exports = Server;