webpack-dev-server 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Server.js CHANGED
@@ -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;
41
267
  }
42
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;
283
+ }
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") {
@@ -1102,38 +1542,61 @@ class Server {
1102
1542
  };
1103
1543
  } else {
1104
1544
  options.webSocketServer = {
1105
- type: options.webSocketServer.type || defaultWebSocketServerType,
1545
+ type:
1546
+ /** @type {WebSocketServerConfiguration} */
1547
+ (options.webSocketServer).type || defaultWebSocketServerType,
1106
1548
  options: {
1107
1549
  ...defaultWebSocketServerOptions,
1108
- ...options.webSocketServer.options,
1550
+ .../** @type {WebSocketServerConfiguration} */
1551
+ (options.webSocketServer).options,
1109
1552
  },
1110
1553
  };
1111
1554
 
1112
- if (typeof options.webSocketServer.options.port === "string") {
1113
- options.webSocketServer.options.port = Number(
1114
- options.webSocketServer.options.port
1115
- );
1555
+ const webSocketServer =
1556
+ /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
1557
+ (options.webSocketServer);
1558
+
1559
+ if (typeof webSocketServer.options.port === "string") {
1560
+ webSocketServer.options.port = Number(webSocketServer.options.port);
1116
1561
  }
1117
1562
  }
1118
1563
  }
1119
1564
 
1565
+ /**
1566
+ * @private
1567
+ * @returns {string}
1568
+ */
1120
1569
  getClientTransport() {
1121
- let ClientImplementation;
1570
+ let clientImplementation;
1122
1571
  let clientImplementationFound = true;
1123
1572
 
1124
1573
  const isKnownWebSocketServerImplementation =
1125
1574
  this.options.webSocketServer &&
1126
- typeof this.options.webSocketServer.type === "string" &&
1575
+ typeof (
1576
+ /** @type {WebSocketServerConfiguration} */
1577
+ (this.options.webSocketServer).type
1578
+ ) === "string" &&
1579
+ // @ts-ignore
1127
1580
  (this.options.webSocketServer.type === "ws" ||
1128
- this.options.webSocketServer.type === "sockjs");
1581
+ /** @type {WebSocketServerConfiguration} */
1582
+ (this.options.webSocketServer).type === "sockjs");
1129
1583
 
1130
1584
  let clientTransport;
1131
1585
 
1132
1586
  if (this.options.client) {
1133
- if (typeof this.options.client.webSocketTransport !== "undefined") {
1134
- clientTransport = this.options.client.webSocketTransport;
1587
+ if (
1588
+ typeof (
1589
+ /** @type {ClientConfiguration} */
1590
+ (this.options.client).webSocketTransport
1591
+ ) !== "undefined"
1592
+ ) {
1593
+ clientTransport =
1594
+ /** @type {ClientConfiguration} */
1595
+ (this.options.client).webSocketTransport;
1135
1596
  } else if (isKnownWebSocketServerImplementation) {
1136
- clientTransport = this.options.webSocketServer.type;
1597
+ clientTransport =
1598
+ /** @type {WebSocketServerConfiguration} */
1599
+ (this.options.webSocketServer).type;
1137
1600
  } else {
1138
1601
  clientTransport = "ws";
1139
1602
  }
@@ -1145,17 +1608,16 @@ class Server {
1145
1608
  case "string":
1146
1609
  // could be 'sockjs', 'ws', or a path that should be required
1147
1610
  if (clientTransport === "sockjs") {
1148
- ClientImplementation = require.resolve(
1611
+ clientImplementation = require.resolve(
1149
1612
  "../client/clients/SockJSClient"
1150
1613
  );
1151
1614
  } else if (clientTransport === "ws") {
1152
- ClientImplementation = require.resolve(
1615
+ clientImplementation = require.resolve(
1153
1616
  "../client/clients/WebSocketClient"
1154
1617
  );
1155
1618
  } else {
1156
1619
  try {
1157
- // eslint-disable-next-line import/no-dynamic-require
1158
- ClientImplementation = require.resolve(clientTransport);
1620
+ clientImplementation = require.resolve(clientTransport);
1159
1621
  } catch (e) {
1160
1622
  clientImplementationFound = false;
1161
1623
  }
@@ -1175,31 +1637,52 @@ class Server {
1175
1637
  );
1176
1638
  }
1177
1639
 
1178
- return ClientImplementation;
1640
+ return /** @type {string} */ (clientImplementation);
1179
1641
  }
1180
1642
 
1643
+ /**
1644
+ * @private
1645
+ * @returns {string}
1646
+ */
1181
1647
  getServerTransport() {
1182
1648
  let implementation;
1183
1649
  let implementationFound = true;
1184
1650
 
1185
- switch (typeof this.options.webSocketServer.type) {
1651
+ switch (
1652
+ typeof (
1653
+ /** @type {WebSocketServerConfiguration} */
1654
+ (this.options.webSocketServer).type
1655
+ )
1656
+ ) {
1186
1657
  case "string":
1187
1658
  // Could be 'sockjs', in the future 'ws', or a path that should be required
1188
- if (this.options.webSocketServer.type === "sockjs") {
1659
+ if (
1660
+ /** @type {WebSocketServerConfiguration} */ (
1661
+ this.options.webSocketServer
1662
+ ).type === "sockjs"
1663
+ ) {
1189
1664
  implementation = require("./servers/SockJSServer");
1190
- } else if (this.options.webSocketServer.type === "ws") {
1665
+ } else if (
1666
+ /** @type {WebSocketServerConfiguration} */ (
1667
+ this.options.webSocketServer
1668
+ ).type === "ws"
1669
+ ) {
1191
1670
  implementation = require("./servers/WebsocketServer");
1192
1671
  } else {
1193
1672
  try {
1194
1673
  // eslint-disable-next-line import/no-dynamic-require
1195
- implementation = require(this.options.webSocketServer.type);
1674
+ implementation = require(/** @type {WebSocketServerConfiguration} */ (
1675
+ this.options.webSocketServer
1676
+ ).type);
1196
1677
  } catch (error) {
1197
1678
  implementationFound = false;
1198
1679
  }
1199
1680
  }
1200
1681
  break;
1201
1682
  case "function":
1202
- implementation = this.options.webSocketServer.type;
1683
+ implementation = /** @type {WebSocketServerConfiguration} */ (
1684
+ this.options.webSocketServer
1685
+ ).type;
1203
1686
  break;
1204
1687
  default:
1205
1688
  implementationFound = false;
@@ -1216,37 +1699,61 @@ class Server {
1216
1699
  return implementation;
1217
1700
  }
1218
1701
 
1702
+ /**
1703
+ * @private
1704
+ * @returns {void}
1705
+ */
1219
1706
  setupProgressPlugin() {
1220
- const { ProgressPlugin } = this.compiler.webpack || require("webpack");
1221
-
1222
- new ProgressPlugin((percent, msg, addInfo, pluginName) => {
1223
- percent = Math.floor(percent * 100);
1707
+ const { ProgressPlugin } =
1708
+ /** @type {MultiCompiler}*/
1709
+ (this.compiler).compilers
1710
+ ? /** @type {MultiCompiler}*/ (this.compiler).compilers[0].webpack
1711
+ : /** @type {Compiler}*/ (this.compiler).webpack ||
1712
+ // TODO remove me after drop webpack v4
1713
+ require("webpack");
1714
+
1715
+ new ProgressPlugin(
1716
+ /**
1717
+ * @param {number} percent
1718
+ * @param {string} msg
1719
+ * @param {string} addInfo
1720
+ * @param {string} pluginName
1721
+ */
1722
+ (percent, msg, addInfo, pluginName) => {
1723
+ percent = Math.floor(percent * 100);
1224
1724
 
1225
- if (percent === 100) {
1226
- msg = "Compilation completed";
1227
- }
1725
+ if (percent === 100) {
1726
+ msg = "Compilation completed";
1727
+ }
1228
1728
 
1229
- if (addInfo) {
1230
- msg = `${msg} (${addInfo})`;
1231
- }
1729
+ if (addInfo) {
1730
+ msg = `${msg} (${addInfo})`;
1731
+ }
1232
1732
 
1233
- if (this.webSocketServer) {
1234
- this.sendMessage(this.webSocketServer.clients, "progress-update", {
1235
- percent,
1236
- msg,
1237
- pluginName,
1238
- });
1239
- }
1733
+ if (this.webSocketServer) {
1734
+ this.sendMessage(this.webSocketServer.clients, "progress-update", {
1735
+ percent,
1736
+ msg,
1737
+ pluginName,
1738
+ });
1739
+ }
1240
1740
 
1241
- if (this.server) {
1242
- this.server.emit("progress-update", { percent, msg, pluginName });
1741
+ if (this.server) {
1742
+ this.server.emit("progress-update", { percent, msg, pluginName });
1743
+ }
1243
1744
  }
1244
- }).apply(this.compiler);
1745
+ ).apply(this.compiler);
1245
1746
  }
1246
1747
 
1748
+ /**
1749
+ * @private
1750
+ * @returns {Promise<void>}
1751
+ */
1247
1752
  async initialize() {
1248
1753
  if (this.options.webSocketServer) {
1249
- const compilers = this.compiler.compilers || [this.compiler];
1754
+ const compilers =
1755
+ /** @type {MultiCompiler} */
1756
+ (this.compiler).compilers || [this.compiler];
1250
1757
 
1251
1758
  compilers.forEach((compiler) => {
1252
1759
  this.addAdditionalEntries(compiler);
@@ -1278,7 +1785,10 @@ class Server {
1278
1785
  }
1279
1786
  });
1280
1787
 
1281
- if (this.options.client && this.options.client.progress) {
1788
+ if (
1789
+ this.options.client &&
1790
+ /** @type {ClientConfiguration} */ (this.options.client).progress
1791
+ ) {
1282
1792
  this.setupProgressPlugin();
1283
1793
  }
1284
1794
  }
@@ -1290,7 +1800,8 @@ class Server {
1290
1800
  // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1291
1801
  this.setupBuiltInRoutes();
1292
1802
  this.setupWatchFiles();
1293
- this.setupFeatures();
1803
+ this.setupWatchStaticFiles();
1804
+ this.setupMiddlewares();
1294
1805
  this.createServer();
1295
1806
 
1296
1807
  if (this.options.setupExitSignals) {
@@ -1301,7 +1812,6 @@ class Server {
1301
1812
  signals.forEach((signal) => {
1302
1813
  const listener = () => {
1303
1814
  if (needForceShutdown) {
1304
- // eslint-disable-next-line no-process-exit
1305
1815
  process.exit();
1306
1816
  }
1307
1817
 
@@ -1314,11 +1824,9 @@ class Server {
1314
1824
  this.stopCallback(() => {
1315
1825
  if (typeof this.compiler.close === "function") {
1316
1826
  this.compiler.close(() => {
1317
- // eslint-disable-next-line no-process-exit
1318
1827
  process.exit();
1319
1828
  });
1320
1829
  } else {
1321
- // eslint-disable-next-line no-process-exit
1322
1830
  process.exit();
1323
1831
  }
1324
1832
  });
@@ -1332,53 +1840,108 @@ class Server {
1332
1840
 
1333
1841
  // Proxy WebSocket without the initial http request
1334
1842
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1335
- this.webSocketProxies.forEach((webSocketProxy) => {
1336
- this.server.on("upgrade", webSocketProxy.upgrade);
1843
+ /** @type {RequestHandler[]} */
1844
+ (this.webSocketProxies).forEach((webSocketProxy) => {
1845
+ /** @type {import("http").Server} */
1846
+ (this.server).on(
1847
+ "upgrade",
1848
+ /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
1849
+ (webSocketProxy).upgrade
1850
+ );
1337
1851
  }, this);
1338
1852
  }
1339
1853
 
1854
+ /**
1855
+ * @private
1856
+ * @returns {void}
1857
+ */
1340
1858
  setupApp() {
1341
- // Init express server
1859
+ /** @type {import("express").Application | undefined}*/
1342
1860
  // eslint-disable-next-line new-cap
1343
- this.app = new express();
1861
+ this.app = new /** @type {any} */ (express)();
1344
1862
  }
1345
1863
 
1864
+ /**
1865
+ * @private
1866
+ * @param {Stats | MultiStats} statsObj
1867
+ * @returns {StatsCompilation}
1868
+ */
1346
1869
  getStats(statsObj) {
1347
1870
  const stats = Server.DEFAULT_STATS;
1348
1871
  const compilerOptions = this.getCompilerOptions();
1349
1872
 
1873
+ // @ts-ignore
1350
1874
  if (compilerOptions.stats && compilerOptions.stats.warningsFilter) {
1875
+ // @ts-ignore
1351
1876
  stats.warningsFilter = compilerOptions.stats.warningsFilter;
1352
1877
  }
1353
1878
 
1354
1879
  return statsObj.toJson(stats);
1355
1880
  }
1356
1881
 
1882
+ /**
1883
+ * @private
1884
+ * @returns {void}
1885
+ */
1357
1886
  setupHooks() {
1358
1887
  this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
1359
1888
  if (this.webSocketServer) {
1360
1889
  this.sendMessage(this.webSocketServer.clients, "invalid");
1361
1890
  }
1362
1891
  });
1363
- this.compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1364
- if (this.webSocketServer) {
1365
- this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1366
- }
1892
+ this.compiler.hooks.done.tap(
1893
+ "webpack-dev-server",
1894
+ /**
1895
+ * @param {Stats | MultiStats} stats
1896
+ */
1897
+ (stats) => {
1898
+ if (this.webSocketServer) {
1899
+ this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1900
+ }
1367
1901
 
1368
- this.stats = stats;
1369
- });
1902
+ /**
1903
+ * @private
1904
+ * @type {Stats | MultiStats}
1905
+ */
1906
+ this.stats = stats;
1907
+ }
1908
+ );
1370
1909
  }
1371
1910
 
1911
+ /**
1912
+ * @private
1913
+ * @returns {void}
1914
+ */
1372
1915
  setupHostHeaderCheck() {
1373
- this.app.all("*", (req, res, next) => {
1374
- if (this.checkHeader(req.headers, "host")) {
1375
- return next();
1376
- }
1916
+ /** @type {import("express").Application} */
1917
+ (this.app).all(
1918
+ "*",
1919
+ /**
1920
+ * @param {Request} req
1921
+ * @param {Response} res
1922
+ * @param {NextFunction} next
1923
+ * @returns {void}
1924
+ */
1925
+ (req, res, next) => {
1926
+ if (
1927
+ this.checkHeader(
1928
+ /** @type {{ [key: string]: string | undefined }} */
1929
+ (req.headers),
1930
+ "host"
1931
+ )
1932
+ ) {
1933
+ return next();
1934
+ }
1377
1935
 
1378
- res.send("Invalid Host header");
1379
- });
1936
+ res.send("Invalid Host header");
1937
+ }
1938
+ );
1380
1939
  }
1381
1940
 
1941
+ /**
1942
+ * @private
1943
+ * @returns {void}
1944
+ */
1382
1945
  setupDevMiddleware() {
1383
1946
  const webpackDevMiddleware = require("webpack-dev-middleware");
1384
1947
 
@@ -1389,435 +1952,616 @@ class Server {
1389
1952
  );
1390
1953
  }
1391
1954
 
1955
+ /**
1956
+ * @private
1957
+ * @returns {void}
1958
+ */
1392
1959
  setupBuiltInRoutes() {
1393
1960
  const { app, middleware } = this;
1394
1961
 
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
- });
1962
+ /** @type {import("express").Application} */
1963
+ (app).get(
1964
+ "/__webpack_dev_server__/sockjs.bundle.js",
1965
+ /**
1966
+ * @param {Request} req
1967
+ * @param {Response} res
1968
+ * @returns {void}
1969
+ */
1970
+ (req, res) => {
1971
+ res.setHeader("Content-Type", "application/javascript");
1411
1972
 
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
- );
1973
+ const { createReadStream } = fs;
1974
+ const clientPath = path.join(__dirname, "..", "client");
1418
1975
 
1419
- const statsForPrint =
1420
- typeof stats.stats !== "undefined"
1421
- ? stats.toJson().children
1422
- : [stats.toJson()];
1976
+ createReadStream(
1977
+ path.join(clientPath, "modules/sockjs-client/index.js")
1978
+ ).pipe(res);
1979
+ }
1980
+ );
1423
1981
 
1424
- res.write(`<h1>Assets Report:</h1>`);
1982
+ /** @type {import("express").Application} */
1983
+ (app).get(
1984
+ "/webpack-dev-server/invalidate",
1985
+ /**
1986
+ * @param {Request} _req
1987
+ * @param {Response} res
1988
+ * @returns {void}
1989
+ */
1990
+ (_req, res) => {
1991
+ this.invalidate();
1425
1992
 
1426
- statsForPrint.forEach((item, index) => {
1427
- res.write("<div>");
1993
+ res.end();
1994
+ }
1995
+ );
1428
1996
 
1429
- const name =
1430
- item.name || (stats.stats ? `unnamed[${index}]` : "unnamed");
1997
+ /** @type {import("express").Application} */
1998
+ (app).get(
1999
+ "/webpack-dev-server",
2000
+ /**
2001
+ * @param {Request} req
2002
+ * @param {Response} res
2003
+ * @returns {void}
2004
+ */
2005
+ (req, res) => {
2006
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
2007
+ (middleware).waitUntilValid((stats) => {
2008
+ res.setHeader("Content-Type", "text/html");
2009
+ res.write(
2010
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
2011
+ );
1431
2012
 
1432
- res.write(`<h2>Compilation: ${name}</h2>`);
1433
- res.write("<ul>");
2013
+ const statsForPrint =
2014
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2015
+ ? /** @type {MultiStats} */ (stats).toJson().children
2016
+ : [/** @type {Stats} */ (stats).toJson()];
1434
2017
 
1435
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2018
+ res.write(`<h1>Assets Report:</h1>`);
1436
2019
 
1437
- for (const asset of item.assets) {
1438
- const assetName = asset.name;
1439
- const assetURL = `${publicPath}${assetName}`;
2020
+ /**
2021
+ * @type {StatsCompilation[]}
2022
+ */
2023
+ (statsForPrint).forEach((item, index) => {
2024
+ res.write("<div>");
1440
2025
 
1441
- res.write(
1442
- `<li>
2026
+ const name =
2027
+ // eslint-disable-next-line no-nested-ternary
2028
+ typeof item.name !== "undefined"
2029
+ ? item.name
2030
+ : /** @type {MultiStats} */ (stats).stats
2031
+ ? `unnamed[${index}]`
2032
+ : "unnamed";
2033
+
2034
+ res.write(`<h2>Compilation: ${name}</h2>`);
2035
+ res.write("<ul>");
2036
+
2037
+ const publicPath =
2038
+ item.publicPath === "auto" ? "" : item.publicPath;
2039
+
2040
+ for (const asset of /** @type {NonNullable<StatsCompilation["assets"]>} */ (
2041
+ item.assets
2042
+ )) {
2043
+ const assetName = asset.name;
2044
+ const assetURL = `${publicPath}${assetName}`;
2045
+
2046
+ res.write(
2047
+ `<li>
1443
2048
  <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1444
2049
  </li>`
1445
- );
1446
- }
2050
+ );
2051
+ }
1447
2052
 
1448
- res.write("</ul>");
1449
- res.write("</div>");
2053
+ res.write("</ul>");
2054
+ res.write("</div>");
2055
+ });
2056
+
2057
+ res.end("</body></html>");
1450
2058
  });
2059
+ }
2060
+ );
2061
+ }
1451
2062
 
1452
- res.end("</body></html>");
2063
+ /**
2064
+ * @private
2065
+ * @returns {void}
2066
+ */
2067
+ setupWatchStaticFiles() {
2068
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2069
+ /** @type {NormalizedStatic[]} */
2070
+ (this.options.static).forEach((staticOption) => {
2071
+ if (staticOption.watch) {
2072
+ this.watchFiles(staticOption.directory, staticOption.watch);
2073
+ }
1453
2074
  });
1454
- });
2075
+ }
1455
2076
  }
1456
2077
 
1457
- setupCompressFeature() {
1458
- const compress = require("compression");
2078
+ /**
2079
+ * @private
2080
+ * @returns {void}
2081
+ */
2082
+ setupWatchFiles() {
2083
+ const { watchFiles } = this.options;
1459
2084
 
1460
- this.app.use(compress());
2085
+ if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
2086
+ /** @type {WatchFiles[]} */
2087
+ (watchFiles).forEach((item) => {
2088
+ this.watchFiles(item.paths, item.options);
2089
+ });
2090
+ }
1461
2091
  }
1462
2092
 
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
- };
2093
+ /**
2094
+ * @private
2095
+ * @returns {void}
2096
+ */
2097
+ setupMiddlewares() {
1479
2098
  /**
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
- * ]
2099
+ * @type {Array<Middleware>}
1494
2100
  */
1495
- this.options.proxy.forEach((proxyConfigOrCallback) => {
1496
- let proxyMiddleware;
2101
+ let middlewares = [];
1497
2102
 
1498
- let proxyConfig =
1499
- typeof proxyConfigOrCallback === "function"
1500
- ? proxyConfigOrCallback()
1501
- : proxyConfigOrCallback;
1502
-
1503
- proxyMiddleware = getProxyMiddleware(proxyConfig);
1504
-
1505
- if (proxyConfig.ws) {
1506
- this.webSocketProxies.push(proxyMiddleware);
1507
- }
2103
+ // compress is placed last and uses unshift so that it will be the first middleware used
2104
+ if (this.options.compress) {
2105
+ const compression = require("compression");
1508
2106
 
1509
- const handle = async (req, res, next) => {
1510
- if (typeof proxyConfigOrCallback === "function") {
1511
- const newProxyConfig = proxyConfigOrCallback(req, res, next);
2107
+ middlewares.push({ name: "compression", middleware: compression() });
2108
+ }
1512
2109
 
1513
- if (newProxyConfig !== proxyConfig) {
1514
- proxyConfig = newProxyConfig;
1515
- proxyMiddleware = getProxyMiddleware(proxyConfig);
1516
- }
1517
- }
2110
+ if (typeof this.options.onBeforeSetupMiddleware === "function") {
2111
+ this.options.onBeforeSetupMiddleware(this);
2112
+ }
1518
2113
 
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
- };
2114
+ if (typeof this.options.headers !== "undefined") {
2115
+ middlewares.push({
2116
+ name: "set-headers",
2117
+ path: "*",
2118
+ middleware: this.setHeaders.bind(this),
2119
+ });
2120
+ }
1542
2121
 
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));
2122
+ middlewares.push({
2123
+ name: "webpack-dev-middleware",
2124
+ middleware:
2125
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2126
+ (this.middleware),
1546
2127
  });
1547
- }
1548
2128
 
1549
- setupHistoryApiFallbackFeature() {
1550
- const { historyApiFallback } = this.options;
2129
+ if (this.options.proxy) {
2130
+ const { createProxyMiddleware } = require("http-proxy-middleware");
1551
2131
 
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
- }
2132
+ /**
2133
+ * @param {ProxyConfigArray} proxyConfig
2134
+ * @returns {RequestHandler | undefined}
2135
+ */
2136
+ const getProxyMiddleware = (proxyConfig) => {
2137
+ // It is possible to use the `bypass` method without a `target` or `router`.
2138
+ // However, the proxy middleware has no use in this case, and will fail to instantiate.
2139
+ if (proxyConfig.target) {
2140
+ const context = proxyConfig.context || proxyConfig.path;
2141
+
2142
+ return createProxyMiddleware(
2143
+ /** @type {string} */ (context),
2144
+ proxyConfig
2145
+ );
2146
+ }
1561
2147
 
1562
- // Fall back to /index.html if nothing else matches.
1563
- this.app.use(require("connect-history-api-fallback")(historyApiFallback));
1564
- }
2148
+ if (proxyConfig.router) {
2149
+ return createProxyMiddleware(proxyConfig);
2150
+ }
2151
+ };
1565
2152
 
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
- }
2153
+ /**
2154
+ * Assume a proxy configuration specified as:
2155
+ * proxy: [
2156
+ * {
2157
+ * context: "value",
2158
+ * ...options,
2159
+ * },
2160
+ * // or:
2161
+ * function() {
2162
+ * return {
2163
+ * context: "context",
2164
+ * ...options,
2165
+ * };
2166
+ * }
2167
+ * ]
2168
+ */
2169
+ /** @type {ProxyArray} */
2170
+ (this.options.proxy).forEach(
2171
+ /**
2172
+ * @param {any} proxyConfigOrCallback
2173
+ */
2174
+ (proxyConfigOrCallback) => {
2175
+ /**
2176
+ * @type {RequestHandler}
2177
+ */
2178
+ let proxyMiddleware;
2179
+
2180
+ let proxyConfig =
2181
+ typeof proxyConfigOrCallback === "function"
2182
+ ? proxyConfigOrCallback()
2183
+ : proxyConfigOrCallback;
2184
+
2185
+ proxyMiddleware =
2186
+ /** @type {RequestHandler} */
2187
+ (getProxyMiddleware(proxyConfig));
2188
+
2189
+ if (proxyConfig.ws) {
2190
+ this.webSocketProxies.push(proxyMiddleware);
2191
+ }
1576
2192
 
1577
- setupStaticServeIndexFeature() {
1578
- const serveIndex = require("serve-index");
2193
+ /**
2194
+ * @param {Request} req
2195
+ * @param {Response} res
2196
+ * @param {NextFunction} next
2197
+ * @returns {Promise<void>}
2198
+ */
2199
+ const handler = async (req, res, next) => {
2200
+ if (typeof proxyConfigOrCallback === "function") {
2201
+ const newProxyConfig = proxyConfigOrCallback(req, res, next);
2202
+
2203
+ if (newProxyConfig !== proxyConfig) {
2204
+ proxyConfig = newProxyConfig;
2205
+ proxyMiddleware =
2206
+ /** @type {RequestHandler} */
2207
+ (getProxyMiddleware(proxyConfig));
2208
+ }
2209
+ }
1579
2210
 
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();
2211
+ // - Check if we have a bypass function defined
2212
+ // - In case the bypass function is defined we'll retrieve the
2213
+ // bypassUrl from it otherwise bypassUrl would be null
2214
+ // TODO remove in the next major in favor `context` and `router` options
2215
+ const isByPassFuncDefined =
2216
+ typeof proxyConfig.bypass === "function";
2217
+ const bypassUrl = isByPassFuncDefined
2218
+ ? await proxyConfig.bypass(req, res, proxyConfig)
2219
+ : null;
2220
+
2221
+ if (typeof bypassUrl === "boolean") {
2222
+ // skip the proxy
2223
+ // @ts-ignore
2224
+ req.url = null;
2225
+ next();
2226
+ } else if (typeof bypassUrl === "string") {
2227
+ // byPass to that url
2228
+ req.url = bypassUrl;
2229
+ next();
2230
+ } else if (proxyMiddleware) {
2231
+ return proxyMiddleware(req, res, next);
2232
+ } else {
2233
+ next();
1587
2234
  }
2235
+ };
1588
2236
 
1589
- serveIndex(staticOption.directory, staticOption.serveIndex)(
1590
- req,
1591
- res,
1592
- next
1593
- );
2237
+ middlewares.push({
2238
+ name: "http-proxy-middleware",
2239
+ middleware: handler,
2240
+ });
2241
+ // Also forward error requests to the proxy so it can handle them.
2242
+ middlewares.push({
2243
+ name: "http-proxy-middleware-error-handler",
2244
+ middleware:
2245
+ /**
2246
+ * @param {Error} error
2247
+ * @param {Request} req
2248
+ * @param {Response} res
2249
+ * @param {NextFunction} next
2250
+ * @returns {any}
2251
+ */
2252
+ (error, req, res, next) => handler(req, res, next),
1594
2253
  });
1595
2254
  }
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;
2255
+ );
1614
2256
 
1615
- if (watchFiles.length > 0) {
1616
- watchFiles.forEach((item) => {
1617
- this.watchFiles(item.paths, item.options);
2257
+ middlewares.push({
2258
+ name: "webpack-dev-middleware",
2259
+ middleware:
2260
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2261
+ (this.middleware),
1618
2262
  });
1619
2263
  }
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
-
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
2264
 
1694
- if (this.options.onBeforeSetupMiddleware) {
1695
- runnableFeatures.push("onBeforeSetupMiddleware");
2265
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2266
+ /** @type {NormalizedStatic[]} */
2267
+ (this.options.static).forEach((staticOption) => {
2268
+ staticOption.publicPath.forEach((publicPath) => {
2269
+ middlewares.push({
2270
+ name: "express-static",
2271
+ path: publicPath,
2272
+ middleware: express.static(
2273
+ staticOption.directory,
2274
+ staticOption.staticOptions
2275
+ ),
2276
+ });
2277
+ });
2278
+ });
1696
2279
  }
1697
2280
 
1698
- runnableFeatures.push("headers", "middleware");
2281
+ if (this.options.historyApiFallback) {
2282
+ const connectHistoryApiFallback = require("connect-history-api-fallback");
2283
+ const { historyApiFallback } = this.options;
1699
2284
 
1700
- if (this.options.proxy) {
1701
- runnableFeatures.push("proxy", "middleware");
1702
- }
2285
+ if (
2286
+ typeof (
2287
+ /** @type {ConnectHistoryApiFallbackOptions} */
2288
+ (historyApiFallback).logger
2289
+ ) === "undefined" &&
2290
+ !(
2291
+ /** @type {ConnectHistoryApiFallbackOptions} */
2292
+ (historyApiFallback).verbose
2293
+ )
2294
+ ) {
2295
+ // @ts-ignore
2296
+ historyApiFallback.logger = this.logger.log.bind(
2297
+ this.logger,
2298
+ "[connect-history-api-fallback]"
2299
+ );
2300
+ }
1703
2301
 
1704
- if (this.options.static) {
1705
- runnableFeatures.push("static");
1706
- }
2302
+ // Fall back to /index.html if nothing else matches.
2303
+ middlewares.push({
2304
+ name: "connect-history-api-fallback",
2305
+ middleware: connectHistoryApiFallback(
2306
+ /** @type {ConnectHistoryApiFallbackOptions} */
2307
+ (historyApiFallback)
2308
+ ),
2309
+ });
1707
2310
 
1708
- if (this.options.historyApiFallback) {
1709
- runnableFeatures.push("historyApiFallback", "middleware");
2311
+ // include our middleware to ensure
2312
+ // it is able to handle '/index.html' request after redirect
2313
+ middlewares.push({
2314
+ name: "webpack-dev-middleware",
2315
+ middleware:
2316
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2317
+ (this.middleware),
2318
+ });
1710
2319
 
1711
- if (this.options.static) {
1712
- runnableFeatures.push("static");
2320
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2321
+ /** @type {NormalizedStatic[]} */
2322
+ (this.options.static).forEach((staticOption) => {
2323
+ staticOption.publicPath.forEach((publicPath) => {
2324
+ middlewares.push({
2325
+ name: "express-static",
2326
+ path: publicPath,
2327
+ middleware: express.static(
2328
+ staticOption.directory,
2329
+ staticOption.staticOptions
2330
+ ),
2331
+ });
2332
+ });
2333
+ });
1713
2334
  }
1714
2335
  }
1715
2336
 
1716
- if (this.options.static) {
1717
- runnableFeatures.push("staticServeIndex", "staticWatch");
2337
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2338
+ const serveIndex = require("serve-index");
2339
+
2340
+ /** @type {NormalizedStatic[]} */
2341
+ (this.options.static).forEach((staticOption) => {
2342
+ staticOption.publicPath.forEach((publicPath) => {
2343
+ if (staticOption.serveIndex) {
2344
+ middlewares.push({
2345
+ name: "serve-index",
2346
+ path: publicPath,
2347
+ /**
2348
+ * @param {Request} req
2349
+ * @param {Response} res
2350
+ * @param {NextFunction} next
2351
+ * @returns {void}
2352
+ */
2353
+ middleware: (req, res, next) => {
2354
+ // serve-index doesn't fallthrough non-get/head request to next middleware
2355
+ if (req.method !== "GET" && req.method !== "HEAD") {
2356
+ return next();
2357
+ }
2358
+
2359
+ serveIndex(
2360
+ staticOption.directory,
2361
+ /** @type {ServeIndexOptions} */
2362
+ (staticOption.serveIndex)
2363
+ )(req, res, next);
2364
+ },
2365
+ });
2366
+ }
2367
+ });
2368
+ });
1718
2369
  }
1719
2370
 
1720
2371
  if (this.options.magicHtml) {
1721
- runnableFeatures.push("magicHtml");
2372
+ middlewares.push({
2373
+ name: "serve-magic-html",
2374
+ middleware: this.serveMagicHtml.bind(this),
2375
+ });
2376
+ }
2377
+
2378
+ if (typeof this.options.onAfterSetupMiddleware === "function") {
2379
+ this.options.onAfterSetupMiddleware(this);
1722
2380
  }
1723
2381
 
1724
- if (this.options.onAfterSetupMiddleware) {
1725
- runnableFeatures.push("onAfterSetupMiddleware");
2382
+ if (typeof this.options.setupMiddlewares === "function") {
2383
+ middlewares = this.options.setupMiddlewares(middlewares, this);
1726
2384
  }
1727
2385
 
1728
- runnableFeatures.forEach((feature) => {
1729
- features[feature]();
2386
+ middlewares.forEach((middleware) => {
2387
+ if (typeof middleware === "function") {
2388
+ /** @type {import("express").Application} */
2389
+ (this.app).use(middleware);
2390
+ } else if (typeof middleware.path !== "undefined") {
2391
+ /** @type {import("express").Application} */
2392
+ (this.app).use(middleware.path, middleware.middleware);
2393
+ } else {
2394
+ /** @type {import("express").Application} */
2395
+ (this.app).use(middleware.middleware);
2396
+ }
1730
2397
  });
1731
2398
  }
1732
2399
 
2400
+ /**
2401
+ * @private
2402
+ * @returns {void}
2403
+ */
1733
2404
  createServer() {
2405
+ const { type, options } = /** @type {ServerConfiguration} */ (
2406
+ this.options.server
2407
+ );
2408
+
2409
+ /** @type {import("http").Server | undefined | null} */
1734
2410
  // eslint-disable-next-line import/no-dynamic-require
1735
- this.server = require(this.options.server.type).createServer(
1736
- this.options.server.options,
2411
+ this.server = require(/** @type {string} */ (type)).createServer(
2412
+ options,
1737
2413
  this.app
1738
2414
  );
1739
2415
 
1740
- this.server.on("connection", (socket) => {
1741
- // Add socket to list
1742
- this.sockets.push(socket);
2416
+ /** @type {import("http").Server} */
2417
+ (this.server).on(
2418
+ "connection",
2419
+ /**
2420
+ * @param {Socket} socket
2421
+ */
2422
+ (socket) => {
2423
+ // Add socket to list
2424
+ this.sockets.push(socket);
1743
2425
 
1744
- socket.once("close", () => {
1745
- // Remove socket from list
1746
- this.sockets.splice(this.sockets.indexOf(socket), 1);
1747
- });
1748
- });
2426
+ socket.once("close", () => {
2427
+ // Remove socket from list
2428
+ this.sockets.splice(this.sockets.indexOf(socket), 1);
2429
+ });
2430
+ }
2431
+ );
1749
2432
 
1750
- this.server.on("error", (error) => {
1751
- throw error;
1752
- });
2433
+ /** @type {import("http").Server} */
2434
+ (this.server).on(
2435
+ "error",
2436
+ /**
2437
+ * @param {Error} error
2438
+ */
2439
+ (error) => {
2440
+ throw error;
2441
+ }
2442
+ );
1753
2443
  }
1754
2444
 
2445
+ /**
2446
+ * @private
2447
+ * @returns {void}
2448
+ */
1755
2449
  // TODO: remove `--web-socket-server` in favor of `--web-socket-server-type`
1756
2450
  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
- }
2451
+ /** @type {WebSocketServerImplementation | undefined | null} */
2452
+ this.webSocketServer = new /** @type {any} */ (this.getServerTransport())(
2453
+ this
2454
+ );
2455
+ /** @type {WebSocketServerImplementation} */
2456
+ (this.webSocketServer).implementation.on(
2457
+ "connection",
2458
+ /**
2459
+ * @param {ClientConnection} client
2460
+ * @param {IncomingMessage} request
2461
+ */
2462
+ (client, request) => {
2463
+ /** @type {{ [key: string]: string | undefined } | undefined} */
2464
+ const headers =
2465
+ // eslint-disable-next-line no-nested-ternary
2466
+ typeof request !== "undefined"
2467
+ ? /** @type {{ [key: string]: string | undefined }} */
2468
+ (request.headers)
2469
+ : typeof (
2470
+ /** @type {import("sockjs").Connection} */ (client).headers
2471
+ ) !== "undefined"
2472
+ ? /** @type {import("sockjs").Connection} */ (client).headers
2473
+ : // eslint-disable-next-line no-undefined
2474
+ undefined;
2475
+
2476
+ if (!headers) {
2477
+ this.logger.warn(
2478
+ 'webSocketServer implementation must pass headers for the "connection" event'
2479
+ );
2480
+ }
1773
2481
 
1774
- if (
1775
- !headers ||
1776
- !this.checkHeader(headers, "host") ||
1777
- !this.checkHeader(headers, "origin")
1778
- ) {
1779
- this.sendMessage([client], "error", "Invalid Host/Origin header");
2482
+ if (
2483
+ !headers ||
2484
+ !this.checkHeader(headers, "host") ||
2485
+ !this.checkHeader(headers, "origin")
2486
+ ) {
2487
+ this.sendMessage([client], "error", "Invalid Host/Origin header");
1780
2488
 
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();
2489
+ // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
2490
+ // Terminate would prevent it sending, so use close to allow it to be sent
2491
+ client.close();
1784
2492
 
1785
- return;
1786
- }
2493
+ return;
2494
+ }
1787
2495
 
1788
- if (this.options.hot === true || this.options.hot === "only") {
1789
- this.sendMessage([client], "hot");
1790
- }
2496
+ if (this.options.hot === true || this.options.hot === "only") {
2497
+ this.sendMessage([client], "hot");
2498
+ }
1791
2499
 
1792
- if (this.options.liveReload) {
1793
- this.sendMessage([client], "liveReload");
1794
- }
2500
+ if (this.options.liveReload) {
2501
+ this.sendMessage([client], "liveReload");
2502
+ }
1795
2503
 
1796
- if (this.options.client && this.options.client.progress) {
1797
- this.sendMessage([client], "progress", this.options.client.progress);
1798
- }
2504
+ if (
2505
+ this.options.client &&
2506
+ /** @type {ClientConfiguration} */
2507
+ (this.options.client).progress
2508
+ ) {
2509
+ this.sendMessage(
2510
+ [client],
2511
+ "progress",
2512
+ /** @type {ClientConfiguration} */
2513
+ (this.options.client).progress
2514
+ );
2515
+ }
1799
2516
 
1800
- if (this.options.client && this.options.client.reconnect) {
1801
- this.sendMessage([client], "reconnect", this.options.client.reconnect);
1802
- }
2517
+ if (
2518
+ this.options.client &&
2519
+ /** @type {ClientConfiguration} */ (this.options.client).reconnect
2520
+ ) {
2521
+ this.sendMessage(
2522
+ [client],
2523
+ "reconnect",
2524
+ /** @type {ClientConfiguration} */
2525
+ (this.options.client).reconnect
2526
+ );
2527
+ }
1803
2528
 
1804
- if (this.options.client && this.options.client.overlay) {
1805
- this.sendMessage([client], "overlay", this.options.client.overlay);
1806
- }
2529
+ if (
2530
+ this.options.client &&
2531
+ /** @type {ClientConfiguration} */
2532
+ (this.options.client).overlay
2533
+ ) {
2534
+ this.sendMessage(
2535
+ [client],
2536
+ "overlay",
2537
+ /** @type {ClientConfiguration} */
2538
+ (this.options.client).overlay
2539
+ );
2540
+ }
1807
2541
 
1808
- if (!this.stats) {
1809
- return;
1810
- }
2542
+ if (!this.stats) {
2543
+ return;
2544
+ }
1811
2545
 
1812
- this.sendStats([client], this.getStats(this.stats), true);
1813
- });
2546
+ this.sendStats([client], this.getStats(this.stats), true);
2547
+ }
2548
+ );
1814
2549
  }
1815
2550
 
2551
+ /**
2552
+ * @private
2553
+ * @param {string} defaultOpenTarget
2554
+ * @returns {void}
2555
+ */
1816
2556
  openBrowser(defaultOpenTarget) {
1817
2557
  const open = require("open");
1818
2558
 
1819
2559
  Promise.all(
1820
- this.options.open.map((item) => {
2560
+ /** @type {NormalizedOpen[]} */
2561
+ (this.options.open).map((item) => {
2562
+ /**
2563
+ * @type {string}
2564
+ */
1821
2565
  let openTarget;
1822
2566
 
1823
2567
  if (item.target === "<url>") {
@@ -1831,13 +2575,17 @@ class Server {
1831
2575
  return open(openTarget, item.options).catch(() => {
1832
2576
  this.logger.warn(
1833
2577
  `Unable to open "${openTarget}" page${
1834
- // eslint-disable-next-line no-nested-ternary
1835
2578
  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`
2579
+ ? ` in "${
2580
+ /** @type {import("open").App} */
2581
+ (item.options.app).name
2582
+ }" app${
2583
+ /** @type {import("open").App} */
2584
+ (item.options.app).arguments
2585
+ ? ` with "${
2586
+ /** @type {import("open").App} */
2587
+ (item.options.app).arguments.join(" ")
2588
+ }" arguments`
1841
2589
  : ""
1842
2590
  }`
1843
2591
  : ""
@@ -1848,38 +2596,68 @@ class Server {
1848
2596
  );
1849
2597
  }
1850
2598
 
1851
- stopBonjour(callback = () => {}) {
1852
- this.bonjour.unpublishAll(() => {
1853
- this.bonjour.destroy();
1854
-
1855
- if (callback) {
1856
- callback();
1857
- }
1858
- });
1859
- }
1860
-
2599
+ /**
2600
+ * @private
2601
+ * @returns {void}
2602
+ */
1861
2603
  runBonjour() {
2604
+ /**
2605
+ * @private
2606
+ * @type {import("bonjour").Bonjour | undefined}
2607
+ */
1862
2608
  this.bonjour = require("bonjour")();
1863
2609
  this.bonjour.publish({
1864
2610
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
1865
- port: this.options.port,
1866
- type: this.options.server.type === "http" ? "http" : "https",
2611
+ port: /** @type {number} */ (this.options.port),
2612
+ type:
2613
+ /** @type {ServerConfiguration} */
2614
+ (this.options.server).type === "http" ? "http" : "https",
1867
2615
  subtypes: ["webpack"],
1868
- ...this.options.bonjour,
2616
+ .../** @type {BonjourOptions} */ (this.options.bonjour),
2617
+ });
2618
+ }
2619
+
2620
+ /**
2621
+ * @private
2622
+ * @returns {void}
2623
+ */
2624
+ stopBonjour(callback = () => {}) {
2625
+ /** @type {Bonjour} */
2626
+ (this.bonjour).unpublishAll(() => {
2627
+ /** @type {Bonjour} */
2628
+ (this.bonjour).destroy();
2629
+
2630
+ if (callback) {
2631
+ callback();
2632
+ }
1869
2633
  });
1870
2634
  }
1871
2635
 
2636
+ /**
2637
+ * @private
2638
+ * @returns {void}
2639
+ */
1872
2640
  logStatus() {
1873
2641
  const { isColorSupported, cyan, red } = require("colorette");
1874
2642
 
2643
+ /**
2644
+ * @param {Compiler["options"]} compilerOptions
2645
+ * @returns {boolean}
2646
+ */
1875
2647
  const getColorsOption = (compilerOptions) => {
2648
+ /**
2649
+ * @type {boolean}
2650
+ */
1876
2651
  let colorsEnabled;
1877
2652
 
1878
2653
  if (
1879
2654
  compilerOptions.stats &&
1880
- typeof compilerOptions.stats.colors !== "undefined"
2655
+ typeof (/** @type {StatsOptions} */ (compilerOptions.stats).colors) !==
2656
+ "undefined"
1881
2657
  ) {
1882
- colorsEnabled = compilerOptions.stats;
2658
+ colorsEnabled =
2659
+ /** @type {boolean} */
2660
+ (/** @type {StatsOptions} */ (compilerOptions.stats).colors);
1883
2661
  } else {
1884
2662
  colorsEnabled = isColorSupported;
1885
2663
  }
@@ -1888,6 +2666,11 @@ class Server {
1888
2666
  };
1889
2667
 
1890
2668
  const colors = {
2669
+ /**
2670
+ * @param {boolean} useColor
2671
+ * @param {string} msg
2672
+ * @returns {string}
2673
+ */
1891
2674
  info(useColor, msg) {
1892
2675
  if (useColor) {
1893
2676
  return cyan(msg);
@@ -1895,6 +2678,11 @@ class Server {
1895
2678
 
1896
2679
  return msg;
1897
2680
  },
2681
+ /**
2682
+ * @param {boolean} useColor
2683
+ * @param {string} msg
2684
+ * @returns {string}
2685
+ */
1898
2686
  error(useColor, msg) {
1899
2687
  if (useColor) {
1900
2688
  return red(msg);
@@ -1906,10 +2694,26 @@ class Server {
1906
2694
  const useColor = getColorsOption(this.getCompilerOptions());
1907
2695
 
1908
2696
  if (this.options.ipc) {
1909
- this.logger.info(`Project is running at: "${this.server.address()}"`);
2697
+ this.logger.info(
2698
+ `Project is running at: "${
2699
+ /** @type {import("http").Server} */
2700
+ (this.server).address()
2701
+ }"`
2702
+ );
1910
2703
  } else {
1911
- const protocol = this.options.server.type === "http" ? "http" : "https";
1912
- const { address, port } = this.server.address();
2704
+ const protocol =
2705
+ /** @type {ServerConfiguration} */
2706
+ (this.options.server).type === "http" ? "http" : "https";
2707
+ const { address, port } =
2708
+ /** @type {import("net").AddressInfo} */
2709
+ (
2710
+ /** @type {import("http").Server} */
2711
+ (this.server).address()
2712
+ );
2713
+ /**
2714
+ * @param {string} newHostname
2715
+ * @returns {string}
2716
+ */
1913
2717
  const prettyPrintURL = (newHostname) =>
1914
2718
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
1915
2719
 
@@ -1962,8 +2766,13 @@ class Server {
1962
2766
  }
1963
2767
  } else {
1964
2768
  networkUrlIPv4 =
1965
- parsedIP.kind() === "ipv6" && parsedIP.isIPv4MappedAddress()
1966
- ? prettyPrintURL(parsedIP.toIPv4Address().toString())
2769
+ parsedIP.kind() === "ipv6" &&
2770
+ /** @type {IPv6} */
2771
+ (parsedIP).isIPv4MappedAddress()
2772
+ ? prettyPrintURL(
2773
+ /** @type {IPv6} */
2774
+ (parsedIP).toIPv4Address().toString()
2775
+ )
1967
2776
  : prettyPrintURL(address);
1968
2777
 
1969
2778
  if (parsedIP.kind() === "ipv6") {
@@ -1978,10 +2787,19 @@ class Server {
1978
2787
  }
1979
2788
 
1980
2789
  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)] : []);
2790
+ const loopbacks = [];
2791
+
2792
+ if (localhost) {
2793
+ loopbacks.push([colors.info(useColor, localhost)]);
2794
+ }
2795
+
2796
+ if (loopbackIPv4) {
2797
+ loopbacks.push([colors.info(useColor, loopbackIPv4)]);
2798
+ }
2799
+
2800
+ if (loopbackIPv6) {
2801
+ loopbacks.push([colors.info(useColor, loopbackIPv6)]);
2802
+ }
1985
2803
 
1986
2804
  this.logger.info(`Loopback: ${loopbacks.join(", ")}`);
1987
2805
  }
@@ -1998,18 +2816,19 @@ class Server {
1998
2816
  );
1999
2817
  }
2000
2818
 
2001
- if (this.options.open.length > 0) {
2819
+ if (/** @type {NormalizedOpen[]} */ (this.options.open).length > 0) {
2002
2820
  const openTarget = prettyPrintURL(this.options.host || "localhost");
2003
2821
 
2004
2822
  this.openBrowser(openTarget);
2005
2823
  }
2006
2824
  }
2007
2825
 
2008
- if (this.options.static && this.options.static.length > 0) {
2826
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2009
2827
  this.logger.info(
2010
2828
  `Content not from webpack is served from '${colors.info(
2011
2829
  useColor,
2012
- this.options.static
2830
+ /** @type {NormalizedStatic[]} */
2831
+ (this.options.static)
2013
2832
  .map((staticOption) => staticOption.directory)
2014
2833
  .join(", ")
2015
2834
  )}' directory`
@@ -2020,14 +2839,19 @@ class Server {
2020
2839
  this.logger.info(
2021
2840
  `404s will fallback to '${colors.info(
2022
2841
  useColor,
2023
- this.options.historyApiFallback.index || "/index.html"
2842
+ /** @type {ConnectHistoryApiFallbackOptions} */ (
2843
+ this.options.historyApiFallback
2844
+ ).index || "/index.html"
2024
2845
  )}'`
2025
2846
  );
2026
2847
  }
2027
2848
 
2028
2849
  if (this.options.bonjour) {
2029
2850
  const bonjourProtocol =
2030
- this.options.bonjour.type || this.options.server.type === "http"
2851
+ /** @type {BonjourOptions} */
2852
+ (this.options.bonjour).type ||
2853
+ /** @type {ServerConfiguration} */
2854
+ (this.options.server).type === "http"
2031
2855
  ? "http"
2032
2856
  : "https";
2033
2857
 
@@ -2037,32 +2861,59 @@ class Server {
2037
2861
  }
2038
2862
  }
2039
2863
 
2864
+ /**
2865
+ * @private
2866
+ * @param {Request} req
2867
+ * @param {Response} res
2868
+ * @param {NextFunction} next
2869
+ */
2040
2870
  setHeaders(req, res, next) {
2041
2871
  let { headers } = this.options;
2042
2872
 
2043
2873
  if (headers) {
2044
2874
  if (typeof headers === "function") {
2045
- headers = headers(req, res, this.middleware.context);
2875
+ headers = headers(
2876
+ req,
2877
+ res,
2878
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
2879
+ (this.middleware).context
2880
+ );
2046
2881
  }
2047
2882
 
2883
+ /**
2884
+ * @type {{key: string, value: string}[]}
2885
+ */
2048
2886
  const allHeaders = [];
2049
2887
 
2050
2888
  if (!Array.isArray(headers)) {
2051
2889
  // eslint-disable-next-line guard-for-in
2052
2890
  for (const name in headers) {
2891
+ // @ts-ignore
2053
2892
  allHeaders.push({ key: name, value: headers[name] });
2054
2893
  }
2894
+
2055
2895
  headers = allHeaders;
2056
2896
  }
2057
2897
 
2058
- headers.forEach((header) => {
2059
- res.setHeader(header.key, header.value);
2060
- });
2898
+ headers.forEach(
2899
+ /**
2900
+ * @param {{key: string, value: any}} header
2901
+ */
2902
+ (header) => {
2903
+ res.setHeader(header.key, header.value);
2904
+ }
2905
+ );
2061
2906
  }
2062
2907
 
2063
2908
  next();
2064
2909
  }
2065
2910
 
2911
+ /**
2912
+ * @private
2913
+ * @param {{ [key: string]: string | undefined }} headers
2914
+ * @param {string} headerToCheck
2915
+ * @returns {boolean}
2916
+ */
2066
2917
  checkHeader(headers, headerToCheck) {
2067
2918
  // allow user to opt out of this security check, at their own risk
2068
2919
  // by explicitly enabling allowedHosts
@@ -2099,8 +2950,8 @@ class Server {
2099
2950
  // always allow localhost host, for convenience (hostname === 'localhost')
2100
2951
  // allow hostname of listening address (hostname === this.options.host)
2101
2952
  const isValidHostname =
2102
- ipaddr.IPv4.isValid(hostname) ||
2103
- ipaddr.IPv6.isValid(hostname) ||
2953
+ (hostname !== null && ipaddr.IPv4.isValid(hostname)) ||
2954
+ (hostname !== null && ipaddr.IPv6.isValid(hostname)) ||
2104
2955
  hostname === "localhost" ||
2105
2956
  hostname === this.options.host;
2106
2957
 
@@ -2127,7 +2978,7 @@ class Server {
2127
2978
  // "*.example.com" (hostname.endsWith(allowedHost))
2128
2979
  if (
2129
2980
  hostname === allowedHost.substring(1) ||
2130
- hostname.endsWith(allowedHost)
2981
+ /** @type {string} */ (hostname).endsWith(allowedHost)
2131
2982
  ) {
2132
2983
  return true;
2133
2984
  }
@@ -2138,15 +2989,27 @@ class Server {
2138
2989
  // Also allow if `client.webSocketURL.hostname` provided
2139
2990
  if (
2140
2991
  this.options.client &&
2141
- typeof this.options.client.webSocketURL !== "undefined"
2992
+ typeof (
2993
+ /** @type {ClientConfiguration} */ (this.options.client).webSocketURL
2994
+ ) !== "undefined"
2142
2995
  ) {
2143
- return this.options.client.webSocketURL.hostname === hostname;
2996
+ return (
2997
+ /** @type {WebSocketURL} */
2998
+ (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
2999
+ .hostname === hostname
3000
+ );
2144
3001
  }
2145
3002
 
2146
3003
  // disallow
2147
3004
  return false;
2148
3005
  }
2149
3006
 
3007
+ /**
3008
+ * @param {ClientConnection[]} clients
3009
+ * @param {string} type
3010
+ * @param {any} [data]
3011
+ * @param {any} [params]
3012
+ */
2150
3013
  // eslint-disable-next-line class-methods-use-this
2151
3014
  sendMessage(clients, type, data, params) {
2152
3015
  for (const client of clients) {
@@ -2158,21 +3021,41 @@ class Server {
2158
3021
  }
2159
3022
  }
2160
3023
 
3024
+ /**
3025
+ * @private
3026
+ * @param {Request} req
3027
+ * @param {Response} res
3028
+ * @param {NextFunction} next
3029
+ * @returns {void}
3030
+ */
2161
3031
  serveMagicHtml(req, res, next) {
2162
- this.middleware.waitUntilValid(() => {
3032
+ if (req.method !== "GET" && req.method !== "HEAD") {
3033
+ return next();
3034
+ }
3035
+
3036
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3037
+ (this.middleware).waitUntilValid(() => {
2163
3038
  const _path = req.path;
2164
3039
 
2165
3040
  try {
2166
- const filename = this.middleware.getFilenameFromUrl(`${_path}.js`);
2167
- const isFile = this.middleware.context.outputFileSystem
2168
- .statSync(filename)
2169
- .isFile();
3041
+ const filename =
3042
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3043
+ (this.middleware).getFilenameFromUrl(`${_path}.js`);
3044
+ const isFile =
3045
+ /** @type {Compiler["outputFileSystem"] & { statSync: import("fs").StatSyncFn }}*/
3046
+ (
3047
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3048
+ (this.middleware).context.outputFileSystem
3049
+ )
3050
+ .statSync(/** @type {import("fs").PathLike} */ (filename))
3051
+ .isFile();
2170
3052
 
2171
3053
  if (!isFile) {
2172
3054
  return next();
2173
3055
  }
2174
3056
 
2175
3057
  // Serve a page that executes the javascript
3058
+ // @ts-ignore
2176
3059
  const queries = req._parsedUrl.search || "";
2177
3060
  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
3061
 
@@ -2184,6 +3067,12 @@ class Server {
2184
3067
  }
2185
3068
 
2186
3069
  // Send stats to a socket or multiple sockets
3070
+ /**
3071
+ * @private
3072
+ * @param {ClientConnection[]} clients
3073
+ * @param {StatsCompilation} stats
3074
+ * @param {boolean} [force]
3075
+ */
2187
3076
  sendStats(clients, stats, force) {
2188
3077
  const shouldEmit =
2189
3078
  !force &&
@@ -2201,10 +3090,20 @@ class Server {
2201
3090
  this.currentHash = stats.hash;
2202
3091
  this.sendMessage(clients, "hash", stats.hash);
2203
3092
 
2204
- if (stats.errors.length > 0 || stats.warnings.length > 0) {
2205
- const hasErrors = stats.errors.length > 0;
3093
+ if (
3094
+ /** @type {NonNullable<StatsCompilation["errors"]>} */
3095
+ (stats.errors).length > 0 ||
3096
+ /** @type {NonNullable<StatsCompilation["warnings"]>} */
3097
+ (stats.warnings).length > 0
3098
+ ) {
3099
+ const hasErrors =
3100
+ /** @type {NonNullable<StatsCompilation["errors"]>} */
3101
+ (stats.errors).length > 0;
2206
3102
 
2207
- if (stats.warnings.length > 0) {
3103
+ if (
3104
+ /** @type {NonNullable<StatsCompilation["warnings"]>} */
3105
+ (stats.warnings).length > 0
3106
+ ) {
2208
3107
  let params;
2209
3108
 
2210
3109
  if (hasErrors) {
@@ -2214,7 +3113,10 @@ class Server {
2214
3113
  this.sendMessage(clients, "warnings", stats.warnings, params);
2215
3114
  }
2216
3115
 
2217
- if (stats.errors.length > 0) {
3116
+ if (
3117
+ /** @type {NonNullable<StatsCompilation["errors"]>} */ (stats.errors)
3118
+ .length > 0
3119
+ ) {
2218
3120
  this.sendMessage(clients, "errors", stats.errors);
2219
3121
  }
2220
3122
  } else {
@@ -2222,6 +3124,10 @@ class Server {
2222
3124
  }
2223
3125
  }
2224
3126
 
3127
+ /**
3128
+ * @param {string | string[]} watchPath
3129
+ * @param {WatchOptions} [watchOptions]
3130
+ */
2225
3131
  watchFiles(watchPath, watchOptions) {
2226
3132
  const chokidar = require("chokidar");
2227
3133
  const watcher = chokidar.watch(watchPath, watchOptions);
@@ -2242,44 +3148,65 @@ class Server {
2242
3148
  this.staticWatchers.push(watcher);
2243
3149
  }
2244
3150
 
2245
- invalidate(callback) {
3151
+ /**
3152
+ * @param {import("webpack-dev-middleware").Callback} [callback]
3153
+ */
3154
+ invalidate(callback = () => {}) {
2246
3155
  if (this.middleware) {
2247
3156
  this.middleware.invalidate(callback);
2248
3157
  }
2249
3158
  }
2250
3159
 
3160
+ /**
3161
+ * @returns {Promise<void>}
3162
+ */
2251
3163
  async start() {
2252
3164
  await this.normalizeOptions();
2253
3165
 
2254
3166
  if (this.options.ipc) {
2255
- await new Promise((resolve, reject) => {
2256
- const net = require("net");
2257
- const socket = new net.Socket();
2258
-
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();
3167
+ await /** @type {Promise<void>} */ (
3168
+ new Promise((resolve, reject) => {
3169
+ const net = require("net");
3170
+ const socket = new net.Socket();
3171
+
3172
+ socket.on(
3173
+ "error",
3174
+ /**
3175
+ * @param {Error & { code?: string }} error
3176
+ */
3177
+ (error) => {
3178
+ if (error.code === "ECONNREFUSED") {
3179
+ // No other server listening on this socket so it can be safely removed
3180
+ fs.unlinkSync(/** @type {string} */ (this.options.ipc));
3181
+
3182
+ resolve();
3183
+
3184
+ return;
3185
+ } else if (error.code === "ENOENT") {
3186
+ resolve();
3187
+
3188
+ return;
3189
+ }
2269
3190
 
2270
- return;
2271
- }
2272
-
2273
- reject(error);
2274
- });
3191
+ reject(error);
3192
+ }
3193
+ );
2275
3194
 
2276
- socket.connect({ path: this.options.ipc }, () => {
2277
- throw new Error(`IPC "${this.options.ipc}" is already used`);
2278
- });
2279
- });
3195
+ socket.connect(
3196
+ { path: /** @type {string} */ (this.options.ipc) },
3197
+ () => {
3198
+ throw new Error(`IPC "${this.options.ipc}" is already used`);
3199
+ }
3200
+ );
3201
+ })
3202
+ );
2280
3203
  } else {
2281
- this.options.host = await Server.getHostname(this.options.host);
2282
- this.options.port = await Server.getFreePort(this.options.port);
3204
+ this.options.host = await Server.getHostname(
3205
+ /** @type {Host} */ (this.options.host)
3206
+ );
3207
+ this.options.port = await Server.getFreePort(
3208
+ /** @type {Port} */ (this.options.port)
3209
+ );
2283
3210
  }
2284
3211
 
2285
3212
  await this.initialize();
@@ -2288,17 +3215,23 @@ class Server {
2288
3215
  ? { path: this.options.ipc }
2289
3216
  : { host: this.options.host, port: this.options.port };
2290
3217
 
2291
- await new Promise((resolve) => {
2292
- this.server.listen(listenOptions, () => {
2293
- resolve();
2294
- });
2295
- });
3218
+ await /** @type {Promise<void>} */ (
3219
+ new Promise((resolve) => {
3220
+ /** @type {import("http").Server} */
3221
+ (this.server).listen(listenOptions, () => {
3222
+ resolve();
3223
+ });
3224
+ })
3225
+ );
2296
3226
 
2297
3227
  if (this.options.ipc) {
2298
3228
  // chmod 666 (rw rw rw)
2299
3229
  const READ_WRITE = 438;
2300
3230
 
2301
- await fs.promises.chmod(this.options.ipc, READ_WRITE);
3231
+ await fs.promises.chmod(
3232
+ /** @type {string} */ (this.options.ipc),
3233
+ READ_WRITE
3234
+ );
2302
3235
  }
2303
3236
 
2304
3237
  if (this.options.webSocketServer) {
@@ -2316,19 +3249,27 @@ class Server {
2316
3249
  }
2317
3250
  }
2318
3251
 
3252
+ /**
3253
+ * @param {(err?: Error) => void} [callback]
3254
+ */
2319
3255
  startCallback(callback = () => {}) {
2320
3256
  this.start()
2321
- .then(() => callback(null), callback)
3257
+ .then(() => callback(), callback)
2322
3258
  .catch(callback);
2323
3259
  }
2324
3260
 
3261
+ /**
3262
+ * @returns {Promise<void>}
3263
+ */
2325
3264
  async stop() {
2326
3265
  if (this.bonjour) {
2327
- await new Promise((resolve) => {
2328
- this.stopBonjour(() => {
2329
- resolve();
2330
- });
2331
- });
3266
+ await /** @type {Promise<void>} */ (
3267
+ new Promise((resolve) => {
3268
+ this.stopBonjour(() => {
3269
+ resolve();
3270
+ });
3271
+ })
3272
+ );
2332
3273
  }
2333
3274
 
2334
3275
  this.webSocketProxies = [];
@@ -2338,48 +3279,60 @@ class Server {
2338
3279
  this.staticWatchers = [];
2339
3280
 
2340
3281
  if (this.webSocketServer) {
2341
- await new Promise((resolve) => {
2342
- this.webSocketServer.implementation.close(() => {
2343
- this.webSocketServer = null;
3282
+ await /** @type {Promise<void>} */ (
3283
+ new Promise((resolve) => {
3284
+ /** @type {WebSocketServerImplementation} */
3285
+ (this.webSocketServer).implementation.close(() => {
3286
+ this.webSocketServer = null;
2344
3287
 
2345
- resolve();
2346
- });
3288
+ resolve();
3289
+ });
2347
3290
 
2348
- for (const client of this.webSocketServer.clients) {
2349
- client.terminate();
2350
- }
3291
+ for (const client of /** @type {WebSocketServerImplementation} */ (
3292
+ this.webSocketServer
3293
+ ).clients) {
3294
+ client.terminate();
3295
+ }
2351
3296
 
2352
- this.webSocketServer.clients = [];
2353
- });
3297
+ /** @type {WebSocketServerImplementation} */
3298
+ (this.webSocketServer).clients = [];
3299
+ })
3300
+ );
2354
3301
  }
2355
3302
 
2356
3303
  if (this.server) {
2357
- await new Promise((resolve) => {
2358
- this.server.close(() => {
2359
- this.server = null;
3304
+ await /** @type {Promise<void>} */ (
3305
+ new Promise((resolve) => {
3306
+ /** @type {import("http").Server} */
3307
+ (this.server).close(() => {
3308
+ this.server = null;
2360
3309
 
2361
- resolve();
2362
- });
3310
+ resolve();
3311
+ });
2363
3312
 
2364
- for (const socket of this.sockets) {
2365
- socket.destroy();
2366
- }
3313
+ for (const socket of this.sockets) {
3314
+ socket.destroy();
3315
+ }
2367
3316
 
2368
- this.sockets = [];
2369
- });
3317
+ this.sockets = [];
3318
+ })
3319
+ );
2370
3320
 
2371
3321
  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
- });
3322
+ await /** @type {Promise<void>} */ (
3323
+ new Promise((resolve, reject) => {
3324
+ /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
3325
+ (this.middleware).close((error) => {
3326
+ if (error) {
3327
+ reject(error);
3328
+
3329
+ return;
3330
+ }
3331
+
3332
+ resolve();
3333
+ });
3334
+ })
3335
+ );
2383
3336
 
2384
3337
  this.middleware = null;
2385
3338
  }
@@ -2392,22 +3345,29 @@ class Server {
2392
3345
  }
2393
3346
  }
2394
3347
 
3348
+ /**
3349
+ * @param {(err?: Error) => void} [callback]
3350
+ */
2395
3351
  stopCallback(callback = () => {}) {
2396
3352
  this.stop()
2397
- .then(() => callback(null), callback)
3353
+ .then(() => callback(), callback)
2398
3354
  .catch(callback);
2399
3355
  }
2400
3356
 
2401
3357
  // TODO remove in the next major release
3358
+ /**
3359
+ * @param {Port} port
3360
+ * @param {Host} hostname
3361
+ * @param {(err?: Error) => void} fn
3362
+ * @returns {void}
3363
+ */
2402
3364
  listen(port, hostname, fn) {
2403
3365
  util.deprecate(
2404
3366
  () => {},
2405
- "'listen' is deprecated. Please use async 'start' or 'startCallback' methods.",
3367
+ "'listen' is deprecated. Please use the async 'start' or 'startCallback' method.",
2406
3368
  "DEP_WEBPACK_DEV_SERVER_LISTEN"
2407
3369
  )();
2408
3370
 
2409
- this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
2410
-
2411
3371
  if (typeof port === "function") {
2412
3372
  fn = port;
2413
3373
  }
@@ -2444,7 +3404,7 @@ class Server {
2444
3404
  this.options.host = hostname;
2445
3405
  }
2446
3406
 
2447
- return this.start()
3407
+ this.start()
2448
3408
  .then(() => {
2449
3409
  if (fn) {
2450
3410
  fn.call(this.server);
@@ -2458,18 +3418,22 @@ class Server {
2458
3418
  });
2459
3419
  }
2460
3420
 
3421
+ /**
3422
+ * @param {(err?: Error) => void} [callback]
3423
+ * @returns {void}
3424
+ */
2461
3425
  // TODO remove in the next major release
2462
3426
  close(callback) {
2463
3427
  util.deprecate(
2464
3428
  () => {},
2465
- "'close' is deprecated. Please use async 'stop' or 'stopCallback' methods.",
3429
+ "'close' is deprecated. Please use the async 'stop' or 'stopCallback' method.",
2466
3430
  "DEP_WEBPACK_DEV_SERVER_CLOSE"
2467
3431
  )();
2468
3432
 
2469
- return this.stop()
3433
+ this.stop()
2470
3434
  .then(() => {
2471
3435
  if (callback) {
2472
- callback(null);
3436
+ callback();
2473
3437
  }
2474
3438
  })
2475
3439
  .catch((error) => {
@@ -2480,48 +3444,4 @@ class Server {
2480
3444
  }
2481
3445
  }
2482
3446
 
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
- });
3447
+ module.exports = Server;