webpack-dev-server 4.5.0 → 4.7.2

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
@@ -6,19 +6,219 @@ const url = require("url");
6
6
  const util = require("util");
7
7
  const fs = require("graceful-fs");
8
8
  const ipaddr = require("ipaddr.js");
9
- const internalIp = require("internal-ip");
9
+ const defaultGateway = require("default-gateway");
10
10
  const express = require("express");
11
11
  const { validate } = require("schema-utils");
12
12
  const schema = require("./options.json");
13
13
 
14
+ /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
15
+ /** @typedef {import("webpack").Compiler} Compiler */
16
+ /** @typedef {import("webpack").MultiCompiler} MultiCompiler */
17
+ /** @typedef {import("webpack").Configuration} WebpackConfiguration */
18
+ /** @typedef {import("webpack").StatsOptions} StatsOptions */
19
+ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */
20
+ /** @typedef {import("webpack").Stats} Stats */
21
+ /** @typedef {import("webpack").MultiStats} MultiStats */
22
+ /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
23
+ /** @typedef {import("express").Request} Request */
24
+ /** @typedef {import("express").Response} Response */
25
+ /** @typedef {import("express").NextFunction} NextFunction */
26
+ /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
27
+ /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
28
+ /** @typedef {import("chokidar").WatchOptions} WatchOptions */
29
+ /** @typedef {import("chokidar").FSWatcher} FSWatcher */
30
+ /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
31
+ /** @typedef {import("bonjour").Bonjour} Bonjour */
32
+ /** @typedef {import("bonjour").BonjourOptions} BonjourOptions */
33
+ /** @typedef {import("http-proxy-middleware").RequestHandler} RequestHandler */
34
+ /** @typedef {import("http-proxy-middleware").Options} HttpProxyMiddlewareOptions */
35
+ /** @typedef {import("http-proxy-middleware").Filter} HttpProxyMiddlewareOptionsFilter */
36
+ /** @typedef {import("serve-index").Options} ServeIndexOptions */
37
+ /** @typedef {import("serve-static").ServeStaticOptions} ServeStaticOptions */
38
+ /** @typedef {import("ipaddr.js").IPv4} IPv4 */
39
+ /** @typedef {import("ipaddr.js").IPv6} IPv6 */
40
+ /** @typedef {import("net").Socket} Socket */
41
+ /** @typedef {import("http").IncomingMessage} IncomingMessage */
42
+ /** @typedef {import("open").Options} OpenOptions */
43
+
44
+ /** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
45
+
46
+ /**
47
+ * @template Request, Response
48
+ * @typedef {import("webpack-dev-middleware").Options<Request, Response>} DevMiddlewareOptions
49
+ */
50
+
51
+ /**
52
+ * @template Request, Response
53
+ * @typedef {import("webpack-dev-middleware").Context<Request, Response>} DevMiddlewareContext
54
+ */
55
+
56
+ /**
57
+ * @typedef {"local-ip" | "local-ipv4" | "local-ipv6" | string} Host
58
+ */
59
+
60
+ /**
61
+ * @typedef {number | string | "auto"} Port
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} WatchFiles
66
+ * @property {string | string[]} paths
67
+ * @property {WatchOptions & { aggregateTimeout?: number, ignored?: string | RegExp | string[], poll?: number | boolean }} [options]
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} Static
72
+ * @property {string} [directory]
73
+ * @property {string | string[]} [publicPath]
74
+ * @property {boolean | ServeIndexOptions} [serveIndex]
75
+ * @property {ServeStaticOptions} [staticOptions]
76
+ * @property {boolean | WatchOptions & { aggregateTimeout?: number, ignored?: string | RegExp | string[], poll?: number | boolean }} [watch]
77
+ */
78
+
79
+ /**
80
+ * @typedef {Object} NormalizedStatic
81
+ * @property {string} directory
82
+ * @property {string[]} publicPath
83
+ * @property {false | ServeIndexOptions} serveIndex
84
+ * @property {ServeStaticOptions} staticOptions
85
+ * @property {false | WatchOptions} watch
86
+ */
87
+
88
+ /**
89
+ * @typedef {Object} ServerConfiguration
90
+ * @property {"http" | "https" | "spdy" | string} [type]
91
+ * @property {ServerOptions} [options]
92
+ */
93
+
94
+ /**
95
+ * @typedef {Object} WebSocketServerConfiguration
96
+ * @property {"sockjs" | "ws" | string | Function} [type]
97
+ * @property {Record<string, any>} [options]
98
+ */
99
+
100
+ /**
101
+ * @typedef {(import("ws").WebSocket | import("sockjs").Connection & { send: import("ws").WebSocket["send"], terminate: import("ws").WebSocket["terminate"], ping: import("ws").WebSocket["ping"] }) & { isAlive?: boolean }} ClientConnection
102
+ */
103
+
104
+ /**
105
+ * @typedef {import("ws").WebSocketServer | import("sockjs").Server & { close: import("ws").WebSocketServer["close"] }} WebSocketServer
106
+ */
107
+
108
+ /**
109
+ * @typedef {{ implementation: WebSocketServer, clients: ClientConnection[] }} WebSocketServerImplementation
110
+ */
111
+
112
+ /**
113
+ * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap
114
+ */
115
+
116
+ /**
117
+ * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray
118
+ */
119
+
120
+ /**
121
+ * @callback ByPass
122
+ * @param {Request} req
123
+ * @param {Response} res
124
+ * @param {ProxyConfigArray} proxyConfig
125
+ */
126
+
127
+ /**
128
+ * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray
129
+ */
130
+
131
+ /**
132
+ * @typedef {Object} OpenApp
133
+ * @property {string} [name]
134
+ * @property {string[]} [arguments]
135
+ */
136
+
137
+ /**
138
+ * @typedef {Object} Open
139
+ * @property {string | string[] | OpenApp} [app]
140
+ * @property {string | string[]} [target]
141
+ */
142
+
143
+ /**
144
+ * @typedef {Object} NormalizedOpen
145
+ * @property {string} target
146
+ * @property {import("open").Options} options
147
+ */
148
+
149
+ /**
150
+ * @typedef {Object} WebSocketURL
151
+ * @property {string} [hostname]
152
+ * @property {string} [password]
153
+ * @property {string} [pathname]
154
+ * @property {number | string} [port]
155
+ * @property {string} [protocol]
156
+ * @property {string} [username]
157
+ */
158
+
159
+ /**
160
+ * @typedef {Object} ClientConfiguration
161
+ * @property {"log" | "info" | "warn" | "error" | "none" | "verbose"} [logging]
162
+ * @property {boolean | { warnings?: boolean, errors?: boolean }} [overlay]
163
+ * @property {boolean} [progress]
164
+ * @property {boolean | number} [reconnect]
165
+ * @property {"ws" | "sockjs" | string} [webSocketTransport]
166
+ * @property {string | WebSocketURL} [webSocketURL]
167
+ */
168
+
169
+ /**
170
+ * @typedef {Array<{ key: string; value: string }> | Record<string, string | string[]>} Headers
171
+ */
172
+
173
+ /**
174
+ * @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler} Middleware
175
+ */
176
+
177
+ /**
178
+ * @typedef {Object} Configuration
179
+ * @property {boolean | string} [ipc]
180
+ * @property {Host} [host]
181
+ * @property {Port} [port]
182
+ * @property {boolean | "only"} [hot]
183
+ * @property {boolean} [liveReload]
184
+ * @property {DevMiddlewareOptions<Request, Response>} [devMiddleware]
185
+ * @property {boolean} [compress]
186
+ * @property {boolean} [magicHtml]
187
+ * @property {"auto" | "all" | string | string[]} [allowedHosts]
188
+ * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback]
189
+ * @property {boolean} [setupExitSignals]
190
+ * @property {boolean | BonjourOptions} [bonjour]
191
+ * @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
192
+ * @property {boolean | string | Static | Array<string | Static>} [static]
193
+ * @property {boolean | ServerOptions} [https]
194
+ * @property {boolean} [http2]
195
+ * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server]
196
+ * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
197
+ * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy]
198
+ * @property {boolean | string | Open | Array<string | Open>} [open]
199
+ * @property {boolean} [setupExitSignals]
200
+ * @property {boolean | ClientConfiguration} [client]
201
+ * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
202
+ * @property {(devServer: Server) => void} [onAfterSetupMiddleware]
203
+ * @property {(devServer: Server) => void} [onBeforeSetupMiddleware]
204
+ * @property {(devServer: Server) => void} [onListening]
205
+ * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
206
+ */
207
+
14
208
  if (!process.env.WEBPACK_SERVE) {
209
+ // TODO fix me in the next major release
210
+ // @ts-ignore
15
211
  process.env.WEBPACK_SERVE = true;
16
212
  }
17
213
 
18
214
  class Server {
215
+ /**
216
+ * @param {Configuration | Compiler | MultiCompiler} options
217
+ * @param {Compiler | MultiCompiler | Configuration} compiler
218
+ */
19
219
  constructor(options = {}, compiler) {
20
220
  // TODO: remove this after plugin support is published
21
- if (options.hooks) {
221
+ if (/** @type {Compiler | MultiCompiler} */ (options).hooks) {
22
222
  util.deprecate(
23
223
  () => {},
24
224
  "Using 'compiler' as the first argument is deprecated. Please use 'options' as the first argument and 'compiler' as the second argument.",
@@ -28,18 +228,65 @@ class Server {
28
228
  [options = {}, compiler] = [compiler, options];
29
229
  }
30
230
 
31
- validate(schema, options, "webpack Dev Server");
231
+ validate(/** @type {Schema} */ (schema), options, {
232
+ name: "Dev Server",
233
+ baseDataPath: "options",
234
+ });
32
235
 
33
- this.options = options;
236
+ this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler);
237
+ /**
238
+ * @type {ReturnType<Compiler["getInfrastructureLogger"]>}
239
+ * */
240
+ this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
241
+ this.options = /** @type {Configuration} */ (options);
242
+ /**
243
+ * @type {FSWatcher[]}
244
+ */
34
245
  this.staticWatchers = [];
246
+ /**
247
+ * @private
248
+ * @type {{ name: string | symbol, listener: (...args: any[]) => void}[] }}
249
+ */
35
250
  this.listeners = [];
36
251
  // Keep track of websocket proxies for external websocket upgrade.
252
+ /**
253
+ * @private
254
+ * @type {RequestHandler[]}
255
+ */
37
256
  this.webSocketProxies = [];
257
+ /**
258
+ * @type {Socket[]}
259
+ */
38
260
  this.sockets = [];
39
- this.compiler = compiler;
40
- this.currentHash = null;
261
+ /**
262
+ * @private
263
+ * @type {string | undefined}
264
+ */
265
+ // eslint-disable-next-line no-undefined
266
+ this.currentHash = undefined;
267
+ }
268
+
269
+ // TODO compatibility with webpack v4, remove it after drop
270
+ static get cli() {
271
+ return {
272
+ get getArguments() {
273
+ return () => require("../bin/cli-flags");
274
+ },
275
+ get processArguments() {
276
+ return require("../bin/process-arguments");
277
+ },
278
+ };
279
+ }
280
+
281
+ static get schema() {
282
+ return schema;
41
283
  }
42
284
 
285
+ /**
286
+ * @private
287
+ * @returns {StatsOptions}
288
+ * @constructor
289
+ */
43
290
  static get DEFAULT_STATS() {
44
291
  return {
45
292
  all: false,
@@ -50,7 +297,10 @@ class Server {
50
297
  };
51
298
  }
52
299
 
53
- // eslint-disable-next-line class-methods-use-this
300
+ /**
301
+ * @param {string} URL
302
+ * @returns {boolean}
303
+ */
54
304
  static isAbsoluteURL(URL) {
55
305
  // Don't match Windows paths `c:\`
56
306
  if (/^[a-zA-Z]:\\/.test(URL)) {
@@ -62,18 +312,81 @@ class Server {
62
312
  return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
63
313
  }
64
314
 
315
+ /**
316
+ * @param {string} gateway
317
+ * @returns {string | undefined}
318
+ */
319
+ static findIp(gateway) {
320
+ const gatewayIp = ipaddr.parse(gateway);
321
+
322
+ // Look for the matching interface in all local interfaces.
323
+ for (const addresses of Object.values(os.networkInterfaces())) {
324
+ for (const { cidr } of /** @type {NetworkInterfaceInfo[]} */ (
325
+ addresses
326
+ )) {
327
+ const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));
328
+
329
+ if (
330
+ net[0] &&
331
+ net[0].kind() === gatewayIp.kind() &&
332
+ gatewayIp.match(net)
333
+ ) {
334
+ return net[0].toString();
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ /**
341
+ * @param {"v4" | "v6"} family
342
+ * @returns {Promise<string | undefined>}
343
+ */
344
+ static async internalIP(family) {
345
+ try {
346
+ const { gateway } = await defaultGateway[family]();
347
+ return Server.findIp(gateway);
348
+ } catch {
349
+ // ignore
350
+ }
351
+ }
352
+
353
+ /**
354
+ * @param {"v4" | "v6"} family
355
+ * @returns {string | undefined}
356
+ */
357
+ static internalIPSync(family) {
358
+ try {
359
+ const { gateway } = defaultGateway[family].sync();
360
+ return Server.findIp(gateway);
361
+ } catch {
362
+ // ignore
363
+ }
364
+ }
365
+
366
+ /**
367
+ * @param {Host} hostname
368
+ * @returns {Promise<string>}
369
+ */
65
370
  static async getHostname(hostname) {
66
371
  if (hostname === "local-ip") {
67
- return (await internalIp.v4()) || (await internalIp.v6()) || "0.0.0.0";
372
+ return (
373
+ (await Server.internalIP("v4")) ||
374
+ (await Server.internalIP("v6")) ||
375
+ "0.0.0.0"
376
+ );
68
377
  } else if (hostname === "local-ipv4") {
69
- return (await internalIp.v4()) || "0.0.0.0";
378
+ return (await Server.internalIP("v4")) || "0.0.0.0";
70
379
  } else if (hostname === "local-ipv6") {
71
- return (await internalIp.v6()) || "::";
380
+ return (await Server.internalIP("v6")) || "::";
72
381
  }
73
382
 
74
383
  return hostname;
75
384
  }
76
385
 
386
+ /**
387
+ * @param {Port} port
388
+ * @returns {Promise<number | string>}
389
+ */
77
390
  static async getFreePort(port) {
78
391
  if (typeof port !== "undefined" && port !== null && port !== "auto") {
79
392
  return port;
@@ -82,21 +395,32 @@ class Server {
82
395
  const pRetry = require("p-retry");
83
396
  const portfinder = require("portfinder");
84
397
 
85
- 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;
86
402
 
87
403
  // Try to find unused port and listen on it for 3 times,
88
404
  // if port is not specified in options.
89
405
  const defaultPortRetry =
90
- 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;
91
409
 
92
410
  return pRetry(() => portfinder.getPortPromise(), {
93
411
  retries: defaultPortRetry,
94
412
  });
95
413
  }
96
414
 
415
+ /**
416
+ * @returns {string}
417
+ */
97
418
  static findCacheDir() {
98
419
  const cwd = process.cwd();
99
420
 
421
+ /**
422
+ * @type {string | undefined}
423
+ */
100
424
  let dir = cwd;
101
425
 
102
426
  for (;;) {
@@ -127,7 +451,14 @@ class Server {
127
451
  return path.resolve(dir, "node_modules/.cache/webpack-dev-server");
128
452
  }
129
453
 
454
+ /**
455
+ * @private
456
+ * @param {Compiler} compiler
457
+ */
130
458
  addAdditionalEntries(compiler) {
459
+ /**
460
+ * @type {string[]}
461
+ */
131
462
  const additionalEntries = [];
132
463
 
133
464
  const isWebTarget = compiler.options.externalsPresets
@@ -141,38 +472,44 @@ class Server {
141
472
  // eslint-disable-next-line no-undefined
142
473
  undefined,
143
474
  null,
144
- ].includes(compiler.options.target);
475
+ ].includes(/** @type {string} */ (compiler.options.target));
145
476
 
146
477
  // TODO maybe empty empty client
147
478
  if (this.options.client && isWebTarget) {
148
- let webSocketURL = "";
479
+ let webSocketURLStr = "";
480
+
149
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);
150
491
  const searchParams = new URLSearchParams();
151
492
 
152
- /** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
493
+ /** @type {string} */
153
494
  let protocol;
154
495
 
155
496
  // We are proxying dev server and need to specify custom `hostname`
156
- if (typeof this.options.client.webSocketURL.protocol !== "undefined") {
157
- protocol = this.options.client.webSocketURL.protocol;
497
+ if (typeof webSocketURL.protocol !== "undefined") {
498
+ protocol = webSocketURL.protocol;
158
499
  } else {
159
- protocol = this.options.server.type === "http" ? "ws:" : "wss:";
500
+ protocol =
501
+ /** @type {ServerConfiguration} */
502
+ (this.options.server).type === "http" ? "ws:" : "wss:";
160
503
  }
161
504
 
162
505
  searchParams.set("protocol", protocol);
163
506
 
164
- if (typeof this.options.client.webSocketURL.username !== "undefined") {
165
- searchParams.set(
166
- "username",
167
- this.options.client.webSocketURL.username
168
- );
507
+ if (typeof webSocketURL.username !== "undefined") {
508
+ searchParams.set("username", webSocketURL.username);
169
509
  }
170
510
 
171
- if (typeof this.options.client.webSocketURL.password !== "undefined") {
172
- searchParams.set(
173
- "password",
174
- this.options.client.webSocketURL.password
175
- );
511
+ if (typeof webSocketURL.password !== "undefined") {
512
+ searchParams.set("password", webSocketURL.password);
176
513
  }
177
514
 
178
515
  /** @type {string} */
@@ -180,18 +517,18 @@ class Server {
180
517
 
181
518
  // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
182
519
  // TODO show warning about this
183
- const isSockJSType = this.options.webSocketServer.type === "sockjs";
520
+ const isSockJSType = webSocketServer.type === "sockjs";
184
521
 
185
522
  // We are proxying dev server and need to specify custom `hostname`
186
- if (typeof this.options.client.webSocketURL.hostname !== "undefined") {
187
- hostname = this.options.client.webSocketURL.hostname;
523
+ if (typeof webSocketURL.hostname !== "undefined") {
524
+ hostname = webSocketURL.hostname;
188
525
  }
189
526
  // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
190
527
  else if (
191
- typeof this.options.webSocketServer.options.host !== "undefined" &&
528
+ typeof webSocketServer.options.host !== "undefined" &&
192
529
  !isSockJSType
193
530
  ) {
194
- hostname = this.options.webSocketServer.options.host;
531
+ hostname = webSocketServer.options.host;
195
532
  }
196
533
  // The `host` option is specified
197
534
  else if (typeof this.options.host !== "undefined") {
@@ -208,15 +545,15 @@ class Server {
208
545
  let port;
209
546
 
210
547
  // We are proxying dev server and need to specify custom `port`
211
- if (typeof this.options.client.webSocketURL.port !== "undefined") {
212
- port = this.options.client.webSocketURL.port;
548
+ if (typeof webSocketURL.port !== "undefined") {
549
+ port = webSocketURL.port;
213
550
  }
214
551
  // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
215
552
  else if (
216
- typeof this.options.webSocketServer.options.port !== "undefined" &&
553
+ typeof webSocketServer.options.port !== "undefined" &&
217
554
  !isSockJSType
218
555
  ) {
219
- port = this.options.webSocketServer.options.port;
556
+ port = webSocketServer.options.port;
220
557
  }
221
558
  // The `port` option is specified
222
559
  else if (typeof this.options.port === "number") {
@@ -240,47 +577,47 @@ class Server {
240
577
  let pathname = "";
241
578
 
242
579
  // We are proxying dev server and need to specify custom `pathname`
243
- if (typeof this.options.client.webSocketURL.pathname !== "undefined") {
244
- pathname = this.options.client.webSocketURL.pathname;
580
+ if (typeof webSocketURL.pathname !== "undefined") {
581
+ pathname = webSocketURL.pathname;
245
582
  }
246
583
  // Web socket server works on custom `path`
247
584
  else if (
248
- typeof this.options.webSocketServer.options.prefix !== "undefined" ||
249
- typeof this.options.webSocketServer.options.path !== "undefined"
585
+ typeof webSocketServer.options.prefix !== "undefined" ||
586
+ typeof webSocketServer.options.path !== "undefined"
250
587
  ) {
251
588
  pathname =
252
- this.options.webSocketServer.options.prefix ||
253
- this.options.webSocketServer.options.path;
589
+ webSocketServer.options.prefix || webSocketServer.options.path;
254
590
  }
255
591
 
256
592
  searchParams.set("pathname", pathname);
257
593
 
258
- if (typeof this.options.client.logging !== "undefined") {
259
- 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);
260
598
  }
261
599
 
262
- if (typeof this.options.client.reconnect !== "undefined") {
263
- 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
+ );
264
607
  }
265
608
 
266
- webSocketURL = searchParams.toString();
609
+ webSocketURLStr = searchParams.toString();
267
610
  }
268
611
 
269
612
  additionalEntries.push(
270
- `${require.resolve("../client/index.js")}?${webSocketURL}`
613
+ `${require.resolve("../client/index.js")}?${webSocketURLStr}`
271
614
  );
272
615
  }
273
616
 
274
- if (this.options.hot) {
275
- let hotEntry;
276
-
277
- if (this.options.hot === "only") {
278
- hotEntry = require.resolve("webpack/hot/only-dev-server");
279
- } else if (this.options.hot) {
280
- hotEntry = require.resolve("webpack/hot/dev-server");
281
- }
282
-
283
- 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"));
284
621
  }
285
622
 
286
623
  const webpack = compiler.webpack || require("webpack");
@@ -298,9 +635,9 @@ class Server {
298
635
  else {
299
636
  /**
300
637
  * prependEntry Method for webpack 4
301
- * @param {Entry} originalEntry
302
- * @param {Entry} newAdditionalEntries
303
- * @returns {Entry}
638
+ * @param {any} originalEntry
639
+ * @param {any} newAdditionalEntries
640
+ * @returns {any}
304
641
  */
305
642
  const prependEntry = (originalEntry, newAdditionalEntries) => {
306
643
  if (typeof originalEntry === "function") {
@@ -329,7 +666,7 @@ class Server {
329
666
 
330
667
  // in this case, entry is a string or an array.
331
668
  // make sure that we do not add duplicates.
332
- /** @type {Entry} */
669
+ /** @type {any} */
333
670
  const entriesClone = additionalEntries.slice(0);
334
671
 
335
672
  [].concat(originalEntry).forEach((newEntry) => {
@@ -346,73 +683,201 @@ class Server {
346
683
  additionalEntries
347
684
  );
348
685
  compiler.hooks.entryOption.call(
349
- compiler.options.context,
686
+ /** @type {string} */ (compiler.options.context),
350
687
  compiler.options.entry
351
688
  );
352
689
  }
353
690
  }
354
691
 
692
+ /**
693
+ * @private
694
+ * @returns {Compiler["options"]}
695
+ */
355
696
  getCompilerOptions() {
356
- if (typeof this.compiler.compilers !== "undefined") {
357
- if (this.compiler.compilers.length === 1) {
358
- 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
+ );
359
706
  }
360
707
 
361
708
  // Configuration with the `devServer` options
362
- const compilerWithDevServer = this.compiler.compilers.find(
363
- (config) => config.options.devServer
364
- );
709
+ const compilerWithDevServer =
710
+ /** @type {MultiCompiler} */
711
+ (this.compiler).compilers.find((config) => config.options.devServer);
365
712
 
366
713
  if (compilerWithDevServer) {
367
714
  return compilerWithDevServer.options;
368
715
  }
369
716
 
370
717
  // Configuration with `web` preset
371
- const compilerWithWebPreset = this.compiler.compilers.find(
372
- (config) =>
373
- (config.options.externalsPresets &&
374
- config.options.externalsPresets.web) ||
375
- [
376
- "web",
377
- "webworker",
378
- "electron-preload",
379
- "electron-renderer",
380
- "node-webkit",
381
- // eslint-disable-next-line no-undefined
382
- undefined,
383
- null,
384
- ].includes(config.options.target)
385
- );
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
+ );
386
735
 
387
736
  if (compilerWithWebPreset) {
388
737
  return compilerWithWebPreset.options;
389
738
  }
390
739
 
391
740
  // Fallback
392
- return this.compiler.compilers[0].options;
741
+ return /** @type {MultiCompiler} */ (this.compiler).compilers[0].options;
393
742
  }
394
743
 
395
- return this.compiler.options;
744
+ return /** @type {Compiler} */ (this.compiler).options;
396
745
  }
397
746
 
398
- // eslint-disable-next-line class-methods-use-this
747
+ /**
748
+ * @private
749
+ * @returns {Promise<void>}
750
+ */
399
751
  async normalizeOptions() {
400
752
  const { options } = this;
401
-
402
- if (!this.logger) {
403
- this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
404
- }
405
-
406
753
  const compilerOptions = this.getCompilerOptions();
407
754
  // TODO remove `{}` after drop webpack v4 support
408
- const watchOptions = compilerOptions.watchOptions || {};
409
- const defaultOptionsForStatic = {
410
- directory: path.join(process.cwd(), "public"),
411
- staticOptions: {},
412
- publicPath: ["/"],
413
- serveIndex: { icons: true },
414
- // Respect options from compiler watchOptions
415
- watch: watchOptions,
755
+ const compilerWatchOptions = compilerOptions.watchOptions || {};
756
+ /**
757
+ * @param {WatchOptions & WebpackConfiguration["watchOptions"]} watchOptions
758
+ * @returns {WatchOptions}
759
+ */
760
+ const getWatchOptions = (watchOptions = {}) => {
761
+ const getPolling = () => {
762
+ if (typeof watchOptions.usePolling !== "undefined") {
763
+ return watchOptions.usePolling;
764
+ }
765
+
766
+ if (typeof watchOptions.poll !== "undefined") {
767
+ return Boolean(watchOptions.poll);
768
+ }
769
+
770
+ if (typeof compilerWatchOptions.poll !== "undefined") {
771
+ return Boolean(compilerWatchOptions.poll);
772
+ }
773
+
774
+ return false;
775
+ };
776
+ const getInterval = () => {
777
+ if (typeof watchOptions.interval !== "undefined") {
778
+ return watchOptions.interval;
779
+ }
780
+
781
+ if (typeof watchOptions.poll === "number") {
782
+ return watchOptions.poll;
783
+ }
784
+
785
+ if (typeof compilerWatchOptions.poll === "number") {
786
+ return compilerWatchOptions.poll;
787
+ }
788
+ };
789
+
790
+ const usePolling = getPolling();
791
+ const interval = getInterval();
792
+ const { poll, ...rest } = watchOptions;
793
+
794
+ return {
795
+ ignoreInitial: true,
796
+ persistent: true,
797
+ followSymlinks: false,
798
+ atomic: false,
799
+ alwaysStat: true,
800
+ ignorePermissionErrors: true,
801
+ // Respect options from compiler watchOptions
802
+ usePolling,
803
+ interval,
804
+ ignored: watchOptions.ignored,
805
+ // TODO: we respect these options for all watch options and allow developers to pass them to chokidar, but chokidar doesn't have these options maybe we need revisit that in future
806
+ ...rest,
807
+ };
808
+ };
809
+ /**
810
+ * @param {string | Static | undefined} [optionsForStatic]
811
+ * @returns {NormalizedStatic}
812
+ */
813
+ const getStaticItem = (optionsForStatic) => {
814
+ const getDefaultStaticOptions = () => {
815
+ return {
816
+ directory: path.join(process.cwd(), "public"),
817
+ staticOptions: {},
818
+ publicPath: ["/"],
819
+ serveIndex: { icons: true },
820
+ watch: getWatchOptions(),
821
+ };
822
+ };
823
+
824
+ /** @type {NormalizedStatic} */
825
+ let item;
826
+
827
+ if (typeof optionsForStatic === "undefined") {
828
+ item = getDefaultStaticOptions();
829
+ } else if (typeof optionsForStatic === "string") {
830
+ item = {
831
+ ...getDefaultStaticOptions(),
832
+ directory: optionsForStatic,
833
+ };
834
+ } else {
835
+ const def = getDefaultStaticOptions();
836
+
837
+ item = {
838
+ directory:
839
+ typeof optionsForStatic.directory !== "undefined"
840
+ ? optionsForStatic.directory
841
+ : def.directory,
842
+ // TODO: do merge in the next major release
843
+ staticOptions:
844
+ typeof optionsForStatic.staticOptions !== "undefined"
845
+ ? optionsForStatic.staticOptions
846
+ : def.staticOptions,
847
+ publicPath:
848
+ // eslint-disable-next-line no-nested-ternary
849
+ typeof optionsForStatic.publicPath !== "undefined"
850
+ ? Array.isArray(optionsForStatic.publicPath)
851
+ ? optionsForStatic.publicPath
852
+ : [optionsForStatic.publicPath]
853
+ : def.publicPath,
854
+ // TODO: do merge in the next major release
855
+ serveIndex:
856
+ // eslint-disable-next-line no-nested-ternary
857
+ typeof optionsForStatic.serveIndex !== "undefined"
858
+ ? typeof optionsForStatic.serveIndex === "boolean" &&
859
+ optionsForStatic.serveIndex
860
+ ? def.serveIndex
861
+ : optionsForStatic.serveIndex
862
+ : def.serveIndex,
863
+ watch:
864
+ // eslint-disable-next-line no-nested-ternary
865
+ typeof optionsForStatic.watch !== "undefined"
866
+ ? // eslint-disable-next-line no-nested-ternary
867
+ typeof optionsForStatic.watch === "boolean"
868
+ ? optionsForStatic.watch
869
+ ? def.watch
870
+ : false
871
+ : getWatchOptions(optionsForStatic.watch)
872
+ : def.watch,
873
+ };
874
+ }
875
+
876
+ if (Server.isAbsoluteURL(item.directory)) {
877
+ throw new Error("Using a URL as static.directory is not supported");
878
+ }
879
+
880
+ return item;
416
881
  };
417
882
 
418
883
  if (typeof options.allowedHosts === "undefined") {
@@ -524,14 +989,21 @@ class Server {
524
989
  const isHTTPs = Boolean(options.https);
525
990
  const isSPDY = Boolean(options.http2);
526
991
 
527
- 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) {
528
1002
  // TODO: remove in the next major release
529
1003
  util.deprecate(
530
1004
  () => {},
531
- `'${
532
- isHTTPs ? "https" : "http2"
533
- }' option is deprecated. Please use the 'server' option.`,
534
- `DEP_WEBPACK_DEV_SERVER_${isHTTPs ? "HTTPS" : "HTTP2"}`
1005
+ "'http2' option is deprecated. Please use the 'server' option.",
1006
+ "DEP_WEBPACK_DEV_SERVER_HTTP2"
535
1007
  )();
536
1008
  }
537
1009
 
@@ -542,7 +1014,7 @@ class Server {
542
1014
  ? options.server
543
1015
  : // eslint-disable-next-line no-nested-ternary
544
1016
  typeof (options.server || {}).type === "string"
545
- ? options.server.type
1017
+ ? /** @type {ServerConfiguration} */ (options.server).type || "http"
546
1018
  : // eslint-disable-next-line no-nested-ternary
547
1019
  isSPDY
548
1020
  ? "spdy"
@@ -550,33 +1022,64 @@ class Server {
550
1022
  ? "https"
551
1023
  : "http",
552
1024
  options: {
553
- ...options.https,
554
- ...(options.server || {}).options,
1025
+ .../** @type {ServerOptions} */ (options.https),
1026
+ .../** @type {ServerConfiguration} */ (options.server || {}).options,
555
1027
  },
556
1028
  };
557
1029
 
558
1030
  if (
559
1031
  options.server.type === "spdy" &&
560
- typeof options.server.options.spdy === "undefined"
1032
+ typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
1033
+ "undefined"
561
1034
  ) {
562
- options.server.options.spdy = {
1035
+ /** @type {ServerOptions} */
1036
+ (options.server.options).spdy = {
563
1037
  protocols: ["h2", "http/1.1"],
564
1038
  };
565
1039
  }
566
1040
 
567
1041
  if (options.server.type === "https" || options.server.type === "spdy") {
568
- if (typeof options.server.options.requestCert === "undefined") {
569
- 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;
570
1049
  }
571
1050
 
572
- // TODO remove the `cacert` option in favor `ca` in the next major release
573
- for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) {
574
- 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
+ ) {
575
1061
  // eslint-disable-next-line no-continue
576
1062
  continue;
577
1063
  }
578
1064
 
579
- 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
+ */
580
1083
  const readFile = (item) => {
581
1084
  if (
582
1085
  Buffer.isBuffer(item) ||
@@ -599,14 +1102,18 @@ class Server {
599
1102
  }
600
1103
  };
601
1104
 
602
- options.server.options[property] = Array.isArray(value)
1105
+ /** @type {any} */
1106
+ (options.server.options)[property] = Array.isArray(value)
603
1107
  ? value.map((item) => readFile(item))
604
1108
  : readFile(value);
605
1109
  }
606
1110
 
607
1111
  let fakeCert;
608
1112
 
609
- 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
+ ) {
610
1117
  const certificateDir = Server.findCacheDir();
611
1118
  const certificatePath = path.join(certificateDir, "server.pem");
612
1119
  let certificateExists;
@@ -621,11 +1128,10 @@ class Server {
621
1128
  if (certificateExists) {
622
1129
  const certificateTtl = 1000 * 60 * 60 * 24;
623
1130
  const certificateStat = await fs.promises.stat(certificatePath);
624
-
625
- const now = new Date();
1131
+ const now = Number(new Date());
626
1132
 
627
1133
  // cert is more than 30 days old, kill it with fire
628
- if ((now - certificateStat.ctime) / certificateTtl > 30) {
1134
+ if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
629
1135
  const del = require("del");
630
1136
 
631
1137
  this.logger.info(
@@ -641,6 +1147,7 @@ class Server {
641
1147
  if (!certificateExists) {
642
1148
  this.logger.info("Generating SSL certificate...");
643
1149
 
1150
+ // @ts-ignore
644
1151
  const selfsigned = require("selfsigned");
645
1152
  const attributes = [{ name: "commonName", value: "localhost" }];
646
1153
  const pems = selfsigned.generate(attributes, {
@@ -721,20 +1228,37 @@ class Server {
721
1228
  this.logger.info(`SSL certificate: ${certificatePath}`);
722
1229
  }
723
1230
 
724
- if (options.server.options.cacert) {
725
- 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) {
726
1237
  this.logger.warn(
727
1238
  "Do not specify 'ca' and 'cacert' options together, the 'ca' option will be used."
728
1239
  );
729
1240
  } else {
730
- 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;
731
1245
  }
732
1246
 
733
- delete options.server.options.cacert;
1247
+ delete (
1248
+ /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ (
1249
+ options.server.options
1250
+ ).cacert
1251
+ );
734
1252
  }
735
1253
 
736
- options.server.options.key = options.server.options.key || fakeCert;
737
- 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;
738
1262
  }
739
1263
 
740
1264
  if (typeof options.ipc === "boolean") {
@@ -753,6 +1277,11 @@ class Server {
753
1277
 
754
1278
  // https://github.com/webpack/webpack-dev-server/issues/1990
755
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
756
1285
  const getOpenItemsFromObject = ({ target, ...rest }) => {
757
1286
  const normalizedOptions = { ...defaultOpenOptions, ...rest };
758
1287
 
@@ -774,14 +1303,25 @@ class Server {
774
1303
  };
775
1304
 
776
1305
  if (typeof options.open === "undefined") {
777
- options.open = [];
1306
+ /** @type {NormalizedOpen[]} */
1307
+ (options.open) = [];
778
1308
  } else if (typeof options.open === "boolean") {
779
- options.open = options.open
780
- ? [{ target: "<url>", options: defaultOpenOptions }]
1309
+ /** @type {NormalizedOpen[]} */
1310
+ (options.open) = options.open
1311
+ ? [
1312
+ {
1313
+ target: "<url>",
1314
+ options: /** @type {OpenOptions} */ (defaultOpenOptions),
1315
+ },
1316
+ ]
781
1317
  : [];
782
1318
  } else if (typeof options.open === "string") {
783
- options.open = [{ target: options.open, options: defaultOpenOptions }];
1319
+ /** @type {NormalizedOpen[]} */
1320
+ (options.open) = [{ target: options.open, options: defaultOpenOptions }];
784
1321
  } else if (Array.isArray(options.open)) {
1322
+ /**
1323
+ * @type {NormalizedOpen[]}
1324
+ */
785
1325
  const result = [];
786
1326
 
787
1327
  options.open.forEach((item) => {
@@ -794,9 +1334,29 @@ class Server {
794
1334
  result.push(...getOpenItemsFromObject(item));
795
1335
  });
796
1336
 
797
- options.open = result;
1337
+ /** @type {NormalizedOpen[]} */
1338
+ (options.open) = result;
798
1339
  } else {
799
- 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
+ )();
800
1360
  }
801
1361
 
802
1362
  if (typeof options.port === "string" && options.port !== "auto") {
@@ -820,61 +1380,91 @@ class Server {
820
1380
  Object.prototype.hasOwnProperty.call(options.proxy, "target") ||
821
1381
  Object.prototype.hasOwnProperty.call(options.proxy, "router")
822
1382
  ) {
823
- options.proxy = [options.proxy];
1383
+ /** @type {ProxyArray} */
1384
+ (options.proxy) = [/** @type {ProxyConfigMap} */ (options.proxy)];
824
1385
  } else {
825
- options.proxy = Object.keys(options.proxy).map((context) => {
826
- let proxyOptions;
827
- // For backwards compatibility reasons.
828
- const correctedContext = context
829
- .replace(/^\*$/, "**")
830
- .replace(/\/\*$/, "");
831
-
832
- if (typeof options.proxy[context] === "string") {
833
- proxyOptions = {
834
- context: correctedContext,
835
- target: options.proxy[context],
836
- };
837
- } else {
838
- proxyOptions = { ...options.proxy[context] };
839
- 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;
840
1419
  }
841
-
842
- return proxyOptions;
843
- });
1420
+ );
844
1421
  }
845
1422
  }
846
1423
 
847
- options.proxy = options.proxy.map((item) => {
848
- const getLogLevelForProxy = (level) => {
849
- if (level === "none") {
850
- return "silent";
851
- }
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
+ }
852
1460
 
853
- if (level === "log") {
854
- return "info";
855
- }
1461
+ if (typeof item.logProvider === "undefined") {
1462
+ item.logProvider = () => this.logger;
1463
+ }
856
1464
 
857
- if (level === "verbose") {
858
- return "debug";
1465
+ return item;
859
1466
  }
860
-
861
- return level;
862
- };
863
-
864
- if (typeof item.logLevel === "undefined") {
865
- item.logLevel = getLogLevelForProxy(
866
- compilerOptions.infrastructureLogging
867
- ? compilerOptions.infrastructureLogging.level
868
- : "info"
869
- );
870
- }
871
-
872
- if (typeof item.logProvider === "undefined") {
873
- item.logProvider = () => this.logger;
874
- }
875
-
876
- return item;
877
- });
1467
+ );
878
1468
  }
879
1469
 
880
1470
  if (typeof options.setupExitSignals === "undefined") {
@@ -882,50 +1472,27 @@ class Server {
882
1472
  }
883
1473
 
884
1474
  if (typeof options.static === "undefined") {
885
- options.static = [defaultOptionsForStatic];
1475
+ options.static = [getStaticItem()];
886
1476
  } else if (typeof options.static === "boolean") {
887
- options.static = options.static ? [defaultOptionsForStatic] : false;
1477
+ options.static = options.static ? [getStaticItem()] : false;
888
1478
  } else if (typeof options.static === "string") {
889
- options.static = [
890
- { ...defaultOptionsForStatic, directory: options.static },
891
- ];
1479
+ options.static = [getStaticItem(options.static)];
892
1480
  } else if (Array.isArray(options.static)) {
893
1481
  options.static = options.static.map((item) => {
894
1482
  if (typeof item === "string") {
895
- return { ...defaultOptionsForStatic, directory: item };
1483
+ return getStaticItem(item);
896
1484
  }
897
1485
 
898
- return { ...defaultOptionsForStatic, ...item };
1486
+ return getStaticItem(item);
899
1487
  });
900
1488
  } else {
901
- options.static = [{ ...defaultOptionsForStatic, ...options.static }];
902
- }
903
-
904
- if (options.static) {
905
- options.static.forEach((staticOption) => {
906
- if (Server.isAbsoluteURL(staticOption.directory)) {
907
- throw new Error("Using a URL as static.directory is not supported");
908
- }
909
-
910
- // ensure that publicPath is an array
911
- if (typeof staticOption.publicPath === "string") {
912
- staticOption.publicPath = [staticOption.publicPath];
913
- }
914
-
915
- // ensure that watch is an object if true
916
- if (staticOption.watch === true) {
917
- staticOption.watch = defaultOptionsForStatic.watch;
918
- }
919
-
920
- // ensure that serveIndex is an object if true
921
- if (staticOption.serveIndex === true) {
922
- staticOption.serveIndex = defaultOptionsForStatic.serveIndex;
923
- }
924
- });
1489
+ options.static = [getStaticItem(options.static)];
925
1490
  }
926
1491
 
927
1492
  if (typeof options.watchFiles === "string") {
928
- options.watchFiles = [{ paths: options.watchFiles, options: {} }];
1493
+ options.watchFiles = [
1494
+ { paths: options.watchFiles, options: getWatchOptions() },
1495
+ ];
929
1496
  } else if (
930
1497
  typeof options.watchFiles === "object" &&
931
1498
  options.watchFiles !== null &&
@@ -934,16 +1501,19 @@ class Server {
934
1501
  options.watchFiles = [
935
1502
  {
936
1503
  paths: options.watchFiles.paths,
937
- options: options.watchFiles.options || {},
1504
+ options: getWatchOptions(options.watchFiles.options || {}),
938
1505
  },
939
1506
  ];
940
1507
  } else if (Array.isArray(options.watchFiles)) {
941
1508
  options.watchFiles = options.watchFiles.map((item) => {
942
1509
  if (typeof item === "string") {
943
- return { paths: item, options: {} };
1510
+ return { paths: item, options: getWatchOptions() };
944
1511
  }
945
1512
 
946
- return { paths: item.paths, options: item.options || {} };
1513
+ return {
1514
+ paths: item.paths,
1515
+ options: getWatchOptions(item.options || {}),
1516
+ };
947
1517
  });
948
1518
  } else {
949
1519
  options.watchFiles = [];
@@ -972,38 +1542,61 @@ class Server {
972
1542
  };
973
1543
  } else {
974
1544
  options.webSocketServer = {
975
- type: options.webSocketServer.type || defaultWebSocketServerType,
1545
+ type:
1546
+ /** @type {WebSocketServerConfiguration} */
1547
+ (options.webSocketServer).type || defaultWebSocketServerType,
976
1548
  options: {
977
1549
  ...defaultWebSocketServerOptions,
978
- ...options.webSocketServer.options,
1550
+ .../** @type {WebSocketServerConfiguration} */
1551
+ (options.webSocketServer).options,
979
1552
  },
980
1553
  };
981
1554
 
982
- if (typeof options.webSocketServer.options.port === "string") {
983
- options.webSocketServer.options.port = Number(
984
- options.webSocketServer.options.port
985
- );
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);
986
1561
  }
987
1562
  }
988
1563
  }
989
1564
 
1565
+ /**
1566
+ * @private
1567
+ * @returns {string}
1568
+ */
990
1569
  getClientTransport() {
991
- let ClientImplementation;
1570
+ let clientImplementation;
992
1571
  let clientImplementationFound = true;
993
1572
 
994
1573
  const isKnownWebSocketServerImplementation =
995
1574
  this.options.webSocketServer &&
996
- typeof this.options.webSocketServer.type === "string" &&
1575
+ typeof (
1576
+ /** @type {WebSocketServerConfiguration} */
1577
+ (this.options.webSocketServer).type
1578
+ ) === "string" &&
1579
+ // @ts-ignore
997
1580
  (this.options.webSocketServer.type === "ws" ||
998
- this.options.webSocketServer.type === "sockjs");
1581
+ /** @type {WebSocketServerConfiguration} */
1582
+ (this.options.webSocketServer).type === "sockjs");
999
1583
 
1000
1584
  let clientTransport;
1001
1585
 
1002
1586
  if (this.options.client) {
1003
- if (typeof this.options.client.webSocketTransport !== "undefined") {
1004
- 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;
1005
1596
  } else if (isKnownWebSocketServerImplementation) {
1006
- clientTransport = this.options.webSocketServer.type;
1597
+ clientTransport =
1598
+ /** @type {WebSocketServerConfiguration} */
1599
+ (this.options.webSocketServer).type;
1007
1600
  } else {
1008
1601
  clientTransport = "ws";
1009
1602
  }
@@ -1015,17 +1608,16 @@ class Server {
1015
1608
  case "string":
1016
1609
  // could be 'sockjs', 'ws', or a path that should be required
1017
1610
  if (clientTransport === "sockjs") {
1018
- ClientImplementation = require.resolve(
1611
+ clientImplementation = require.resolve(
1019
1612
  "../client/clients/SockJSClient"
1020
1613
  );
1021
1614
  } else if (clientTransport === "ws") {
1022
- ClientImplementation = require.resolve(
1615
+ clientImplementation = require.resolve(
1023
1616
  "../client/clients/WebSocketClient"
1024
1617
  );
1025
1618
  } else {
1026
1619
  try {
1027
- // eslint-disable-next-line import/no-dynamic-require
1028
- ClientImplementation = require.resolve(clientTransport);
1620
+ clientImplementation = require.resolve(clientTransport);
1029
1621
  } catch (e) {
1030
1622
  clientImplementationFound = false;
1031
1623
  }
@@ -1045,31 +1637,52 @@ class Server {
1045
1637
  );
1046
1638
  }
1047
1639
 
1048
- return ClientImplementation;
1640
+ return /** @type {string} */ (clientImplementation);
1049
1641
  }
1050
1642
 
1643
+ /**
1644
+ * @private
1645
+ * @returns {string}
1646
+ */
1051
1647
  getServerTransport() {
1052
1648
  let implementation;
1053
1649
  let implementationFound = true;
1054
1650
 
1055
- switch (typeof this.options.webSocketServer.type) {
1651
+ switch (
1652
+ typeof (
1653
+ /** @type {WebSocketServerConfiguration} */
1654
+ (this.options.webSocketServer).type
1655
+ )
1656
+ ) {
1056
1657
  case "string":
1057
1658
  // Could be 'sockjs', in the future 'ws', or a path that should be required
1058
- if (this.options.webSocketServer.type === "sockjs") {
1659
+ if (
1660
+ /** @type {WebSocketServerConfiguration} */ (
1661
+ this.options.webSocketServer
1662
+ ).type === "sockjs"
1663
+ ) {
1059
1664
  implementation = require("./servers/SockJSServer");
1060
- } else if (this.options.webSocketServer.type === "ws") {
1665
+ } else if (
1666
+ /** @type {WebSocketServerConfiguration} */ (
1667
+ this.options.webSocketServer
1668
+ ).type === "ws"
1669
+ ) {
1061
1670
  implementation = require("./servers/WebsocketServer");
1062
1671
  } else {
1063
1672
  try {
1064
1673
  // eslint-disable-next-line import/no-dynamic-require
1065
- implementation = require(this.options.webSocketServer.type);
1674
+ implementation = require(/** @type {WebSocketServerConfiguration} */ (
1675
+ this.options.webSocketServer
1676
+ ).type);
1066
1677
  } catch (error) {
1067
1678
  implementationFound = false;
1068
1679
  }
1069
1680
  }
1070
1681
  break;
1071
1682
  case "function":
1072
- implementation = this.options.webSocketServer.type;
1683
+ implementation = /** @type {WebSocketServerConfiguration} */ (
1684
+ this.options.webSocketServer
1685
+ ).type;
1073
1686
  break;
1074
1687
  default:
1075
1688
  implementationFound = false;
@@ -1086,39 +1699,62 @@ class Server {
1086
1699
  return implementation;
1087
1700
  }
1088
1701
 
1702
+ /**
1703
+ * @private
1704
+ * @returns {void}
1705
+ */
1089
1706
  setupProgressPlugin() {
1090
- const { ProgressPlugin } = this.compiler.webpack || require("webpack");
1091
-
1092
- new ProgressPlugin((percent, msg, addInfo, pluginName) => {
1093
- 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);
1094
1724
 
1095
- if (percent === 100) {
1096
- msg = "Compilation completed";
1097
- }
1725
+ if (percent === 100) {
1726
+ msg = "Compilation completed";
1727
+ }
1098
1728
 
1099
- if (addInfo) {
1100
- msg = `${msg} (${addInfo})`;
1101
- }
1729
+ if (addInfo) {
1730
+ msg = `${msg} (${addInfo})`;
1731
+ }
1102
1732
 
1103
- if (this.webSocketServer) {
1104
- this.sendMessage(this.webSocketServer.clients, "progress-update", {
1105
- percent,
1106
- msg,
1107
- pluginName,
1108
- });
1109
- }
1733
+ if (this.webSocketServer) {
1734
+ this.sendMessage(this.webSocketServer.clients, "progress-update", {
1735
+ percent,
1736
+ msg,
1737
+ pluginName,
1738
+ });
1739
+ }
1110
1740
 
1111
- if (this.server) {
1112
- this.server.emit("progress-update", { percent, msg, pluginName });
1741
+ if (this.server) {
1742
+ this.server.emit("progress-update", { percent, msg, pluginName });
1743
+ }
1113
1744
  }
1114
- }).apply(this.compiler);
1745
+ ).apply(this.compiler);
1115
1746
  }
1116
1747
 
1748
+ /**
1749
+ * @private
1750
+ * @returns {Promise<void>}
1751
+ */
1117
1752
  async initialize() {
1118
1753
  if (this.options.webSocketServer) {
1119
- const compilers = this.compiler.compilers || [this.compiler];
1754
+ const compilers =
1755
+ /** @type {MultiCompiler} */
1756
+ (this.compiler).compilers || [this.compiler];
1120
1757
 
1121
- // eslint-disable-next-line no-shadow
1122
1758
  compilers.forEach((compiler) => {
1123
1759
  this.addAdditionalEntries(compiler);
1124
1760
 
@@ -1149,7 +1785,10 @@ class Server {
1149
1785
  }
1150
1786
  });
1151
1787
 
1152
- if (this.options.client && this.options.client.progress) {
1788
+ if (
1789
+ this.options.client &&
1790
+ /** @type {ClientConfiguration} */ (this.options.client).progress
1791
+ ) {
1153
1792
  this.setupProgressPlugin();
1154
1793
  }
1155
1794
  }
@@ -1161,7 +1800,8 @@ class Server {
1161
1800
  // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1162
1801
  this.setupBuiltInRoutes();
1163
1802
  this.setupWatchFiles();
1164
- this.setupFeatures();
1803
+ this.setupWatchStaticFiles();
1804
+ this.setupMiddlewares();
1165
1805
  this.createServer();
1166
1806
 
1167
1807
  if (this.options.setupExitSignals) {
@@ -1172,7 +1812,6 @@ class Server {
1172
1812
  signals.forEach((signal) => {
1173
1813
  const listener = () => {
1174
1814
  if (needForceShutdown) {
1175
- // eslint-disable-next-line no-process-exit
1176
1815
  process.exit();
1177
1816
  }
1178
1817
 
@@ -1185,11 +1824,9 @@ class Server {
1185
1824
  this.stopCallback(() => {
1186
1825
  if (typeof this.compiler.close === "function") {
1187
1826
  this.compiler.close(() => {
1188
- // eslint-disable-next-line no-process-exit
1189
1827
  process.exit();
1190
1828
  });
1191
1829
  } else {
1192
- // eslint-disable-next-line no-process-exit
1193
1830
  process.exit();
1194
1831
  }
1195
1832
  });
@@ -1203,53 +1840,108 @@ class Server {
1203
1840
 
1204
1841
  // Proxy WebSocket without the initial http request
1205
1842
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1206
- this.webSocketProxies.forEach((webSocketProxy) => {
1207
- 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
+ );
1208
1851
  }, this);
1209
1852
  }
1210
1853
 
1854
+ /**
1855
+ * @private
1856
+ * @returns {void}
1857
+ */
1211
1858
  setupApp() {
1212
- // Init express server
1859
+ /** @type {import("express").Application | undefined}*/
1213
1860
  // eslint-disable-next-line new-cap
1214
- this.app = new express();
1861
+ this.app = new /** @type {any} */ (express)();
1215
1862
  }
1216
1863
 
1864
+ /**
1865
+ * @private
1866
+ * @param {Stats | MultiStats} statsObj
1867
+ * @returns {StatsCompilation}
1868
+ */
1217
1869
  getStats(statsObj) {
1218
1870
  const stats = Server.DEFAULT_STATS;
1219
1871
  const compilerOptions = this.getCompilerOptions();
1220
1872
 
1873
+ // @ts-ignore
1221
1874
  if (compilerOptions.stats && compilerOptions.stats.warningsFilter) {
1875
+ // @ts-ignore
1222
1876
  stats.warningsFilter = compilerOptions.stats.warningsFilter;
1223
1877
  }
1224
1878
 
1225
1879
  return statsObj.toJson(stats);
1226
1880
  }
1227
1881
 
1882
+ /**
1883
+ * @private
1884
+ * @returns {void}
1885
+ */
1228
1886
  setupHooks() {
1229
1887
  this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
1230
1888
  if (this.webSocketServer) {
1231
1889
  this.sendMessage(this.webSocketServer.clients, "invalid");
1232
1890
  }
1233
1891
  });
1234
- this.compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1235
- if (this.webSocketServer) {
1236
- this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1237
- }
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
+ }
1238
1901
 
1239
- this.stats = stats;
1240
- });
1902
+ /**
1903
+ * @private
1904
+ * @type {Stats | MultiStats}
1905
+ */
1906
+ this.stats = stats;
1907
+ }
1908
+ );
1241
1909
  }
1242
1910
 
1911
+ /**
1912
+ * @private
1913
+ * @returns {void}
1914
+ */
1243
1915
  setupHostHeaderCheck() {
1244
- this.app.all("*", (req, res, next) => {
1245
- if (this.checkHeader(req.headers, "host")) {
1246
- return next();
1247
- }
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
+ }
1248
1935
 
1249
- res.send("Invalid Host header");
1250
- });
1936
+ res.send("Invalid Host header");
1937
+ }
1938
+ );
1251
1939
  }
1252
1940
 
1941
+ /**
1942
+ * @private
1943
+ * @returns {void}
1944
+ */
1253
1945
  setupDevMiddleware() {
1254
1946
  const webpackDevMiddleware = require("webpack-dev-middleware");
1255
1947
 
@@ -1260,435 +1952,616 @@ class Server {
1260
1952
  );
1261
1953
  }
1262
1954
 
1955
+ /**
1956
+ * @private
1957
+ * @returns {void}
1958
+ */
1263
1959
  setupBuiltInRoutes() {
1264
1960
  const { app, middleware } = this;
1265
1961
 
1266
- app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
1267
- res.setHeader("Content-Type", "application/javascript");
1268
-
1269
- const { createReadStream } = fs;
1270
- const clientPath = path.join(__dirname, "..", "client");
1271
-
1272
- createReadStream(
1273
- path.join(clientPath, "modules/sockjs-client/index.js")
1274
- ).pipe(res);
1275
- });
1276
-
1277
- app.get("/webpack-dev-server/invalidate", (_req, res) => {
1278
- this.invalidate();
1279
-
1280
- res.end();
1281
- });
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");
1282
1972
 
1283
- app.get("/webpack-dev-server", (req, res) => {
1284
- middleware.waitUntilValid((stats) => {
1285
- res.setHeader("Content-Type", "text/html");
1286
- res.write(
1287
- '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
1288
- );
1973
+ const { createReadStream } = fs;
1974
+ const clientPath = path.join(__dirname, "..", "client");
1289
1975
 
1290
- const statsForPrint =
1291
- typeof stats.stats !== "undefined"
1292
- ? stats.toJson().children
1293
- : [stats.toJson()];
1976
+ createReadStream(
1977
+ path.join(clientPath, "modules/sockjs-client/index.js")
1978
+ ).pipe(res);
1979
+ }
1980
+ );
1294
1981
 
1295
- 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();
1296
1992
 
1297
- statsForPrint.forEach((item, index) => {
1298
- res.write("<div>");
1993
+ res.end();
1994
+ }
1995
+ );
1299
1996
 
1300
- const name =
1301
- 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
+ );
1302
2012
 
1303
- res.write(`<h2>Compilation: ${name}</h2>`);
1304
- res.write("<ul>");
2013
+ const statsForPrint =
2014
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2015
+ ? /** @type {MultiStats} */ (stats).toJson().children
2016
+ : [/** @type {Stats} */ (stats).toJson()];
1305
2017
 
1306
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2018
+ res.write(`<h1>Assets Report:</h1>`);
1307
2019
 
1308
- for (const asset of item.assets) {
1309
- const assetName = asset.name;
1310
- const assetURL = `${publicPath}${assetName}`;
2020
+ /**
2021
+ * @type {StatsCompilation[]}
2022
+ */
2023
+ (statsForPrint).forEach((item, index) => {
2024
+ res.write("<div>");
1311
2025
 
1312
- res.write(
1313
- `<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>
1314
2048
  <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1315
2049
  </li>`
1316
- );
1317
- }
2050
+ );
2051
+ }
2052
+
2053
+ res.write("</ul>");
2054
+ res.write("</div>");
2055
+ });
1318
2056
 
1319
- res.write("</ul>");
1320
- res.write("</div>");
2057
+ res.end("</body></html>");
1321
2058
  });
2059
+ }
2060
+ );
2061
+ }
1322
2062
 
1323
- 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
+ }
1324
2074
  });
1325
- });
2075
+ }
1326
2076
  }
1327
2077
 
1328
- setupCompressFeature() {
1329
- const compress = require("compression");
2078
+ /**
2079
+ * @private
2080
+ * @returns {void}
2081
+ */
2082
+ setupWatchFiles() {
2083
+ const { watchFiles } = this.options;
1330
2084
 
1331
- 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
+ }
1332
2091
  }
1333
2092
 
1334
- setupProxyFeature() {
1335
- const { createProxyMiddleware } = require("http-proxy-middleware");
2093
+ /**
2094
+ * @private
2095
+ * @returns {void}
2096
+ */
2097
+ setupMiddlewares() {
2098
+ /**
2099
+ * @type {Array<Middleware>}
2100
+ */
2101
+ let middlewares = [];
1336
2102
 
1337
- const getProxyMiddleware = (proxyConfig) => {
1338
- // It is possible to use the `bypass` method without a `target` or `router`.
1339
- // However, the proxy middleware has no use in this case, and will fail to instantiate.
1340
- if (proxyConfig.target) {
1341
- const context = proxyConfig.context || proxyConfig.path;
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");
1342
2106
 
1343
- return createProxyMiddleware(context, proxyConfig);
1344
- }
2107
+ middlewares.push({ name: "compression", middleware: compression() });
2108
+ }
1345
2109
 
1346
- if (proxyConfig.router) {
1347
- return createProxyMiddleware(proxyConfig);
1348
- }
1349
- };
1350
- /**
1351
- * Assume a proxy configuration specified as:
1352
- * proxy: [
1353
- * {
1354
- * context: "value",
1355
- * ...options,
1356
- * },
1357
- * // or:
1358
- * function() {
1359
- * return {
1360
- * context: "context",
1361
- * ...options,
1362
- * };
1363
- * }
1364
- * ]
1365
- */
1366
- this.options.proxy.forEach((proxyConfigOrCallback) => {
1367
- let proxyMiddleware;
1368
-
1369
- let proxyConfig =
1370
- typeof proxyConfigOrCallback === "function"
1371
- ? proxyConfigOrCallback()
1372
- : proxyConfigOrCallback;
2110
+ if (typeof this.options.onBeforeSetupMiddleware === "function") {
2111
+ this.options.onBeforeSetupMiddleware(this);
2112
+ }
1373
2113
 
1374
- proxyMiddleware = getProxyMiddleware(proxyConfig);
2114
+ if (typeof this.options.headers !== "undefined") {
2115
+ middlewares.push({
2116
+ name: "set-headers",
2117
+ path: "*",
2118
+ middleware: this.setHeaders.bind(this),
2119
+ });
2120
+ }
1375
2121
 
1376
- if (proxyConfig.ws) {
1377
- this.webSocketProxies.push(proxyMiddleware);
1378
- }
2122
+ middlewares.push({
2123
+ name: "webpack-dev-middleware",
2124
+ middleware:
2125
+ /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2126
+ (this.middleware),
2127
+ });
1379
2128
 
1380
- const handle = async (req, res, next) => {
1381
- if (typeof proxyConfigOrCallback === "function") {
1382
- const newProxyConfig = proxyConfigOrCallback(req, res, next);
2129
+ if (this.options.proxy) {
2130
+ const { createProxyMiddleware } = require("http-proxy-middleware");
1383
2131
 
1384
- if (newProxyConfig !== proxyConfig) {
1385
- proxyConfig = newProxyConfig;
1386
- proxyMiddleware = getProxyMiddleware(proxyConfig);
1387
- }
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
+ );
1388
2146
  }
1389
2147
 
1390
- // - Check if we have a bypass function defined
1391
- // - In case the bypass function is defined we'll retrieve the
1392
- // bypassUrl from it otherwise bypassUrl would be null
1393
- // TODO remove in the next major in favor `context` and `router` options
1394
- const isByPassFuncDefined = typeof proxyConfig.bypass === "function";
1395
- const bypassUrl = isByPassFuncDefined
1396
- ? await proxyConfig.bypass(req, res, proxyConfig)
1397
- : null;
1398
-
1399
- if (typeof bypassUrl === "boolean") {
1400
- // skip the proxy
1401
- req.url = null;
1402
- next();
1403
- } else if (typeof bypassUrl === "string") {
1404
- // byPass to that url
1405
- req.url = bypassUrl;
1406
- next();
1407
- } else if (proxyMiddleware) {
1408
- return proxyMiddleware(req, res, next);
1409
- } else {
1410
- next();
2148
+ if (proxyConfig.router) {
2149
+ return createProxyMiddleware(proxyConfig);
1411
2150
  }
1412
2151
  };
1413
2152
 
1414
- this.app.use(handle);
1415
- // Also forward error requests to the proxy so it can handle them.
1416
- this.app.use((error, req, res, next) => handle(req, res, next));
1417
- });
1418
- }
1419
-
1420
- setupHistoryApiFallbackFeature() {
1421
- const { historyApiFallback } = this.options;
1422
-
1423
- if (
1424
- typeof historyApiFallback.logger === "undefined" &&
1425
- !historyApiFallback.verbose
1426
- ) {
1427
- historyApiFallback.logger = this.logger.log.bind(
1428
- this.logger,
1429
- "[connect-history-api-fallback]"
1430
- );
1431
- }
1432
-
1433
- // Fall back to /index.html if nothing else matches.
1434
- this.app.use(require("connect-history-api-fallback")(historyApiFallback));
1435
- }
1436
-
1437
- setupStaticFeature() {
1438
- this.options.static.forEach((staticOption) => {
1439
- staticOption.publicPath.forEach((publicPath) => {
1440
- this.app.use(
1441
- publicPath,
1442
- express.static(staticOption.directory, staticOption.staticOptions)
1443
- );
1444
- });
1445
- });
1446
- }
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
+ }
1447
2192
 
1448
- setupStaticServeIndexFeature() {
1449
- 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
+ }
1450
2210
 
1451
- this.options.static.forEach((staticOption) => {
1452
- staticOption.publicPath.forEach((publicPath) => {
1453
- if (staticOption.serveIndex) {
1454
- this.app.use(publicPath, (req, res, next) => {
1455
- // serve-index doesn't fallthrough non-get/head request to next middleware
1456
- if (req.method !== "GET" && req.method !== "HEAD") {
1457
- 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();
1458
2234
  }
2235
+ };
1459
2236
 
1460
- serveIndex(staticOption.directory, staticOption.serveIndex)(
1461
- req,
1462
- res,
1463
- next
1464
- );
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),
1465
2253
  });
1466
2254
  }
1467
- });
1468
- });
1469
- }
1470
-
1471
- setupStaticWatchFeature() {
1472
- this.options.static.forEach((staticOption) => {
1473
- if (staticOption.watch) {
1474
- this.watchFiles(staticOption.directory, staticOption.watch);
1475
- }
1476
- });
1477
- }
1478
-
1479
- setupOnBeforeSetupMiddlewareFeature() {
1480
- this.options.onBeforeSetupMiddleware(this);
1481
- }
1482
-
1483
- setupWatchFiles() {
1484
- const { watchFiles } = this.options;
2255
+ );
1485
2256
 
1486
- if (watchFiles.length > 0) {
1487
- watchFiles.forEach((item) => {
1488
- 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),
1489
2262
  });
1490
2263
  }
1491
- }
1492
-
1493
- setupMiddleware() {
1494
- this.app.use(this.middleware);
1495
- }
1496
-
1497
- setupOnAfterSetupMiddlewareFeature() {
1498
- this.options.onAfterSetupMiddleware(this);
1499
- }
1500
-
1501
- setupHeadersFeature() {
1502
- this.app.all("*", this.setHeaders.bind(this));
1503
- }
1504
-
1505
- setupMagicHtmlFeature() {
1506
- this.app.get("*", this.serveMagicHtml.bind(this));
1507
- }
1508
-
1509
- setupFeatures() {
1510
- const features = {
1511
- compress: () => {
1512
- if (this.options.compress) {
1513
- this.setupCompressFeature();
1514
- }
1515
- },
1516
- proxy: () => {
1517
- if (this.options.proxy) {
1518
- this.setupProxyFeature();
1519
- }
1520
- },
1521
- historyApiFallback: () => {
1522
- if (this.options.historyApiFallback) {
1523
- this.setupHistoryApiFallbackFeature();
1524
- }
1525
- },
1526
- static: () => {
1527
- this.setupStaticFeature();
1528
- },
1529
- staticServeIndex: () => {
1530
- this.setupStaticServeIndexFeature();
1531
- },
1532
- staticWatch: () => {
1533
- this.setupStaticWatchFeature();
1534
- },
1535
- onBeforeSetupMiddleware: () => {
1536
- if (typeof this.options.onBeforeSetupMiddleware === "function") {
1537
- this.setupOnBeforeSetupMiddlewareFeature();
1538
- }
1539
- },
1540
- onAfterSetupMiddleware: () => {
1541
- if (typeof this.options.onAfterSetupMiddleware === "function") {
1542
- this.setupOnAfterSetupMiddlewareFeature();
1543
- }
1544
- },
1545
- middleware: () => {
1546
- // include our middleware to ensure
1547
- // it is able to handle '/index.html' request after redirect
1548
- this.setupMiddleware();
1549
- },
1550
- headers: () => {
1551
- this.setupHeadersFeature();
1552
- },
1553
- magicHtml: () => {
1554
- this.setupMagicHtmlFeature();
1555
- },
1556
- };
1557
-
1558
- const runnableFeatures = [];
1559
-
1560
- // compress is placed last and uses unshift so that it will be the first middleware used
1561
- if (this.options.compress) {
1562
- runnableFeatures.push("compress");
1563
- }
1564
2264
 
1565
- if (this.options.onBeforeSetupMiddleware) {
1566
- 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
+ });
1567
2279
  }
1568
2280
 
1569
- runnableFeatures.push("headers", "middleware");
2281
+ if (this.options.historyApiFallback) {
2282
+ const connectHistoryApiFallback = require("connect-history-api-fallback");
2283
+ const { historyApiFallback } = this.options;
1570
2284
 
1571
- if (this.options.proxy) {
1572
- runnableFeatures.push("proxy", "middleware");
1573
- }
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
+ }
1574
2301
 
1575
- if (this.options.static) {
1576
- runnableFeatures.push("static");
1577
- }
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
+ });
1578
2310
 
1579
- if (this.options.historyApiFallback) {
1580
- 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
+ });
1581
2319
 
1582
- if (this.options.static) {
1583
- 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
+ });
1584
2334
  }
1585
2335
  }
1586
2336
 
1587
- if (this.options.static) {
1588
- 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
+ });
1589
2369
  }
1590
2370
 
1591
2371
  if (this.options.magicHtml) {
1592
- runnableFeatures.push("magicHtml");
2372
+ middlewares.push({
2373
+ name: "serve-magic-html",
2374
+ middleware: this.serveMagicHtml.bind(this),
2375
+ });
1593
2376
  }
1594
2377
 
1595
- if (this.options.onAfterSetupMiddleware) {
1596
- runnableFeatures.push("onAfterSetupMiddleware");
2378
+ if (typeof this.options.setupMiddlewares === "function") {
2379
+ middlewares = this.options.setupMiddlewares(middlewares, this);
1597
2380
  }
1598
2381
 
1599
- runnableFeatures.forEach((feature) => {
1600
- features[feature]();
2382
+ middlewares.forEach((middleware) => {
2383
+ if (typeof middleware === "function") {
2384
+ /** @type {import("express").Application} */
2385
+ (this.app).use(middleware);
2386
+ } else if (typeof middleware.path !== "undefined") {
2387
+ /** @type {import("express").Application} */
2388
+ (this.app).use(middleware.path, middleware.middleware);
2389
+ } else {
2390
+ /** @type {import("express").Application} */
2391
+ (this.app).use(middleware.middleware);
2392
+ }
1601
2393
  });
2394
+
2395
+ if (typeof this.options.onAfterSetupMiddleware === "function") {
2396
+ this.options.onAfterSetupMiddleware(this);
2397
+ }
1602
2398
  }
1603
2399
 
2400
+ /**
2401
+ * @private
2402
+ * @returns {void}
2403
+ */
1604
2404
  createServer() {
2405
+ const { type, options } = /** @type {ServerConfiguration} */ (
2406
+ this.options.server
2407
+ );
2408
+
2409
+ /** @type {import("http").Server | undefined | null} */
1605
2410
  // eslint-disable-next-line import/no-dynamic-require
1606
- this.server = require(this.options.server.type).createServer(
1607
- this.options.server.options,
2411
+ this.server = require(/** @type {string} */ (type)).createServer(
2412
+ options,
1608
2413
  this.app
1609
2414
  );
1610
2415
 
1611
- this.server.on("connection", (socket) => {
1612
- // Add socket to list
1613
- 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);
1614
2425
 
1615
- socket.once("close", () => {
1616
- // Remove socket from list
1617
- this.sockets.splice(this.sockets.indexOf(socket), 1);
1618
- });
1619
- });
2426
+ socket.once("close", () => {
2427
+ // Remove socket from list
2428
+ this.sockets.splice(this.sockets.indexOf(socket), 1);
2429
+ });
2430
+ }
2431
+ );
1620
2432
 
1621
- this.server.on("error", (error) => {
1622
- throw error;
1623
- });
2433
+ /** @type {import("http").Server} */
2434
+ (this.server).on(
2435
+ "error",
2436
+ /**
2437
+ * @param {Error} error
2438
+ */
2439
+ (error) => {
2440
+ throw error;
2441
+ }
2442
+ );
1624
2443
  }
1625
2444
 
2445
+ /**
2446
+ * @private
2447
+ * @returns {void}
2448
+ */
1626
2449
  // TODO: remove `--web-socket-server` in favor of `--web-socket-server-type`
1627
2450
  createWebSocketServer() {
1628
- this.webSocketServer = new (this.getServerTransport())(this);
1629
- this.webSocketServer.implementation.on("connection", (client, request) => {
1630
- const headers =
1631
- // eslint-disable-next-line no-nested-ternary
1632
- typeof request !== "undefined"
1633
- ? request.headers
1634
- : typeof client.headers !== "undefined"
1635
- ? client.headers
1636
- : // eslint-disable-next-line no-undefined
1637
- undefined;
1638
-
1639
- if (!headers) {
1640
- this.logger.warn(
1641
- 'webSocketServer implementation must pass headers for the "connection" event'
1642
- );
1643
- }
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
+ }
1644
2481
 
1645
- if (
1646
- !headers ||
1647
- !this.checkHeader(headers, "host") ||
1648
- !this.checkHeader(headers, "origin")
1649
- ) {
1650
- 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");
1651
2488
 
1652
- // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
1653
- // Terminate would prevent it sending, so use close to allow it to be sent
1654
- 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();
1655
2492
 
1656
- return;
1657
- }
2493
+ return;
2494
+ }
1658
2495
 
1659
- if (this.options.hot === true || this.options.hot === "only") {
1660
- this.sendMessage([client], "hot");
1661
- }
2496
+ if (this.options.hot === true || this.options.hot === "only") {
2497
+ this.sendMessage([client], "hot");
2498
+ }
1662
2499
 
1663
- if (this.options.liveReload) {
1664
- this.sendMessage([client], "liveReload");
1665
- }
2500
+ if (this.options.liveReload) {
2501
+ this.sendMessage([client], "liveReload");
2502
+ }
1666
2503
 
1667
- if (this.options.client && this.options.client.progress) {
1668
- this.sendMessage([client], "progress", this.options.client.progress);
1669
- }
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
+ }
1670
2516
 
1671
- if (this.options.client && this.options.client.reconnect) {
1672
- this.sendMessage([client], "reconnect", this.options.client.reconnect);
1673
- }
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
+ }
1674
2528
 
1675
- if (this.options.client && this.options.client.overlay) {
1676
- this.sendMessage([client], "overlay", this.options.client.overlay);
1677
- }
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
+ }
1678
2541
 
1679
- if (!this.stats) {
1680
- return;
1681
- }
2542
+ if (!this.stats) {
2543
+ return;
2544
+ }
1682
2545
 
1683
- this.sendStats([client], this.getStats(this.stats), true);
1684
- });
2546
+ this.sendStats([client], this.getStats(this.stats), true);
2547
+ }
2548
+ );
1685
2549
  }
1686
2550
 
2551
+ /**
2552
+ * @private
2553
+ * @param {string} defaultOpenTarget
2554
+ * @returns {void}
2555
+ */
1687
2556
  openBrowser(defaultOpenTarget) {
1688
2557
  const open = require("open");
1689
2558
 
1690
2559
  Promise.all(
1691
- this.options.open.map((item) => {
2560
+ /** @type {NormalizedOpen[]} */
2561
+ (this.options.open).map((item) => {
2562
+ /**
2563
+ * @type {string}
2564
+ */
1692
2565
  let openTarget;
1693
2566
 
1694
2567
  if (item.target === "<url>") {
@@ -1702,13 +2575,17 @@ class Server {
1702
2575
  return open(openTarget, item.options).catch(() => {
1703
2576
  this.logger.warn(
1704
2577
  `Unable to open "${openTarget}" page${
1705
- // eslint-disable-next-line no-nested-ternary
1706
2578
  item.options.app
1707
- ? ` in "${item.options.app.name}" app${
1708
- item.options.app.arguments
1709
- ? ` with "${item.options.app.arguments.join(
1710
- " "
1711
- )}" 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`
1712
2589
  : ""
1713
2590
  }`
1714
2591
  : ""
@@ -1719,38 +2596,68 @@ class Server {
1719
2596
  );
1720
2597
  }
1721
2598
 
1722
- stopBonjour(callback = () => {}) {
1723
- this.bonjour.unpublishAll(() => {
1724
- this.bonjour.destroy();
1725
-
1726
- if (callback) {
1727
- callback();
1728
- }
1729
- });
1730
- }
1731
-
2599
+ /**
2600
+ * @private
2601
+ * @returns {void}
2602
+ */
1732
2603
  runBonjour() {
2604
+ /**
2605
+ * @private
2606
+ * @type {import("bonjour").Bonjour | undefined}
2607
+ */
1733
2608
  this.bonjour = require("bonjour")();
1734
2609
  this.bonjour.publish({
1735
2610
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
1736
- port: this.options.port,
1737
- 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",
1738
2615
  subtypes: ["webpack"],
1739
- ...this.options.bonjour,
2616
+ .../** @type {BonjourOptions} */ (this.options.bonjour),
1740
2617
  });
1741
2618
  }
1742
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
+ }
2633
+ });
2634
+ }
2635
+
2636
+ /**
2637
+ * @private
2638
+ * @returns {void}
2639
+ */
1743
2640
  logStatus() {
1744
2641
  const { isColorSupported, cyan, red } = require("colorette");
1745
2642
 
2643
+ /**
2644
+ * @param {Compiler["options"]} compilerOptions
2645
+ * @returns {boolean}
2646
+ */
1746
2647
  const getColorsOption = (compilerOptions) => {
2648
+ /**
2649
+ * @type {boolean}
2650
+ */
1747
2651
  let colorsEnabled;
1748
2652
 
1749
2653
  if (
1750
2654
  compilerOptions.stats &&
1751
- typeof compilerOptions.stats.colors !== "undefined"
2655
+ typeof (/** @type {StatsOptions} */ (compilerOptions.stats).colors) !==
2656
+ "undefined"
1752
2657
  ) {
1753
- colorsEnabled = compilerOptions.stats;
2658
+ colorsEnabled =
2659
+ /** @type {boolean} */
2660
+ (/** @type {StatsOptions} */ (compilerOptions.stats).colors);
1754
2661
  } else {
1755
2662
  colorsEnabled = isColorSupported;
1756
2663
  }
@@ -1759,6 +2666,11 @@ class Server {
1759
2666
  };
1760
2667
 
1761
2668
  const colors = {
2669
+ /**
2670
+ * @param {boolean} useColor
2671
+ * @param {string} msg
2672
+ * @returns {string}
2673
+ */
1762
2674
  info(useColor, msg) {
1763
2675
  if (useColor) {
1764
2676
  return cyan(msg);
@@ -1766,6 +2678,11 @@ class Server {
1766
2678
 
1767
2679
  return msg;
1768
2680
  },
2681
+ /**
2682
+ * @param {boolean} useColor
2683
+ * @param {string} msg
2684
+ * @returns {string}
2685
+ */
1769
2686
  error(useColor, msg) {
1770
2687
  if (useColor) {
1771
2688
  return red(msg);
@@ -1777,10 +2694,26 @@ class Server {
1777
2694
  const useColor = getColorsOption(this.getCompilerOptions());
1778
2695
 
1779
2696
  if (this.options.ipc) {
1780
- 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
+ );
1781
2703
  } else {
1782
- const protocol = this.options.server.type === "http" ? "http" : "https";
1783
- 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
+ */
1784
2717
  const prettyPrintURL = (newHostname) =>
1785
2718
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
1786
2719
 
@@ -1814,13 +2747,13 @@ class Server {
1814
2747
  if (parsedIP.range() === "unspecified") {
1815
2748
  localhost = prettyPrintURL("localhost");
1816
2749
 
1817
- const networkIPv4 = internalIp.v4.sync();
2750
+ const networkIPv4 = Server.internalIPSync("v4");
1818
2751
 
1819
2752
  if (networkIPv4) {
1820
2753
  networkUrlIPv4 = prettyPrintURL(networkIPv4);
1821
2754
  }
1822
2755
 
1823
- const networkIPv6 = internalIp.v6.sync();
2756
+ const networkIPv6 = Server.internalIPSync("v6");
1824
2757
 
1825
2758
  if (networkIPv6) {
1826
2759
  networkUrlIPv6 = prettyPrintURL(networkIPv6);
@@ -1833,8 +2766,13 @@ class Server {
1833
2766
  }
1834
2767
  } else {
1835
2768
  networkUrlIPv4 =
1836
- parsedIP.kind() === "ipv6" && parsedIP.isIPv4MappedAddress()
1837
- ? prettyPrintURL(parsedIP.toIPv4Address().toString())
2769
+ parsedIP.kind() === "ipv6" &&
2770
+ /** @type {IPv6} */
2771
+ (parsedIP).isIPv4MappedAddress()
2772
+ ? prettyPrintURL(
2773
+ /** @type {IPv6} */
2774
+ (parsedIP).toIPv4Address().toString()
2775
+ )
1838
2776
  : prettyPrintURL(address);
1839
2777
 
1840
2778
  if (parsedIP.kind() === "ipv6") {
@@ -1849,10 +2787,19 @@ class Server {
1849
2787
  }
1850
2788
 
1851
2789
  if (localhost || loopbackIPv4 || loopbackIPv6) {
1852
- const loopbacks = []
1853
- .concat(localhost ? [colors.info(useColor, localhost)] : [])
1854
- .concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
1855
- .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
+ }
1856
2803
 
1857
2804
  this.logger.info(`Loopback: ${loopbacks.join(", ")}`);
1858
2805
  }
@@ -1869,18 +2816,19 @@ class Server {
1869
2816
  );
1870
2817
  }
1871
2818
 
1872
- if (this.options.open.length > 0) {
2819
+ if (/** @type {NormalizedOpen[]} */ (this.options.open).length > 0) {
1873
2820
  const openTarget = prettyPrintURL(this.options.host || "localhost");
1874
2821
 
1875
2822
  this.openBrowser(openTarget);
1876
2823
  }
1877
2824
  }
1878
2825
 
1879
- if (this.options.static && this.options.static.length > 0) {
2826
+ if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
1880
2827
  this.logger.info(
1881
2828
  `Content not from webpack is served from '${colors.info(
1882
2829
  useColor,
1883
- this.options.static
2830
+ /** @type {NormalizedStatic[]} */
2831
+ (this.options.static)
1884
2832
  .map((staticOption) => staticOption.directory)
1885
2833
  .join(", ")
1886
2834
  )}' directory`
@@ -1891,14 +2839,19 @@ class Server {
1891
2839
  this.logger.info(
1892
2840
  `404s will fallback to '${colors.info(
1893
2841
  useColor,
1894
- this.options.historyApiFallback.index || "/index.html"
2842
+ /** @type {ConnectHistoryApiFallbackOptions} */ (
2843
+ this.options.historyApiFallback
2844
+ ).index || "/index.html"
1895
2845
  )}'`
1896
2846
  );
1897
2847
  }
1898
2848
 
1899
2849
  if (this.options.bonjour) {
1900
2850
  const bonjourProtocol =
1901
- 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"
1902
2855
  ? "http"
1903
2856
  : "https";
1904
2857
 
@@ -1908,32 +2861,59 @@ class Server {
1908
2861
  }
1909
2862
  }
1910
2863
 
2864
+ /**
2865
+ * @private
2866
+ * @param {Request} req
2867
+ * @param {Response} res
2868
+ * @param {NextFunction} next
2869
+ */
1911
2870
  setHeaders(req, res, next) {
1912
2871
  let { headers } = this.options;
1913
2872
 
1914
2873
  if (headers) {
1915
2874
  if (typeof headers === "function") {
1916
- 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
+ );
1917
2881
  }
1918
2882
 
2883
+ /**
2884
+ * @type {{key: string, value: string}[]}
2885
+ */
1919
2886
  const allHeaders = [];
1920
2887
 
1921
2888
  if (!Array.isArray(headers)) {
1922
2889
  // eslint-disable-next-line guard-for-in
1923
2890
  for (const name in headers) {
2891
+ // @ts-ignore
1924
2892
  allHeaders.push({ key: name, value: headers[name] });
1925
2893
  }
2894
+
1926
2895
  headers = allHeaders;
1927
2896
  }
1928
2897
 
1929
- headers.forEach((header) => {
1930
- res.setHeader(header.key, header.value);
1931
- });
2898
+ headers.forEach(
2899
+ /**
2900
+ * @param {{key: string, value: any}} header
2901
+ */
2902
+ (header) => {
2903
+ res.setHeader(header.key, header.value);
2904
+ }
2905
+ );
1932
2906
  }
1933
2907
 
1934
2908
  next();
1935
2909
  }
1936
2910
 
2911
+ /**
2912
+ * @private
2913
+ * @param {{ [key: string]: string | undefined }} headers
2914
+ * @param {string} headerToCheck
2915
+ * @returns {boolean}
2916
+ */
1937
2917
  checkHeader(headers, headerToCheck) {
1938
2918
  // allow user to opt out of this security check, at their own risk
1939
2919
  // by explicitly enabling allowedHosts
@@ -1970,8 +2950,8 @@ class Server {
1970
2950
  // always allow localhost host, for convenience (hostname === 'localhost')
1971
2951
  // allow hostname of listening address (hostname === this.options.host)
1972
2952
  const isValidHostname =
1973
- ipaddr.IPv4.isValid(hostname) ||
1974
- ipaddr.IPv6.isValid(hostname) ||
2953
+ (hostname !== null && ipaddr.IPv4.isValid(hostname)) ||
2954
+ (hostname !== null && ipaddr.IPv6.isValid(hostname)) ||
1975
2955
  hostname === "localhost" ||
1976
2956
  hostname === this.options.host;
1977
2957
 
@@ -1998,7 +2978,7 @@ class Server {
1998
2978
  // "*.example.com" (hostname.endsWith(allowedHost))
1999
2979
  if (
2000
2980
  hostname === allowedHost.substring(1) ||
2001
- hostname.endsWith(allowedHost)
2981
+ /** @type {string} */ (hostname).endsWith(allowedHost)
2002
2982
  ) {
2003
2983
  return true;
2004
2984
  }
@@ -2009,41 +2989,73 @@ class Server {
2009
2989
  // Also allow if `client.webSocketURL.hostname` provided
2010
2990
  if (
2011
2991
  this.options.client &&
2012
- typeof this.options.client.webSocketURL !== "undefined"
2992
+ typeof (
2993
+ /** @type {ClientConfiguration} */ (this.options.client).webSocketURL
2994
+ ) !== "undefined"
2013
2995
  ) {
2014
- return this.options.client.webSocketURL.hostname === hostname;
2996
+ return (
2997
+ /** @type {WebSocketURL} */
2998
+ (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
2999
+ .hostname === hostname
3000
+ );
2015
3001
  }
2016
3002
 
2017
3003
  // disallow
2018
3004
  return false;
2019
3005
  }
2020
3006
 
3007
+ /**
3008
+ * @param {ClientConnection[]} clients
3009
+ * @param {string} type
3010
+ * @param {any} [data]
3011
+ * @param {any} [params]
3012
+ */
2021
3013
  // eslint-disable-next-line class-methods-use-this
2022
- sendMessage(clients, type, data) {
3014
+ sendMessage(clients, type, data, params) {
2023
3015
  for (const client of clients) {
2024
3016
  // `sockjs` uses `1` to indicate client is ready to accept data
2025
3017
  // `ws` uses `WebSocket.OPEN`, but it is mean `1` too
2026
3018
  if (client.readyState === 1) {
2027
- client.send(JSON.stringify({ type, data }));
3019
+ client.send(JSON.stringify({ type, data, params }));
2028
3020
  }
2029
3021
  }
2030
3022
  }
2031
3023
 
3024
+ /**
3025
+ * @private
3026
+ * @param {Request} req
3027
+ * @param {Response} res
3028
+ * @param {NextFunction} next
3029
+ * @returns {void}
3030
+ */
2032
3031
  serveMagicHtml(req, res, next) {
2033
- 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(() => {
2034
3038
  const _path = req.path;
2035
3039
 
2036
3040
  try {
2037
- const filename = this.middleware.getFilenameFromUrl(`${_path}.js`);
2038
- const isFile = this.middleware.context.outputFileSystem
2039
- .statSync(filename)
2040
- .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();
2041
3052
 
2042
3053
  if (!isFile) {
2043
3054
  return next();
2044
3055
  }
2045
3056
 
2046
3057
  // Serve a page that executes the javascript
3058
+ // @ts-ignore
2047
3059
  const queries = req._parsedUrl.search || "";
2048
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>`;
2049
3061
 
@@ -2055,6 +3067,12 @@ class Server {
2055
3067
  }
2056
3068
 
2057
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
+ */
2058
3076
  sendStats(clients, stats, force) {
2059
3077
  const shouldEmit =
2060
3078
  !force &&
@@ -2072,12 +3090,33 @@ class Server {
2072
3090
  this.currentHash = stats.hash;
2073
3091
  this.sendMessage(clients, "hash", stats.hash);
2074
3092
 
2075
- if (stats.errors.length > 0 || stats.warnings.length > 0) {
2076
- if (stats.warnings.length > 0) {
2077
- this.sendMessage(clients, "warnings", stats.warnings);
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;
3102
+
3103
+ if (
3104
+ /** @type {NonNullable<StatsCompilation["warnings"]>} */
3105
+ (stats.warnings).length > 0
3106
+ ) {
3107
+ let params;
3108
+
3109
+ if (hasErrors) {
3110
+ params = { preventReloading: true };
3111
+ }
3112
+
3113
+ this.sendMessage(clients, "warnings", stats.warnings, params);
2078
3114
  }
2079
3115
 
2080
- if (stats.errors.length > 0) {
3116
+ if (
3117
+ /** @type {NonNullable<StatsCompilation["errors"]>} */ (stats.errors)
3118
+ .length > 0
3119
+ ) {
2081
3120
  this.sendMessage(clients, "errors", stats.errors);
2082
3121
  }
2083
3122
  } else {
@@ -2085,38 +3124,13 @@ class Server {
2085
3124
  }
2086
3125
  }
2087
3126
 
3127
+ /**
3128
+ * @param {string | string[]} watchPath
3129
+ * @param {WatchOptions} [watchOptions]
3130
+ */
2088
3131
  watchFiles(watchPath, watchOptions) {
2089
- // duplicate the same massaging of options that watchpack performs
2090
- // https://github.com/webpack/watchpack/blob/master/lib/DirectoryWatcher.js#L49
2091
- // this isn't an elegant solution, but we'll improve it in the future
2092
- // eslint-disable-next-line no-undefined
2093
- const usePolling =
2094
- typeof watchOptions.usePolling !== "undefined"
2095
- ? watchOptions.usePolling
2096
- : Boolean(watchOptions.poll);
2097
- const interval =
2098
- // eslint-disable-next-line no-nested-ternary
2099
- typeof watchOptions.interval !== "undefined"
2100
- ? watchOptions.interval
2101
- : typeof watchOptions.poll === "number"
2102
- ? watchOptions.poll
2103
- : // eslint-disable-next-line no-undefined
2104
- undefined;
2105
-
2106
- const finalWatchOptions = {
2107
- ignoreInitial: true,
2108
- persistent: true,
2109
- followSymlinks: false,
2110
- atomic: false,
2111
- alwaysStat: true,
2112
- ignorePermissionErrors: true,
2113
- ignored: watchOptions.ignored,
2114
- usePolling,
2115
- interval,
2116
- };
2117
-
2118
3132
  const chokidar = require("chokidar");
2119
- const watcher = chokidar.watch(watchPath, finalWatchOptions);
3133
+ const watcher = chokidar.watch(watchPath, watchOptions);
2120
3134
 
2121
3135
  // disabling refreshing on changing the content
2122
3136
  if (this.options.liveReload) {
@@ -2134,44 +3148,65 @@ class Server {
2134
3148
  this.staticWatchers.push(watcher);
2135
3149
  }
2136
3150
 
2137
- invalidate(callback) {
3151
+ /**
3152
+ * @param {import("webpack-dev-middleware").Callback} [callback]
3153
+ */
3154
+ invalidate(callback = () => {}) {
2138
3155
  if (this.middleware) {
2139
3156
  this.middleware.invalidate(callback);
2140
3157
  }
2141
3158
  }
2142
3159
 
3160
+ /**
3161
+ * @returns {Promise<void>}
3162
+ */
2143
3163
  async start() {
2144
3164
  await this.normalizeOptions();
2145
3165
 
2146
3166
  if (this.options.ipc) {
2147
- await new Promise((resolve, reject) => {
2148
- const net = require("net");
2149
- const socket = new net.Socket();
2150
-
2151
- socket.on("error", (error) => {
2152
- if (error.code === "ECONNREFUSED") {
2153
- // No other server listening on this socket so it can be safely removed
2154
- fs.unlinkSync(this.options.ipc);
2155
-
2156
- 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
+ }
2157
3190
 
2158
- return;
2159
- } else if (error.code === "ENOENT") {
2160
- resolve();
2161
-
2162
- return;
2163
- }
2164
-
2165
- reject(error);
2166
- });
3191
+ reject(error);
3192
+ }
3193
+ );
2167
3194
 
2168
- socket.connect({ path: this.options.ipc }, () => {
2169
- throw new Error(`IPC "${this.options.ipc}" is already used`);
2170
- });
2171
- });
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
+ );
2172
3203
  } else {
2173
- this.options.host = await Server.getHostname(this.options.host);
2174
- 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
+ );
2175
3210
  }
2176
3211
 
2177
3212
  await this.initialize();
@@ -2180,17 +3215,23 @@ class Server {
2180
3215
  ? { path: this.options.ipc }
2181
3216
  : { host: this.options.host, port: this.options.port };
2182
3217
 
2183
- await new Promise((resolve) => {
2184
- this.server.listen(listenOptions, () => {
2185
- resolve();
2186
- });
2187
- });
3218
+ await /** @type {Promise<void>} */ (
3219
+ new Promise((resolve) => {
3220
+ /** @type {import("http").Server} */
3221
+ (this.server).listen(listenOptions, () => {
3222
+ resolve();
3223
+ });
3224
+ })
3225
+ );
2188
3226
 
2189
3227
  if (this.options.ipc) {
2190
3228
  // chmod 666 (rw rw rw)
2191
3229
  const READ_WRITE = 438;
2192
3230
 
2193
- await fs.promises.chmod(this.options.ipc, READ_WRITE);
3231
+ await fs.promises.chmod(
3232
+ /** @type {string} */ (this.options.ipc),
3233
+ READ_WRITE
3234
+ );
2194
3235
  }
2195
3236
 
2196
3237
  if (this.options.webSocketServer) {
@@ -2208,19 +3249,27 @@ class Server {
2208
3249
  }
2209
3250
  }
2210
3251
 
3252
+ /**
3253
+ * @param {(err?: Error) => void} [callback]
3254
+ */
2211
3255
  startCallback(callback = () => {}) {
2212
3256
  this.start()
2213
- .then(() => callback(null), callback)
3257
+ .then(() => callback(), callback)
2214
3258
  .catch(callback);
2215
3259
  }
2216
3260
 
3261
+ /**
3262
+ * @returns {Promise<void>}
3263
+ */
2217
3264
  async stop() {
2218
3265
  if (this.bonjour) {
2219
- await new Promise((resolve) => {
2220
- this.stopBonjour(() => {
2221
- resolve();
2222
- });
2223
- });
3266
+ await /** @type {Promise<void>} */ (
3267
+ new Promise((resolve) => {
3268
+ this.stopBonjour(() => {
3269
+ resolve();
3270
+ });
3271
+ })
3272
+ );
2224
3273
  }
2225
3274
 
2226
3275
  this.webSocketProxies = [];
@@ -2230,48 +3279,60 @@ class Server {
2230
3279
  this.staticWatchers = [];
2231
3280
 
2232
3281
  if (this.webSocketServer) {
2233
- await new Promise((resolve) => {
2234
- this.webSocketServer.implementation.close(() => {
2235
- this.webSocketServer = null;
3282
+ await /** @type {Promise<void>} */ (
3283
+ new Promise((resolve) => {
3284
+ /** @type {WebSocketServerImplementation} */
3285
+ (this.webSocketServer).implementation.close(() => {
3286
+ this.webSocketServer = null;
2236
3287
 
2237
- resolve();
2238
- });
3288
+ resolve();
3289
+ });
2239
3290
 
2240
- for (const client of this.webSocketServer.clients) {
2241
- client.terminate();
2242
- }
3291
+ for (const client of /** @type {WebSocketServerImplementation} */ (
3292
+ this.webSocketServer
3293
+ ).clients) {
3294
+ client.terminate();
3295
+ }
2243
3296
 
2244
- this.webSocketServer.clients = [];
2245
- });
3297
+ /** @type {WebSocketServerImplementation} */
3298
+ (this.webSocketServer).clients = [];
3299
+ })
3300
+ );
2246
3301
  }
2247
3302
 
2248
3303
  if (this.server) {
2249
- await new Promise((resolve) => {
2250
- this.server.close(() => {
2251
- 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;
2252
3309
 
2253
- resolve();
2254
- });
3310
+ resolve();
3311
+ });
2255
3312
 
2256
- for (const socket of this.sockets) {
2257
- socket.destroy();
2258
- }
3313
+ for (const socket of this.sockets) {
3314
+ socket.destroy();
3315
+ }
2259
3316
 
2260
- this.sockets = [];
2261
- });
3317
+ this.sockets = [];
3318
+ })
3319
+ );
2262
3320
 
2263
3321
  if (this.middleware) {
2264
- await new Promise((resolve, reject) => {
2265
- this.middleware.close((error) => {
2266
- if (error) {
2267
- reject(error);
2268
-
2269
- return;
2270
- }
2271
-
2272
- resolve();
2273
- });
2274
- });
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
+ );
2275
3336
 
2276
3337
  this.middleware = null;
2277
3338
  }
@@ -2284,22 +3345,29 @@ class Server {
2284
3345
  }
2285
3346
  }
2286
3347
 
3348
+ /**
3349
+ * @param {(err?: Error) => void} [callback]
3350
+ */
2287
3351
  stopCallback(callback = () => {}) {
2288
3352
  this.stop()
2289
- .then(() => callback(null), callback)
3353
+ .then(() => callback(), callback)
2290
3354
  .catch(callback);
2291
3355
  }
2292
3356
 
2293
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
+ */
2294
3364
  listen(port, hostname, fn) {
2295
3365
  util.deprecate(
2296
3366
  () => {},
2297
- "'listen' is deprecated. Please use async 'start' or 'startCallback' methods.",
3367
+ "'listen' is deprecated. Please use the async 'start' or 'startCallback' method.",
2298
3368
  "DEP_WEBPACK_DEV_SERVER_LISTEN"
2299
3369
  )();
2300
3370
 
2301
- this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
2302
-
2303
3371
  if (typeof port === "function") {
2304
3372
  fn = port;
2305
3373
  }
@@ -2336,7 +3404,7 @@ class Server {
2336
3404
  this.options.host = hostname;
2337
3405
  }
2338
3406
 
2339
- return this.start()
3407
+ this.start()
2340
3408
  .then(() => {
2341
3409
  if (fn) {
2342
3410
  fn.call(this.server);
@@ -2350,18 +3418,22 @@ class Server {
2350
3418
  });
2351
3419
  }
2352
3420
 
3421
+ /**
3422
+ * @param {(err?: Error) => void} [callback]
3423
+ * @returns {void}
3424
+ */
2353
3425
  // TODO remove in the next major release
2354
3426
  close(callback) {
2355
3427
  util.deprecate(
2356
3428
  () => {},
2357
- "'close' is deprecated. Please use async 'stop' or 'stopCallback' methods.",
3429
+ "'close' is deprecated. Please use the async 'stop' or 'stopCallback' method.",
2358
3430
  "DEP_WEBPACK_DEV_SERVER_CLOSE"
2359
3431
  )();
2360
3432
 
2361
- return this.stop()
3433
+ this.stop()
2362
3434
  .then(() => {
2363
3435
  if (callback) {
2364
- callback(null);
3436
+ callback();
2365
3437
  }
2366
3438
  })
2367
3439
  .catch((error) => {
@@ -2372,48 +3444,4 @@ class Server {
2372
3444
  }
2373
3445
  }
2374
3446
 
2375
- const mergeExports = (obj, exports) => {
2376
- const descriptors = Object.getOwnPropertyDescriptors(exports);
2377
-
2378
- for (const name of Object.keys(descriptors)) {
2379
- const descriptor = descriptors[name];
2380
-
2381
- if (descriptor.get) {
2382
- const fn = descriptor.get;
2383
-
2384
- Object.defineProperty(obj, name, {
2385
- configurable: false,
2386
- enumerable: true,
2387
- get: fn,
2388
- });
2389
- } else if (typeof descriptor.value === "object") {
2390
- Object.defineProperty(obj, name, {
2391
- configurable: false,
2392
- enumerable: true,
2393
- writable: false,
2394
- value: mergeExports({}, descriptor.value),
2395
- });
2396
- } else {
2397
- throw new Error(
2398
- "Exposed values must be either a getter or an nested object"
2399
- );
2400
- }
2401
- }
2402
-
2403
- return Object.freeze(obj);
2404
- };
2405
-
2406
- module.exports = mergeExports(Server, {
2407
- get schema() {
2408
- return schema;
2409
- },
2410
- // TODO compatibility with webpack v4, remove it after drop
2411
- cli: {
2412
- get getArguments() {
2413
- return () => require("../bin/cli-flags");
2414
- },
2415
- get processArguments() {
2416
- return require("../bin/process-arguments");
2417
- },
2418
- },
2419
- });
3447
+ module.exports = Server;