webpack-dev-server 5.0.2 → 5.2.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/README.md +1 -1
- package/client/clients/SockJSClient.js +7 -7
- package/client/clients/WebSocketClient.js +7 -7
- package/client/index.js +303 -23
- package/client/modules/logger/index.js +240 -133
- package/client/modules/sockjs-client/index.js +18 -12
- package/client/overlay.js +365 -18
- package/client/progress.js +125 -0
- package/client/socket.js +5 -1
- package/client/utils/log.js +1 -17
- package/lib/Server.js +831 -479
- package/lib/options.json +23 -5
- package/package.json +37 -36
- package/types/bin/cli-flags.d.ts +15 -0
- package/types/lib/Server.d.ts +216 -339
- package/types/lib/servers/WebsocketServer.d.ts +0 -1
- package/client/overlay/fsm.js +0 -64
- package/client/overlay/runtime-error.js +0 -47
- package/client/overlay/state-machine.js +0 -100
- package/client/overlay/styles.js +0 -74
- package/client/utils/createSocketURL.js +0 -121
- package/client/utils/getCurrentScriptSource.js +0 -24
- package/client/utils/parseURL.js +0 -36
- package/client/utils/reloadApp.js +0 -63
- package/client/utils/stripAnsi.js +0 -18
package/lib/Server.js
CHANGED
|
@@ -18,9 +18,6 @@ const schema = require("./options.json");
|
|
|
18
18
|
/** @typedef {import("webpack").Stats} Stats */
|
|
19
19
|
/** @typedef {import("webpack").MultiStats} MultiStats */
|
|
20
20
|
/** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
|
|
21
|
-
/** @typedef {import("express").NextFunction} NextFunction */
|
|
22
|
-
/** @typedef {import("express").RequestHandler} ExpressRequestHandler */
|
|
23
|
-
/** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
|
|
24
21
|
/** @typedef {import("chokidar").WatchOptions} WatchOptions */
|
|
25
22
|
/** @typedef {import("chokidar").FSWatcher} FSWatcher */
|
|
26
23
|
/** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
|
|
@@ -34,14 +31,32 @@ const schema = require("./options.json");
|
|
|
34
31
|
/** @typedef {import("ipaddr.js").IPv4} IPv4 */
|
|
35
32
|
/** @typedef {import("ipaddr.js").IPv6} IPv6 */
|
|
36
33
|
/** @typedef {import("net").Socket} Socket */
|
|
34
|
+
/** @typedef {import("http").Server} HTTPServer*/
|
|
37
35
|
/** @typedef {import("http").IncomingMessage} IncomingMessage */
|
|
38
36
|
/** @typedef {import("http").ServerResponse} ServerResponse */
|
|
39
37
|
/** @typedef {import("open").Options} OpenOptions */
|
|
38
|
+
/** @typedef {import("express").Application} ExpressApplication */
|
|
39
|
+
/** @typedef {import("express").RequestHandler} ExpressRequestHandler */
|
|
40
|
+
/** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
|
|
41
|
+
/** @typedef {import("express").Request} ExpressRequest */
|
|
42
|
+
/** @typedef {import("express").Response} ExpressResponse */
|
|
43
|
+
|
|
44
|
+
/** @typedef {(err?: any) => void} NextFunction */
|
|
45
|
+
/** @typedef {(req: IncomingMessage, res: ServerResponse) => void} SimpleHandleFunction */
|
|
46
|
+
/** @typedef {(req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} NextHandleFunction */
|
|
47
|
+
/** @typedef {(err: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} ErrorHandleFunction */
|
|
48
|
+
/** @typedef {SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction} HandleFunction */
|
|
40
49
|
|
|
41
50
|
/** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
|
|
42
51
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @template {BasicApplication} [T=ExpressApplication]
|
|
54
|
+
* @typedef {T extends ExpressApplication ? ExpressRequest : IncomingMessage} Request
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* @template {BasicApplication} [T=ExpressApplication]
|
|
58
|
+
* @typedef {T extends ExpressApplication ? ExpressResponse : ServerResponse} Response
|
|
59
|
+
*/
|
|
45
60
|
|
|
46
61
|
/**
|
|
47
62
|
* @template {Request} T
|
|
@@ -88,8 +103,16 @@ const schema = require("./options.json");
|
|
|
88
103
|
*/
|
|
89
104
|
|
|
90
105
|
/**
|
|
106
|
+
* @template {BasicApplication} [A=ExpressApplication]
|
|
107
|
+
* @template {BasicServer} [S=import("http").Server]
|
|
108
|
+
* @typedef {"http" | "https" | "spdy" | "http2" | string | function(ServerOptions, A): S} ServerType
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @template {BasicApplication} [A=ExpressApplication]
|
|
113
|
+
* @template {BasicServer} [S=import("http").Server]
|
|
91
114
|
* @typedef {Object} ServerConfiguration
|
|
92
|
-
* @property {
|
|
115
|
+
* @property {ServerType<A, S>} [type]
|
|
93
116
|
* @property {ServerOptions} [options]
|
|
94
117
|
*/
|
|
95
118
|
|
|
@@ -126,10 +149,6 @@ const schema = require("./options.json");
|
|
|
126
149
|
* @typedef {(ProxyConfigArrayItem | ((req?: Request | undefined, res?: Response | undefined, next?: NextFunction | undefined) => ProxyConfigArrayItem))[]} ProxyConfigArray
|
|
127
150
|
*/
|
|
128
151
|
|
|
129
|
-
/**
|
|
130
|
-
* @typedef {{ [url: string]: string | ProxyConfigArrayItem }} ProxyConfigMap
|
|
131
|
-
*/
|
|
132
|
-
|
|
133
152
|
/**
|
|
134
153
|
* @typedef {Object} OpenApp
|
|
135
154
|
* @property {string} [name]
|
|
@@ -177,10 +196,23 @@ const schema = require("./options.json");
|
|
|
177
196
|
*/
|
|
178
197
|
|
|
179
198
|
/**
|
|
180
|
-
* @
|
|
199
|
+
* @template {BasicApplication} [T=ExpressApplication]
|
|
200
|
+
* @typedef {T extends ExpressApplication ? ExpressRequestHandler | ExpressErrorRequestHandler : HandleFunction} MiddlewareHandler
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @typedef {{ name?: string, path?: string, middleware: MiddlewareHandler }} MiddlewareObject
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @typedef {MiddlewareObject | MiddlewareHandler } Middleware
|
|
181
209
|
*/
|
|
182
210
|
|
|
211
|
+
/** @typedef {import("net").Server | import("tls").Server} BasicServer */
|
|
212
|
+
|
|
183
213
|
/**
|
|
214
|
+
* @template {BasicApplication} [A=ExpressApplication]
|
|
215
|
+
* @template {BasicServer} [S=import("http").Server]
|
|
184
216
|
* @typedef {Object} Configuration
|
|
185
217
|
* @property {boolean | string} [ipc]
|
|
186
218
|
* @property {Host} [host]
|
|
@@ -194,17 +226,16 @@ const schema = require("./options.json");
|
|
|
194
226
|
* @property {boolean | Record<string, never> | BonjourOptions} [bonjour]
|
|
195
227
|
* @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
|
|
196
228
|
* @property {boolean | string | Static | Array<string | Static>} [static]
|
|
197
|
-
* @property {
|
|
198
|
-
* @property {
|
|
199
|
-
* @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server]
|
|
229
|
+
* @property {ServerType<A, S> | ServerConfiguration<A, S>} [server]
|
|
230
|
+
* @property {() => Promise<A>} [app]
|
|
200
231
|
* @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
|
|
201
|
-
* @property {
|
|
232
|
+
* @property {ProxyConfigArray} [proxy]
|
|
202
233
|
* @property {boolean | string | Open | Array<string | Open>} [open]
|
|
203
234
|
* @property {boolean} [setupExitSignals]
|
|
204
235
|
* @property {boolean | ClientConfiguration} [client]
|
|
205
|
-
* @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
|
|
206
|
-
* @property {(devServer: Server) => void} [onListening]
|
|
207
|
-
* @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
|
|
236
|
+
* @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response> | undefined) => Headers)} [headers]
|
|
237
|
+
* @property {(devServer: Server<A, S>) => void} [onListening]
|
|
238
|
+
* @property {(middlewares: Middleware[], devServer: Server<A, S>) => Middleware[]} [setupMiddlewares]
|
|
208
239
|
*/
|
|
209
240
|
|
|
210
241
|
if (!process.env.WEBPACK_SERVE) {
|
|
@@ -249,10 +280,48 @@ const encodeOverlaySettings = (setting) =>
|
|
|
249
280
|
? encodeURIComponent(setting.toString())
|
|
250
281
|
: setting;
|
|
251
282
|
|
|
283
|
+
// Working for overload, because typescript doesn't support this yes
|
|
284
|
+
/**
|
|
285
|
+
* @overload
|
|
286
|
+
* @param {NextHandleFunction} fn
|
|
287
|
+
* @returns {BasicApplication}
|
|
288
|
+
*/
|
|
289
|
+
/**
|
|
290
|
+
* @overload
|
|
291
|
+
* @param {HandleFunction} fn
|
|
292
|
+
* @returns {BasicApplication}
|
|
293
|
+
*/
|
|
294
|
+
/**
|
|
295
|
+
* @overload
|
|
296
|
+
* @param {string} route
|
|
297
|
+
* @param {NextHandleFunction} fn
|
|
298
|
+
* @returns {BasicApplication}
|
|
299
|
+
*/
|
|
300
|
+
/**
|
|
301
|
+
* @param {string} route
|
|
302
|
+
* @param {HandleFunction} fn
|
|
303
|
+
* @returns {BasicApplication}
|
|
304
|
+
*/
|
|
305
|
+
// eslint-disable-next-line no-unused-vars
|
|
306
|
+
function useFn(route, fn) {
|
|
307
|
+
return /** @type {BasicApplication} */ ({});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @typedef {Object} BasicApplication
|
|
314
|
+
* @property {typeof useFn} use
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @template {BasicApplication} [A=ExpressApplication]
|
|
319
|
+
* @template {BasicServer} [S=HTTPServer]
|
|
320
|
+
*/
|
|
252
321
|
class Server {
|
|
253
322
|
/**
|
|
254
|
-
* @param {Configuration
|
|
255
|
-
* @param {Compiler | MultiCompiler
|
|
323
|
+
* @param {Configuration<A, S>} options
|
|
324
|
+
* @param {Compiler | MultiCompiler} compiler
|
|
256
325
|
*/
|
|
257
326
|
constructor(options = {}, compiler) {
|
|
258
327
|
validate(/** @type {Schema} */ (schema), options, {
|
|
@@ -260,12 +329,12 @@ class Server {
|
|
|
260
329
|
baseDataPath: "options",
|
|
261
330
|
});
|
|
262
331
|
|
|
263
|
-
this.compiler =
|
|
332
|
+
this.compiler = compiler;
|
|
264
333
|
/**
|
|
265
334
|
* @type {ReturnType<Compiler["getInfrastructureLogger"]>}
|
|
266
335
|
* */
|
|
267
336
|
this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
|
|
268
|
-
this.options =
|
|
337
|
+
this.options = options;
|
|
269
338
|
/**
|
|
270
339
|
* @type {FSWatcher[]}
|
|
271
340
|
*/
|
|
@@ -328,11 +397,61 @@ class Server {
|
|
|
328
397
|
}
|
|
329
398
|
|
|
330
399
|
/**
|
|
331
|
-
* @param {string}
|
|
400
|
+
* @param {string} gatewayOrFamily or family
|
|
401
|
+
* @param {boolean} [isInternal] ip should be internal
|
|
332
402
|
* @returns {string | undefined}
|
|
333
403
|
*/
|
|
334
|
-
static findIp(
|
|
335
|
-
|
|
404
|
+
static findIp(gatewayOrFamily, isInternal) {
|
|
405
|
+
if (gatewayOrFamily === "v4" || gatewayOrFamily === "v6") {
|
|
406
|
+
let host;
|
|
407
|
+
|
|
408
|
+
const networks = Object.values(os.networkInterfaces())
|
|
409
|
+
// eslint-disable-next-line no-shadow
|
|
410
|
+
.flatMap((networks) => networks ?? [])
|
|
411
|
+
.filter((network) => {
|
|
412
|
+
if (!network || !network.address) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (network.family !== `IP${gatewayOrFamily}`) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (
|
|
421
|
+
typeof isInternal !== "undefined" &&
|
|
422
|
+
network.internal !== isInternal
|
|
423
|
+
) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (gatewayOrFamily === "v6") {
|
|
428
|
+
const range = ipaddr.parse(network.address).range();
|
|
429
|
+
|
|
430
|
+
if (
|
|
431
|
+
range !== "ipv4Mapped" &&
|
|
432
|
+
range !== "uniqueLocal" &&
|
|
433
|
+
range !== "loopback"
|
|
434
|
+
) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return network.address;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (networks.length > 0) {
|
|
443
|
+
// Take the first network found
|
|
444
|
+
host = networks[0].address;
|
|
445
|
+
|
|
446
|
+
if (host.includes(":")) {
|
|
447
|
+
host = `[${host}]`;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return host;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const gatewayIp = ipaddr.parse(gatewayOrFamily);
|
|
336
455
|
|
|
337
456
|
// Look for the matching interface in all local interfaces.
|
|
338
457
|
for (const addresses of Object.values(os.networkInterfaces())) {
|
|
@@ -352,32 +471,22 @@ class Server {
|
|
|
352
471
|
}
|
|
353
472
|
}
|
|
354
473
|
|
|
474
|
+
// TODO remove me in the next major release, we have `findIp`
|
|
355
475
|
/**
|
|
356
476
|
* @param {"v4" | "v6"} family
|
|
357
477
|
* @returns {Promise<string | undefined>}
|
|
358
478
|
*/
|
|
359
479
|
static async internalIP(family) {
|
|
360
|
-
|
|
361
|
-
const { gateway } = await require("default-gateway")[family]();
|
|
362
|
-
|
|
363
|
-
return Server.findIp(gateway);
|
|
364
|
-
} catch {
|
|
365
|
-
// ignore
|
|
366
|
-
}
|
|
480
|
+
return Server.findIp(family, false);
|
|
367
481
|
}
|
|
368
482
|
|
|
483
|
+
// TODO remove me in the next major release, we have `findIp`
|
|
369
484
|
/**
|
|
370
485
|
* @param {"v4" | "v6"} family
|
|
371
486
|
* @returns {string | undefined}
|
|
372
487
|
*/
|
|
373
488
|
static internalIPSync(family) {
|
|
374
|
-
|
|
375
|
-
const { gateway } = require("default-gateway")[family].sync();
|
|
376
|
-
|
|
377
|
-
return Server.findIp(gateway);
|
|
378
|
-
} catch {
|
|
379
|
-
// ignore
|
|
380
|
-
}
|
|
489
|
+
return Server.findIp(family, false);
|
|
381
490
|
}
|
|
382
491
|
|
|
383
492
|
/**
|
|
@@ -387,14 +496,12 @@ class Server {
|
|
|
387
496
|
static async getHostname(hostname) {
|
|
388
497
|
if (hostname === "local-ip") {
|
|
389
498
|
return (
|
|
390
|
-
(
|
|
391
|
-
(await Server.internalIP("v6")) ||
|
|
392
|
-
"0.0.0.0"
|
|
499
|
+
Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0"
|
|
393
500
|
);
|
|
394
501
|
} else if (hostname === "local-ipv4") {
|
|
395
|
-
return
|
|
502
|
+
return Server.findIp("v4", false) || "0.0.0.0";
|
|
396
503
|
} else if (hostname === "local-ipv6") {
|
|
397
|
-
return
|
|
504
|
+
return Server.findIp("v6", false) || "::";
|
|
398
505
|
}
|
|
399
506
|
|
|
400
507
|
return hostname;
|
|
@@ -474,7 +581,11 @@ class Server {
|
|
|
474
581
|
* @returns bool
|
|
475
582
|
*/
|
|
476
583
|
static isWebTarget(compiler) {
|
|
477
|
-
|
|
584
|
+
if (compiler.platform && compiler.platform.web) {
|
|
585
|
+
return compiler.platform.web;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// TODO improve for the next major version and keep only `webTargets` to fallback for old versions
|
|
478
589
|
if (
|
|
479
590
|
compiler.options.externalsPresets &&
|
|
480
591
|
compiler.options.externalsPresets.web
|
|
@@ -494,6 +605,7 @@ class Server {
|
|
|
494
605
|
"webworker",
|
|
495
606
|
"electron-preload",
|
|
496
607
|
"electron-renderer",
|
|
608
|
+
"nwjs",
|
|
497
609
|
"node-webkit",
|
|
498
610
|
// eslint-disable-next-line no-undefined
|
|
499
611
|
undefined,
|
|
@@ -541,9 +653,7 @@ class Server {
|
|
|
541
653
|
if (typeof webSocketURL.protocol !== "undefined") {
|
|
542
654
|
protocol = webSocketURL.protocol;
|
|
543
655
|
} else {
|
|
544
|
-
protocol =
|
|
545
|
-
/** @type {ServerConfiguration} */
|
|
546
|
-
(this.options.server).type === "http" ? "ws:" : "wss:";
|
|
656
|
+
protocol = this.isTlsServer ? "wss:" : "ws:";
|
|
547
657
|
}
|
|
548
658
|
|
|
549
659
|
searchParams.set("protocol", protocol);
|
|
@@ -687,15 +797,12 @@ class Server {
|
|
|
687
797
|
webSocketURLStr = searchParams.toString();
|
|
688
798
|
}
|
|
689
799
|
|
|
690
|
-
additionalEntries.push(
|
|
691
|
-
`${require.resolve("../client/index.js")}?${webSocketURLStr}`,
|
|
692
|
-
);
|
|
800
|
+
additionalEntries.push(`${this.getClientEntry()}?${webSocketURLStr}`);
|
|
693
801
|
}
|
|
694
802
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
additionalEntries.push(require.resolve("webpack/hot/dev-server"));
|
|
803
|
+
const clientHotEntry = this.getClientHotEntry();
|
|
804
|
+
if (clientHotEntry) {
|
|
805
|
+
additionalEntries.push(clientHotEntry);
|
|
699
806
|
}
|
|
700
807
|
|
|
701
808
|
const webpack = compiler.webpack || require("webpack");
|
|
@@ -1011,39 +1118,41 @@ class Server {
|
|
|
1011
1118
|
? options.hot
|
|
1012
1119
|
: true;
|
|
1013
1120
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1121
|
+
if (
|
|
1122
|
+
typeof options.server === "function" ||
|
|
1123
|
+
typeof options.server === "string"
|
|
1124
|
+
) {
|
|
1125
|
+
options.server = {
|
|
1126
|
+
type: options.server,
|
|
1127
|
+
options: {},
|
|
1128
|
+
};
|
|
1129
|
+
} else {
|
|
1130
|
+
const serverOptions =
|
|
1131
|
+
/** @type {ServerConfiguration<A, S>} */
|
|
1132
|
+
(options.server || {});
|
|
1133
|
+
|
|
1134
|
+
options.server = {
|
|
1135
|
+
type: serverOptions.type || "http",
|
|
1136
|
+
options: { ...serverOptions.options },
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const serverOptions = /** @type {ServerOptions} */ (options.server.options);
|
|
1027
1141
|
|
|
1028
1142
|
if (
|
|
1029
1143
|
options.server.type === "spdy" &&
|
|
1030
|
-
typeof
|
|
1031
|
-
"undefined"
|
|
1144
|
+
typeof serverOptions.spdy === "undefined"
|
|
1032
1145
|
) {
|
|
1033
|
-
|
|
1034
|
-
(options.server.options).spdy = {
|
|
1035
|
-
protocols: ["h2", "http/1.1"],
|
|
1036
|
-
};
|
|
1146
|
+
serverOptions.spdy = { protocols: ["h2", "http/1.1"] };
|
|
1037
1147
|
}
|
|
1038
1148
|
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
) {
|
|
1045
|
-
|
|
1046
|
-
(options.server.options).requestCert = false;
|
|
1149
|
+
if (
|
|
1150
|
+
options.server.type === "https" ||
|
|
1151
|
+
options.server.type === "http2" ||
|
|
1152
|
+
options.server.type === "spdy"
|
|
1153
|
+
) {
|
|
1154
|
+
if (typeof serverOptions.requestCert === "undefined") {
|
|
1155
|
+
serverOptions.requestCert = false;
|
|
1047
1156
|
}
|
|
1048
1157
|
|
|
1049
1158
|
const httpsProperties =
|
|
@@ -1051,19 +1160,13 @@ class Server {
|
|
|
1051
1160
|
(["ca", "cert", "crl", "key", "pfx"]);
|
|
1052
1161
|
|
|
1053
1162
|
for (const property of httpsProperties) {
|
|
1054
|
-
if (
|
|
1055
|
-
typeof (
|
|
1056
|
-
/** @type {ServerOptions} */ (options.server.options)[property]
|
|
1057
|
-
) === "undefined"
|
|
1058
|
-
) {
|
|
1163
|
+
if (typeof serverOptions[property] === "undefined") {
|
|
1059
1164
|
// eslint-disable-next-line no-continue
|
|
1060
1165
|
continue;
|
|
1061
1166
|
}
|
|
1062
1167
|
|
|
1063
1168
|
/** @type {any} */
|
|
1064
|
-
const value =
|
|
1065
|
-
/** @type {ServerOptions} */
|
|
1066
|
-
(options.server.options)[property];
|
|
1169
|
+
const value = serverOptions[property];
|
|
1067
1170
|
/**
|
|
1068
1171
|
* @param {string | Buffer | undefined} item
|
|
1069
1172
|
* @returns {string | Buffer | undefined}
|
|
@@ -1091,17 +1194,14 @@ class Server {
|
|
|
1091
1194
|
};
|
|
1092
1195
|
|
|
1093
1196
|
/** @type {any} */
|
|
1094
|
-
(
|
|
1197
|
+
(serverOptions)[property] = Array.isArray(value)
|
|
1095
1198
|
? value.map((item) => readFile(item))
|
|
1096
1199
|
: readFile(value);
|
|
1097
1200
|
}
|
|
1098
1201
|
|
|
1099
1202
|
let fakeCert;
|
|
1100
1203
|
|
|
1101
|
-
if (
|
|
1102
|
-
!(/** @type {ServerOptions} */ (options.server.options).key) ||
|
|
1103
|
-
!(/** @type {ServerOptions} */ (options.server.options).cert)
|
|
1104
|
-
) {
|
|
1204
|
+
if (!serverOptions.key || !serverOptions.cert) {
|
|
1105
1205
|
const certificateDir = Server.findCacheDir();
|
|
1106
1206
|
const certificatePath = path.join(certificateDir, "server.pem");
|
|
1107
1207
|
let certificateExists;
|
|
@@ -1120,13 +1220,11 @@ class Server {
|
|
|
1120
1220
|
|
|
1121
1221
|
// cert is more than 30 days old, kill it with fire
|
|
1122
1222
|
if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
|
|
1123
|
-
const { rimraf } = require("rimraf");
|
|
1124
|
-
|
|
1125
1223
|
this.logger.info(
|
|
1126
1224
|
"SSL certificate is more than 30 days old. Removing...",
|
|
1127
1225
|
);
|
|
1128
1226
|
|
|
1129
|
-
await
|
|
1227
|
+
await fs.promises.rm(certificatePath, { recursive: true });
|
|
1130
1228
|
|
|
1131
1229
|
certificateExists = false;
|
|
1132
1230
|
}
|
|
@@ -1135,7 +1233,6 @@ class Server {
|
|
|
1135
1233
|
if (!certificateExists) {
|
|
1136
1234
|
this.logger.info("Generating SSL certificate...");
|
|
1137
1235
|
|
|
1138
|
-
// @ts-ignore
|
|
1139
1236
|
const selfsigned = require("selfsigned");
|
|
1140
1237
|
const attributes = [{ name: "commonName", value: "localhost" }];
|
|
1141
1238
|
const pems = selfsigned.generate(attributes, {
|
|
@@ -1216,14 +1313,8 @@ class Server {
|
|
|
1216
1313
|
this.logger.info(`SSL certificate: ${certificatePath}`);
|
|
1217
1314
|
}
|
|
1218
1315
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
/** @type {ServerOptions} */
|
|
1222
|
-
(options.server.options).key || fakeCert;
|
|
1223
|
-
/** @type {ServerOptions} */
|
|
1224
|
-
(options.server.options).cert =
|
|
1225
|
-
/** @type {ServerOptions} */
|
|
1226
|
-
(options.server.options).cert || fakeCert;
|
|
1316
|
+
serverOptions.key = serverOptions.key || fakeCert;
|
|
1317
|
+
serverOptions.cert = serverOptions.cert || fakeCert;
|
|
1227
1318
|
}
|
|
1228
1319
|
|
|
1229
1320
|
if (typeof options.ipc === "boolean") {
|
|
@@ -1285,15 +1376,15 @@ class Server {
|
|
|
1285
1376
|
*/
|
|
1286
1377
|
const result = [];
|
|
1287
1378
|
|
|
1288
|
-
options.open
|
|
1379
|
+
for (const item of options.open) {
|
|
1289
1380
|
if (typeof item === "string") {
|
|
1290
1381
|
result.push({ target: item, options: defaultOpenOptions });
|
|
1291
|
-
|
|
1292
|
-
|
|
1382
|
+
// eslint-disable-next-line no-continue
|
|
1383
|
+
continue;
|
|
1293
1384
|
}
|
|
1294
1385
|
|
|
1295
1386
|
result.push(...getOpenItemsFromObject(item));
|
|
1296
|
-
}
|
|
1387
|
+
}
|
|
1297
1388
|
|
|
1298
1389
|
/** @type {NormalizedOpen[]} */
|
|
1299
1390
|
(options.open) = result;
|
|
@@ -1317,48 +1408,45 @@ class Server {
|
|
|
1317
1408
|
* }
|
|
1318
1409
|
*/
|
|
1319
1410
|
if (typeof options.proxy !== "undefined") {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
if (typeof item === "function") {
|
|
1325
|
-
return item;
|
|
1326
|
-
}
|
|
1411
|
+
options.proxy = options.proxy.map((item) => {
|
|
1412
|
+
if (typeof item === "function") {
|
|
1413
|
+
return item;
|
|
1414
|
+
}
|
|
1327
1415
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1416
|
+
/**
|
|
1417
|
+
* @param {"info" | "warn" | "error" | "debug" | "silent" | undefined | "none" | "log" | "verbose"} level
|
|
1418
|
+
* @returns {"info" | "warn" | "error" | "debug" | "silent" | undefined}
|
|
1419
|
+
*/
|
|
1420
|
+
const getLogLevelForProxy = (level) => {
|
|
1421
|
+
if (level === "none") {
|
|
1422
|
+
return "silent";
|
|
1423
|
+
}
|
|
1336
1424
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1425
|
+
if (level === "log") {
|
|
1426
|
+
return "info";
|
|
1427
|
+
}
|
|
1340
1428
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1429
|
+
if (level === "verbose") {
|
|
1430
|
+
return "debug";
|
|
1431
|
+
}
|
|
1344
1432
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1433
|
+
return level;
|
|
1434
|
+
};
|
|
1347
1435
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1436
|
+
if (typeof item.logLevel === "undefined") {
|
|
1437
|
+
item.logLevel = getLogLevelForProxy(
|
|
1438
|
+
compilerOptions.infrastructureLogging
|
|
1439
|
+
? compilerOptions.infrastructureLogging.level
|
|
1440
|
+
: "info",
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1355
1443
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1444
|
+
if (typeof item.logProvider === "undefined") {
|
|
1445
|
+
item.logProvider = () => this.logger;
|
|
1446
|
+
}
|
|
1359
1447
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1448
|
+
return item;
|
|
1449
|
+
});
|
|
1362
1450
|
}
|
|
1363
1451
|
|
|
1364
1452
|
if (typeof options.setupExitSignals === "undefined") {
|
|
@@ -1529,8 +1617,9 @@ class Server {
|
|
|
1529
1617
|
}
|
|
1530
1618
|
|
|
1531
1619
|
/**
|
|
1620
|
+
* @template T
|
|
1532
1621
|
* @private
|
|
1533
|
-
* @returns {
|
|
1622
|
+
* @returns {T}
|
|
1534
1623
|
*/
|
|
1535
1624
|
getServerTransport() {
|
|
1536
1625
|
let implementation;
|
|
@@ -1560,9 +1649,8 @@ class Server {
|
|
|
1560
1649
|
try {
|
|
1561
1650
|
// eslint-disable-next-line import/no-dynamic-require
|
|
1562
1651
|
implementation = require(
|
|
1563
|
-
/** @type {WebSocketServerConfiguration} */
|
|
1564
|
-
|
|
1565
|
-
).type,
|
|
1652
|
+
/** @type {WebSocketServerConfiguration} */
|
|
1653
|
+
(this.options.webSocketServer).type,
|
|
1566
1654
|
);
|
|
1567
1655
|
} catch (error) {
|
|
1568
1656
|
implementationFound = false;
|
|
@@ -1570,9 +1658,9 @@ class Server {
|
|
|
1570
1658
|
}
|
|
1571
1659
|
break;
|
|
1572
1660
|
case "function":
|
|
1573
|
-
implementation =
|
|
1574
|
-
|
|
1575
|
-
|
|
1661
|
+
implementation =
|
|
1662
|
+
/** @type {WebSocketServerConfiguration} */
|
|
1663
|
+
(this.options.webSocketServer).type;
|
|
1576
1664
|
break;
|
|
1577
1665
|
default:
|
|
1578
1666
|
implementationFound = false;
|
|
@@ -1589,6 +1677,25 @@ class Server {
|
|
|
1589
1677
|
return implementation;
|
|
1590
1678
|
}
|
|
1591
1679
|
|
|
1680
|
+
/**
|
|
1681
|
+
* @returns {string}
|
|
1682
|
+
*/
|
|
1683
|
+
// eslint-disable-next-line class-methods-use-this
|
|
1684
|
+
getClientEntry() {
|
|
1685
|
+
return require.resolve("../client/index.js");
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* @returns {string | void}
|
|
1690
|
+
*/
|
|
1691
|
+
getClientHotEntry() {
|
|
1692
|
+
if (this.options.hot === "only") {
|
|
1693
|
+
return require.resolve("webpack/hot/only-dev-server");
|
|
1694
|
+
} else if (this.options.hot) {
|
|
1695
|
+
return require.resolve("webpack/hot/dev-server");
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1592
1699
|
/**
|
|
1593
1700
|
* @private
|
|
1594
1701
|
* @returns {void}
|
|
@@ -1638,12 +1745,22 @@ class Server {
|
|
|
1638
1745
|
* @returns {Promise<void>}
|
|
1639
1746
|
*/
|
|
1640
1747
|
async initialize() {
|
|
1748
|
+
this.setupHooks();
|
|
1749
|
+
|
|
1750
|
+
await this.setupApp();
|
|
1751
|
+
await this.createServer();
|
|
1752
|
+
|
|
1641
1753
|
if (this.options.webSocketServer) {
|
|
1642
1754
|
const compilers =
|
|
1643
1755
|
/** @type {MultiCompiler} */
|
|
1644
1756
|
(this.compiler).compilers || [this.compiler];
|
|
1645
1757
|
|
|
1646
|
-
|
|
1758
|
+
for (const compiler of compilers) {
|
|
1759
|
+
if (compiler.options.devServer === false) {
|
|
1760
|
+
// eslint-disable-next-line no-continue
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1647
1764
|
this.addAdditionalEntries(compiler);
|
|
1648
1765
|
|
|
1649
1766
|
const webpack = compiler.webpack || require("webpack");
|
|
@@ -1668,7 +1785,7 @@ class Server {
|
|
|
1668
1785
|
plugin.apply(compiler);
|
|
1669
1786
|
}
|
|
1670
1787
|
}
|
|
1671
|
-
}
|
|
1788
|
+
}
|
|
1672
1789
|
|
|
1673
1790
|
if (
|
|
1674
1791
|
this.options.client &&
|
|
@@ -1678,16 +1795,9 @@ class Server {
|
|
|
1678
1795
|
}
|
|
1679
1796
|
}
|
|
1680
1797
|
|
|
1681
|
-
this.setupHooks();
|
|
1682
|
-
this.setupApp();
|
|
1683
|
-
this.setupHostHeaderCheck();
|
|
1684
|
-
this.setupDevMiddleware();
|
|
1685
|
-
// Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
|
|
1686
|
-
this.setupBuiltInRoutes();
|
|
1687
1798
|
this.setupWatchFiles();
|
|
1688
1799
|
this.setupWatchStaticFiles();
|
|
1689
1800
|
this.setupMiddlewares();
|
|
1690
|
-
this.createServer();
|
|
1691
1801
|
|
|
1692
1802
|
if (this.options.setupExitSignals) {
|
|
1693
1803
|
const signals = ["SIGINT", "SIGTERM"];
|
|
@@ -1725,24 +1835,30 @@ class Server {
|
|
|
1725
1835
|
|
|
1726
1836
|
// Proxy WebSocket without the initial http request
|
|
1727
1837
|
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1838
|
+
const webSocketProxies =
|
|
1839
|
+
/** @type {RequestHandler[]} */
|
|
1840
|
+
(this.webSocketProxies);
|
|
1841
|
+
|
|
1842
|
+
for (const webSocketProxy of webSocketProxies) {
|
|
1843
|
+
/** @type {S} */
|
|
1731
1844
|
(this.server).on(
|
|
1732
1845
|
"upgrade",
|
|
1733
1846
|
/** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
|
|
1734
1847
|
(webSocketProxy).upgrade,
|
|
1735
1848
|
);
|
|
1736
|
-
}
|
|
1849
|
+
}
|
|
1737
1850
|
}
|
|
1738
1851
|
|
|
1739
1852
|
/**
|
|
1740
1853
|
* @private
|
|
1741
|
-
* @returns {void}
|
|
1854
|
+
* @returns {Promise<void>}
|
|
1742
1855
|
*/
|
|
1743
|
-
setupApp() {
|
|
1744
|
-
/** @type {
|
|
1745
|
-
this.app =
|
|
1856
|
+
async setupApp() {
|
|
1857
|
+
/** @type {A | undefined}*/
|
|
1858
|
+
this.app =
|
|
1859
|
+
typeof this.options.app === "function"
|
|
1860
|
+
? await this.options.app()
|
|
1861
|
+
: getExpress()();
|
|
1746
1862
|
}
|
|
1747
1863
|
|
|
1748
1864
|
/**
|
|
@@ -1796,204 +1912,330 @@ class Server {
|
|
|
1796
1912
|
* @private
|
|
1797
1913
|
* @returns {void}
|
|
1798
1914
|
*/
|
|
1799
|
-
|
|
1800
|
-
/** @type {
|
|
1801
|
-
(this.app).all(
|
|
1802
|
-
"*",
|
|
1803
|
-
/**
|
|
1804
|
-
* @param {Request} req
|
|
1805
|
-
* @param {Response} res
|
|
1806
|
-
* @param {NextFunction} next
|
|
1807
|
-
* @returns {void}
|
|
1808
|
-
*/
|
|
1809
|
-
(req, res, next) => {
|
|
1810
|
-
if (
|
|
1811
|
-
this.checkHeader(
|
|
1812
|
-
/** @type {{ [key: string]: string | undefined }} */
|
|
1813
|
-
(req.headers),
|
|
1814
|
-
"host",
|
|
1815
|
-
)
|
|
1816
|
-
) {
|
|
1817
|
-
return next();
|
|
1818
|
-
}
|
|
1915
|
+
setupWatchStaticFiles() {
|
|
1916
|
+
const watchFiles = /** @type {NormalizedStatic[]} */ (this.options.static);
|
|
1819
1917
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1918
|
+
if (watchFiles.length > 0) {
|
|
1919
|
+
for (const item of watchFiles) {
|
|
1920
|
+
if (item.watch) {
|
|
1921
|
+
this.watchFiles(item.directory, item.watch);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1823
1925
|
}
|
|
1824
1926
|
|
|
1825
1927
|
/**
|
|
1826
1928
|
* @private
|
|
1827
1929
|
* @returns {void}
|
|
1828
1930
|
*/
|
|
1829
|
-
|
|
1830
|
-
const
|
|
1931
|
+
setupWatchFiles() {
|
|
1932
|
+
const watchFiles = /** @type {WatchFiles[]} */ (this.options.watchFiles);
|
|
1831
1933
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1934
|
+
if (watchFiles.length > 0) {
|
|
1935
|
+
for (const item of watchFiles) {
|
|
1936
|
+
this.watchFiles(item.paths, item.options);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1837
1939
|
}
|
|
1838
1940
|
|
|
1839
1941
|
/**
|
|
1840
1942
|
* @private
|
|
1841
1943
|
* @returns {void}
|
|
1842
1944
|
*/
|
|
1843
|
-
|
|
1844
|
-
|
|
1945
|
+
setupMiddlewares() {
|
|
1946
|
+
/**
|
|
1947
|
+
* @type {Array<Middleware>}
|
|
1948
|
+
*/
|
|
1949
|
+
let middlewares = [];
|
|
1845
1950
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1951
|
+
// Register setup host header check for security
|
|
1952
|
+
middlewares.push({
|
|
1953
|
+
name: "host-header-check",
|
|
1954
|
+
/**
|
|
1955
|
+
* @param {Request} req
|
|
1956
|
+
* @param {Response} res
|
|
1957
|
+
* @param {NextFunction} next
|
|
1958
|
+
* @returns {void}
|
|
1959
|
+
*/
|
|
1960
|
+
middleware: (req, res, next) => {
|
|
1961
|
+
const headers =
|
|
1962
|
+
/** @type {{ [key: string]: string | undefined }} */
|
|
1963
|
+
(req.headers);
|
|
1964
|
+
const headerName = headers[":authority"] ? ":authority" : "host";
|
|
1849
1965
|
|
|
1850
|
-
|
|
1966
|
+
if (this.isValidHost(headers, headerName)) {
|
|
1967
|
+
next();
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1851
1970
|
|
|
1852
|
-
|
|
1971
|
+
res.statusCode = 403;
|
|
1972
|
+
res.end("Invalid Host header");
|
|
1973
|
+
},
|
|
1853
1974
|
});
|
|
1854
1975
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1976
|
+
// Register setup cross origin request check for security
|
|
1977
|
+
middlewares.push({
|
|
1978
|
+
name: "cross-origin-header-check",
|
|
1979
|
+
/**
|
|
1980
|
+
* @param {Request} req
|
|
1981
|
+
* @param {Response} res
|
|
1982
|
+
* @param {NextFunction} next
|
|
1983
|
+
* @returns {void}
|
|
1984
|
+
*/
|
|
1985
|
+
middleware: (req, res, next) => {
|
|
1986
|
+
const headers =
|
|
1987
|
+
/** @type {{ [key: string]: string | undefined }} */
|
|
1988
|
+
(req.headers);
|
|
1989
|
+
const headerName = headers[":authority"] ? ":authority" : "host";
|
|
1990
|
+
|
|
1991
|
+
if (this.isValidHost(headers, headerName, false)) {
|
|
1992
|
+
next();
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if (
|
|
1997
|
+
headers["sec-fetch-mode"] === "no-cors" &&
|
|
1998
|
+
headers["sec-fetch-site"] === "cross-site"
|
|
1999
|
+
) {
|
|
2000
|
+
res.statusCode = 403;
|
|
2001
|
+
res.end("Cross-Origin request blocked");
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
1858
2004
|
|
|
1859
|
-
|
|
2005
|
+
next();
|
|
2006
|
+
},
|
|
1860
2007
|
});
|
|
1861
2008
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2009
|
+
const isHTTP2 =
|
|
2010
|
+
/** @type {ServerConfiguration<A, S>} */ (this.options.server).type ===
|
|
2011
|
+
"http2";
|
|
1865
2012
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2013
|
+
if (isHTTP2) {
|
|
2014
|
+
// TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
|
|
2015
|
+
middlewares.push({
|
|
2016
|
+
name: "http2-status-message-patch",
|
|
2017
|
+
middleware:
|
|
2018
|
+
/** @type {NextHandleFunction} */
|
|
2019
|
+
(_req, res, next) => {
|
|
2020
|
+
Object.defineProperty(res, "statusMessage", {
|
|
2021
|
+
get() {
|
|
2022
|
+
return "";
|
|
2023
|
+
},
|
|
2024
|
+
set() {},
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
next();
|
|
2028
|
+
},
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// compress is placed last and uses unshift so that it will be the first middleware used
|
|
2033
|
+
if (this.options.compress && !isHTTP2) {
|
|
2034
|
+
const compression = require("compression");
|
|
2035
|
+
|
|
2036
|
+
middlewares.push({ name: "compression", middleware: compression() });
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
if (typeof this.options.headers !== "undefined") {
|
|
2040
|
+
middlewares.push({
|
|
2041
|
+
name: "set-headers",
|
|
2042
|
+
middleware: this.setHeaders.bind(this),
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
1871
2045
|
|
|
1872
|
-
|
|
2046
|
+
middlewares.push({
|
|
2047
|
+
name: "webpack-dev-middleware",
|
|
2048
|
+
middleware: /** @type {MiddlewareHandler} */ (this.middleware),
|
|
1873
2049
|
});
|
|
1874
2050
|
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2051
|
+
// Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
|
|
2052
|
+
middlewares.push({
|
|
2053
|
+
name: "webpack-dev-server-sockjs-bundle",
|
|
2054
|
+
path: "/__webpack_dev_server__/sockjs.bundle.js",
|
|
2055
|
+
/**
|
|
2056
|
+
* @param {Request} req
|
|
2057
|
+
* @param {Response} res
|
|
2058
|
+
* @param {NextFunction} next
|
|
2059
|
+
* @returns {void}
|
|
2060
|
+
*/
|
|
2061
|
+
middleware: (req, res, next) => {
|
|
2062
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
2063
|
+
next();
|
|
1883
2064
|
return;
|
|
1884
2065
|
}
|
|
1885
|
-
res.write(
|
|
1886
|
-
'<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
|
|
1887
|
-
);
|
|
1888
2066
|
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
res.write(`<h1>Assets Report:</h1>`);
|
|
2067
|
+
const clientPath = path.join(
|
|
2068
|
+
__dirname,
|
|
2069
|
+
"..",
|
|
2070
|
+
"client/modules/sockjs-client/index.js",
|
|
2071
|
+
);
|
|
1895
2072
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2073
|
+
// Express send Etag and other headers by default, so let's keep them for compatibility reasons
|
|
2074
|
+
if (typeof res.sendFile === "function") {
|
|
2075
|
+
res.sendFile(clientPath);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
1901
2078
|
|
|
1902
|
-
|
|
1903
|
-
// eslint-disable-next-line no-nested-ternary
|
|
1904
|
-
typeof item.name !== "undefined"
|
|
1905
|
-
? item.name
|
|
1906
|
-
: /** @type {MultiStats} */ (stats).stats
|
|
1907
|
-
? `unnamed[${index}]`
|
|
1908
|
-
: "unnamed";
|
|
2079
|
+
let stats;
|
|
1909
2080
|
|
|
1910
|
-
|
|
1911
|
-
|
|
2081
|
+
try {
|
|
2082
|
+
// TODO implement `inputFileSystem.createReadStream` in webpack
|
|
2083
|
+
stats = fs.statSync(clientPath);
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
next();
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
1912
2088
|
|
|
1913
|
-
|
|
2089
|
+
res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
|
|
2090
|
+
res.setHeader("Content-Length", stats.size);
|
|
1914
2091
|
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
const assetURL = `${publicPath}${assetName}`;
|
|
2092
|
+
if (req.method === "HEAD") {
|
|
2093
|
+
res.end();
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
1920
2096
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
</li>`,
|
|
1925
|
-
);
|
|
1926
|
-
}
|
|
2097
|
+
fs.createReadStream(clientPath).pipe(res);
|
|
2098
|
+
},
|
|
2099
|
+
});
|
|
1927
2100
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2101
|
+
middlewares.push({
|
|
2102
|
+
name: "webpack-dev-server-invalidate",
|
|
2103
|
+
path: "/webpack-dev-server/invalidate",
|
|
2104
|
+
/**
|
|
2105
|
+
* @param {Request} req
|
|
2106
|
+
* @param {Response} res
|
|
2107
|
+
* @param {NextFunction} next
|
|
2108
|
+
* @returns {void}
|
|
2109
|
+
*/
|
|
2110
|
+
middleware: (req, res, next) => {
|
|
2111
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
2112
|
+
next();
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
1931
2115
|
|
|
1932
|
-
|
|
1933
|
-
|
|
2116
|
+
this.invalidate();
|
|
2117
|
+
|
|
2118
|
+
res.end();
|
|
2119
|
+
},
|
|
1934
2120
|
});
|
|
1935
|
-
}
|
|
1936
2121
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2122
|
+
middlewares.push({
|
|
2123
|
+
name: "webpack-dev-server-open-editor",
|
|
2124
|
+
path: "/webpack-dev-server/open-editor",
|
|
2125
|
+
/**
|
|
2126
|
+
* @param {Request} req
|
|
2127
|
+
* @param {Response} res
|
|
2128
|
+
* @param {NextFunction} next
|
|
2129
|
+
* @returns {void}
|
|
2130
|
+
*/
|
|
2131
|
+
middleware: (req, res, next) => {
|
|
2132
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
2133
|
+
next();
|
|
2134
|
+
return;
|
|
1947
2135
|
}
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
2136
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
setupWatchFiles() {
|
|
1957
|
-
const { watchFiles } = this.options;
|
|
1958
|
-
|
|
1959
|
-
if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
|
|
1960
|
-
/** @type {WatchFiles[]} */
|
|
1961
|
-
(watchFiles).forEach((item) => {
|
|
1962
|
-
this.watchFiles(item.paths, item.options);
|
|
1963
|
-
});
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
2137
|
+
if (!req.url) {
|
|
2138
|
+
next();
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
1966
2141
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
*/
|
|
1971
|
-
setupMiddlewares() {
|
|
1972
|
-
/**
|
|
1973
|
-
* @type {Array<Middleware>}
|
|
1974
|
-
*/
|
|
1975
|
-
let middlewares = [];
|
|
2142
|
+
const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
2143
|
+
const params = new URLSearchParams(resolveUrl.search);
|
|
2144
|
+
const fileName = params.get("fileName");
|
|
1976
2145
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2146
|
+
if (typeof fileName === "string") {
|
|
2147
|
+
// @ts-ignore
|
|
2148
|
+
const launchEditor = require("launch-editor");
|
|
1980
2149
|
|
|
1981
|
-
|
|
1982
|
-
|
|
2150
|
+
launchEditor(fileName);
|
|
2151
|
+
}
|
|
1983
2152
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
path: "*",
|
|
1988
|
-
middleware: this.setHeaders.bind(this),
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
2153
|
+
res.end();
|
|
2154
|
+
},
|
|
2155
|
+
});
|
|
1991
2156
|
|
|
1992
2157
|
middlewares.push({
|
|
1993
|
-
name: "webpack-dev-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2158
|
+
name: "webpack-dev-server-assets",
|
|
2159
|
+
path: "/webpack-dev-server",
|
|
2160
|
+
/**
|
|
2161
|
+
* @param {Request} req
|
|
2162
|
+
* @param {Response} res
|
|
2163
|
+
* @param {NextFunction} next
|
|
2164
|
+
* @returns {void}
|
|
2165
|
+
*/
|
|
2166
|
+
middleware: (req, res, next) => {
|
|
2167
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
2168
|
+
next();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
if (!this.middleware) {
|
|
2173
|
+
next();
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
this.middleware.waitUntilValid((stats) => {
|
|
2178
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
2179
|
+
|
|
2180
|
+
// HEAD requests should not return body content
|
|
2181
|
+
if (req.method === "HEAD") {
|
|
2182
|
+
res.end();
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
res.write(
|
|
2187
|
+
'<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
|
|
2188
|
+
);
|
|
2189
|
+
|
|
2190
|
+
/**
|
|
2191
|
+
* @type {StatsCompilation[]}
|
|
2192
|
+
*/
|
|
2193
|
+
const statsForPrint =
|
|
2194
|
+
typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
|
|
2195
|
+
? /** @type {NonNullable<StatsCompilation["children"]>} */
|
|
2196
|
+
(/** @type {MultiStats} */ (stats).toJson().children)
|
|
2197
|
+
: [/** @type {Stats} */ (stats).toJson()];
|
|
2198
|
+
|
|
2199
|
+
res.write(`<h1>Assets Report:</h1>`);
|
|
2200
|
+
|
|
2201
|
+
for (const [index, item] of statsForPrint.entries()) {
|
|
2202
|
+
res.write("<div>");
|
|
2203
|
+
|
|
2204
|
+
const name =
|
|
2205
|
+
// eslint-disable-next-line no-nested-ternary
|
|
2206
|
+
typeof item.name !== "undefined"
|
|
2207
|
+
? item.name
|
|
2208
|
+
: /** @type {MultiStats} */ (stats).stats
|
|
2209
|
+
? `unnamed[${index}]`
|
|
2210
|
+
: "unnamed";
|
|
2211
|
+
|
|
2212
|
+
res.write(`<h2>Compilation: ${name}</h2>`);
|
|
2213
|
+
res.write("<ul>");
|
|
2214
|
+
|
|
2215
|
+
const publicPath =
|
|
2216
|
+
item.publicPath === "auto" ? "" : item.publicPath;
|
|
2217
|
+
const assets =
|
|
2218
|
+
/** @type {NonNullable<StatsCompilation["assets"]>} */
|
|
2219
|
+
(item.assets);
|
|
2220
|
+
|
|
2221
|
+
for (const asset of assets) {
|
|
2222
|
+
const assetName = asset.name;
|
|
2223
|
+
const assetURL = `${publicPath}${assetName}`;
|
|
2224
|
+
|
|
2225
|
+
res.write(
|
|
2226
|
+
`<li>
|
|
2227
|
+
<strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
|
|
2228
|
+
</li>`,
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
res.write("</ul>");
|
|
2233
|
+
res.write("</div>");
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
res.end("</body></html>");
|
|
2237
|
+
});
|
|
2238
|
+
},
|
|
1997
2239
|
});
|
|
1998
2240
|
|
|
1999
2241
|
if (this.options.proxy) {
|
|
@@ -2045,8 +2287,7 @@ class Server {
|
|
|
2045
2287
|
* }
|
|
2046
2288
|
* ]
|
|
2047
2289
|
*/
|
|
2048
|
-
|
|
2049
|
-
(this.options.proxy).forEach((proxyConfigOrCallback) => {
|
|
2290
|
+
this.options.proxy.forEach((proxyConfigOrCallback) => {
|
|
2050
2291
|
/**
|
|
2051
2292
|
* @type {RequestHandler}
|
|
2052
2293
|
*/
|
|
@@ -2114,8 +2355,8 @@ class Server {
|
|
|
2114
2355
|
|
|
2115
2356
|
if (typeof bypassUrl === "boolean") {
|
|
2116
2357
|
// skip the proxy
|
|
2117
|
-
|
|
2118
|
-
req.url =
|
|
2358
|
+
res.statusCode = 404;
|
|
2359
|
+
req.url = "";
|
|
2119
2360
|
next();
|
|
2120
2361
|
} else if (typeof bypassUrl === "string") {
|
|
2121
2362
|
// byPass to that url
|
|
@@ -2132,6 +2373,7 @@ class Server {
|
|
|
2132
2373
|
name: "http-proxy-middleware",
|
|
2133
2374
|
middleware: handler,
|
|
2134
2375
|
});
|
|
2376
|
+
|
|
2135
2377
|
// Also forward error requests to the proxy so it can handle them.
|
|
2136
2378
|
middlewares.push({
|
|
2137
2379
|
name: "http-proxy-middleware-error-handler",
|
|
@@ -2149,16 +2391,17 @@ class Server {
|
|
|
2149
2391
|
|
|
2150
2392
|
middlewares.push({
|
|
2151
2393
|
name: "webpack-dev-middleware",
|
|
2152
|
-
middleware:
|
|
2153
|
-
/** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
|
|
2154
|
-
(this.middleware),
|
|
2394
|
+
middleware: /** @type {MiddlewareHandler} */ (this.middleware),
|
|
2155
2395
|
});
|
|
2156
2396
|
}
|
|
2157
2397
|
|
|
2158
|
-
|
|
2398
|
+
const staticOptions =
|
|
2159
2399
|
/** @type {NormalizedStatic[]} */
|
|
2160
|
-
(this.options.static)
|
|
2161
|
-
|
|
2400
|
+
(this.options.static);
|
|
2401
|
+
|
|
2402
|
+
if (staticOptions.length > 0) {
|
|
2403
|
+
for (const staticOption of staticOptions) {
|
|
2404
|
+
for (const publicPath of staticOption.publicPath) {
|
|
2162
2405
|
middlewares.push({
|
|
2163
2406
|
name: "express-static",
|
|
2164
2407
|
path: publicPath,
|
|
@@ -2167,8 +2410,8 @@ class Server {
|
|
|
2167
2410
|
staticOption.staticOptions,
|
|
2168
2411
|
),
|
|
2169
2412
|
});
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2172
2415
|
}
|
|
2173
2416
|
|
|
2174
2417
|
if (this.options.historyApiFallback) {
|
|
@@ -2205,15 +2448,12 @@ class Server {
|
|
|
2205
2448
|
// it is able to handle '/index.html' request after redirect
|
|
2206
2449
|
middlewares.push({
|
|
2207
2450
|
name: "webpack-dev-middleware",
|
|
2208
|
-
middleware:
|
|
2209
|
-
/** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
|
|
2210
|
-
(this.middleware),
|
|
2451
|
+
middleware: /** @type {MiddlewareHandler} */ (this.middleware),
|
|
2211
2452
|
});
|
|
2212
2453
|
|
|
2213
|
-
if (
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
staticOption.publicPath.forEach((publicPath) => {
|
|
2454
|
+
if (staticOptions.length > 0) {
|
|
2455
|
+
for (const staticOption of staticOptions) {
|
|
2456
|
+
for (const publicPath of staticOption.publicPath) {
|
|
2217
2457
|
middlewares.push({
|
|
2218
2458
|
name: "express-static",
|
|
2219
2459
|
path: publicPath,
|
|
@@ -2222,17 +2462,16 @@ class Server {
|
|
|
2222
2462
|
staticOption.staticOptions,
|
|
2223
2463
|
),
|
|
2224
2464
|
});
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2227
2467
|
}
|
|
2228
2468
|
}
|
|
2229
2469
|
|
|
2230
|
-
if (
|
|
2470
|
+
if (staticOptions.length > 0) {
|
|
2231
2471
|
const serveIndex = require("serve-index");
|
|
2232
2472
|
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
staticOption.publicPath.forEach((publicPath) => {
|
|
2473
|
+
for (const staticOption of staticOptions) {
|
|
2474
|
+
for (const publicPath of staticOption.publicPath) {
|
|
2236
2475
|
if (staticOption.serveIndex) {
|
|
2237
2476
|
middlewares.push({
|
|
2238
2477
|
name: "serve-index",
|
|
@@ -2257,15 +2496,14 @@ class Server {
|
|
|
2257
2496
|
},
|
|
2258
2497
|
});
|
|
2259
2498
|
}
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2262
2501
|
}
|
|
2263
2502
|
|
|
2264
2503
|
// Register this middleware always as the last one so that it's only used as a
|
|
2265
2504
|
// fallback when no other middleware responses.
|
|
2266
2505
|
middlewares.push({
|
|
2267
2506
|
name: "options-middleware",
|
|
2268
|
-
path: "*",
|
|
2269
2507
|
/**
|
|
2270
2508
|
* @param {Request} req
|
|
2271
2509
|
* @param {Response} res
|
|
@@ -2287,37 +2525,93 @@ class Server {
|
|
|
2287
2525
|
middlewares = this.options.setupMiddlewares(middlewares, this);
|
|
2288
2526
|
}
|
|
2289
2527
|
|
|
2290
|
-
|
|
2528
|
+
// Lazy init webpack dev middleware
|
|
2529
|
+
const lazyInitDevMiddleware = () => {
|
|
2530
|
+
if (!this.middleware) {
|
|
2531
|
+
const webpackDevMiddleware = require("webpack-dev-middleware");
|
|
2532
|
+
|
|
2533
|
+
// middleware for serving webpack bundle
|
|
2534
|
+
/** @type {import("webpack-dev-middleware").API<Request, Response>} */
|
|
2535
|
+
this.middleware = webpackDevMiddleware(
|
|
2536
|
+
this.compiler,
|
|
2537
|
+
this.options.devMiddleware,
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
return this.middleware;
|
|
2542
|
+
};
|
|
2543
|
+
|
|
2544
|
+
for (const i of middlewares) {
|
|
2545
|
+
if (i.name === "webpack-dev-middleware") {
|
|
2546
|
+
const item = /** @type {MiddlewareObject} */ (i);
|
|
2547
|
+
|
|
2548
|
+
if (typeof item.middleware === "undefined") {
|
|
2549
|
+
item.middleware = lazyInitDevMiddleware();
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
for (const middleware of middlewares) {
|
|
2291
2555
|
if (typeof middleware === "function") {
|
|
2292
|
-
/** @type {
|
|
2293
|
-
(this.app).use(
|
|
2556
|
+
/** @type {A} */
|
|
2557
|
+
(this.app).use(
|
|
2558
|
+
/** @type {NextHandleFunction | HandleFunction} */
|
|
2559
|
+
(middleware),
|
|
2560
|
+
);
|
|
2294
2561
|
} else if (typeof middleware.path !== "undefined") {
|
|
2295
|
-
/** @type {
|
|
2296
|
-
(this.app).use(
|
|
2562
|
+
/** @type {A} */
|
|
2563
|
+
(this.app).use(
|
|
2564
|
+
middleware.path,
|
|
2565
|
+
/** @type {SimpleHandleFunction | NextHandleFunction} */
|
|
2566
|
+
(middleware.middleware),
|
|
2567
|
+
);
|
|
2297
2568
|
} else {
|
|
2298
|
-
/** @type {
|
|
2299
|
-
(this.app).use(
|
|
2569
|
+
/** @type {A} */
|
|
2570
|
+
(this.app).use(
|
|
2571
|
+
/** @type {NextHandleFunction | HandleFunction} */
|
|
2572
|
+
(middleware.middleware),
|
|
2573
|
+
);
|
|
2300
2574
|
}
|
|
2301
|
-
}
|
|
2575
|
+
}
|
|
2302
2576
|
}
|
|
2303
2577
|
|
|
2304
2578
|
/**
|
|
2305
2579
|
* @private
|
|
2306
|
-
* @returns {void}
|
|
2580
|
+
* @returns {Promise<void>}
|
|
2307
2581
|
*/
|
|
2308
|
-
createServer() {
|
|
2309
|
-
const { type, options } =
|
|
2310
|
-
|
|
2311
|
-
|
|
2582
|
+
async createServer() {
|
|
2583
|
+
const { type, options } =
|
|
2584
|
+
/** @type {ServerConfiguration<A, S>} */
|
|
2585
|
+
(this.options.server);
|
|
2586
|
+
|
|
2587
|
+
if (typeof type === "function") {
|
|
2588
|
+
/** @type {S | undefined}*/
|
|
2589
|
+
this.server = await type(
|
|
2590
|
+
/** @type {ServerOptions} */
|
|
2591
|
+
(options),
|
|
2592
|
+
/** @type {A} */
|
|
2593
|
+
(this.app),
|
|
2594
|
+
);
|
|
2595
|
+
} else {
|
|
2596
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
2597
|
+
const serverType = require(/** @type {string} */ (type));
|
|
2312
2598
|
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2599
|
+
/** @type {S | undefined}*/
|
|
2600
|
+
this.server =
|
|
2601
|
+
type === "http2"
|
|
2602
|
+
? serverType.createSecureServer(
|
|
2603
|
+
{ ...options, allowHTTP1: true },
|
|
2604
|
+
this.app,
|
|
2605
|
+
)
|
|
2606
|
+
: serverType.createServer(options, this.app);
|
|
2607
|
+
}
|
|
2319
2608
|
|
|
2320
|
-
|
|
2609
|
+
this.isTlsServer =
|
|
2610
|
+
typeof (
|
|
2611
|
+
/** @type {import("tls").Server} */ (this.server).setSecureContext
|
|
2612
|
+
) !== "undefined";
|
|
2613
|
+
|
|
2614
|
+
/** @type {S} */
|
|
2321
2615
|
(this.server).on(
|
|
2322
2616
|
"connection",
|
|
2323
2617
|
/**
|
|
@@ -2334,7 +2628,7 @@ class Server {
|
|
|
2334
2628
|
},
|
|
2335
2629
|
);
|
|
2336
2630
|
|
|
2337
|
-
/** @type {
|
|
2631
|
+
/** @type {S} */
|
|
2338
2632
|
(this.server).on(
|
|
2339
2633
|
"error",
|
|
2340
2634
|
/**
|
|
@@ -2352,9 +2646,8 @@ class Server {
|
|
|
2352
2646
|
*/
|
|
2353
2647
|
createWebSocketServer() {
|
|
2354
2648
|
/** @type {WebSocketServerImplementation | undefined | null} */
|
|
2355
|
-
this.webSocketServer = new
|
|
2356
|
-
|
|
2357
|
-
);
|
|
2649
|
+
this.webSocketServer = new (this.getServerTransport())(this);
|
|
2650
|
+
|
|
2358
2651
|
/** @type {WebSocketServerImplementation} */
|
|
2359
2652
|
(this.webSocketServer).implementation.on(
|
|
2360
2653
|
"connection",
|
|
@@ -2384,8 +2677,9 @@ class Server {
|
|
|
2384
2677
|
|
|
2385
2678
|
if (
|
|
2386
2679
|
!headers ||
|
|
2387
|
-
!this.
|
|
2388
|
-
!this.
|
|
2680
|
+
!this.isValidHost(headers, "host") ||
|
|
2681
|
+
!this.isValidHost(headers, "origin") ||
|
|
2682
|
+
!this.isSameOrigin(headers)
|
|
2389
2683
|
) {
|
|
2390
2684
|
this.sendMessage([client], "error", "Invalid Host/Origin header");
|
|
2391
2685
|
|
|
@@ -2419,7 +2713,8 @@ class Server {
|
|
|
2419
2713
|
|
|
2420
2714
|
if (
|
|
2421
2715
|
this.options.client &&
|
|
2422
|
-
/** @type {ClientConfiguration} */
|
|
2716
|
+
/** @type {ClientConfiguration} */
|
|
2717
|
+
(this.options.client).reconnect
|
|
2423
2718
|
) {
|
|
2424
2719
|
this.sendMessage(
|
|
2425
2720
|
[client],
|
|
@@ -2434,9 +2729,9 @@ class Server {
|
|
|
2434
2729
|
/** @type {ClientConfiguration} */
|
|
2435
2730
|
(this.options.client).overlay
|
|
2436
2731
|
) {
|
|
2437
|
-
const overlayConfig =
|
|
2438
|
-
|
|
2439
|
-
|
|
2732
|
+
const overlayConfig =
|
|
2733
|
+
/** @type {ClientConfiguration} */
|
|
2734
|
+
(this.options.client).overlay;
|
|
2440
2735
|
|
|
2441
2736
|
this.sendMessage(
|
|
2442
2737
|
[client],
|
|
@@ -2521,22 +2816,19 @@ class Server {
|
|
|
2521
2816
|
*/
|
|
2522
2817
|
runBonjour() {
|
|
2523
2818
|
const { Bonjour } = require("bonjour-service");
|
|
2819
|
+
const type = this.isTlsServer ? "https" : "http";
|
|
2820
|
+
|
|
2524
2821
|
/**
|
|
2525
2822
|
* @private
|
|
2526
2823
|
* @type {Bonjour | undefined}
|
|
2527
2824
|
*/
|
|
2528
2825
|
this.bonjour = new Bonjour();
|
|
2529
2826
|
this.bonjour.publish({
|
|
2530
|
-
// @ts-expect-error
|
|
2531
2827
|
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
|
|
2532
|
-
// @ts-expect-error
|
|
2533
2828
|
port: /** @type {number} */ (this.options.port),
|
|
2534
|
-
|
|
2535
|
-
type:
|
|
2536
|
-
/** @type {ServerConfiguration} */
|
|
2537
|
-
(this.options.server).type === "http" ? "http" : "https",
|
|
2829
|
+
type,
|
|
2538
2830
|
subtypes: ["webpack"],
|
|
2539
|
-
.../** @type {BonjourOptions} */ (this.options.bonjour),
|
|
2831
|
+
.../** @type {Partial<BonjourOptions>} */ (this.options.bonjour),
|
|
2540
2832
|
});
|
|
2541
2833
|
}
|
|
2542
2834
|
|
|
@@ -2616,23 +2908,15 @@ class Server {
|
|
|
2616
2908
|
};
|
|
2617
2909
|
const useColor = getColorsOption(this.getCompilerOptions());
|
|
2618
2910
|
|
|
2911
|
+
const server = /** @type {S} */ (this.server);
|
|
2912
|
+
|
|
2619
2913
|
if (this.options.ipc) {
|
|
2620
|
-
this.logger.info(
|
|
2621
|
-
`Project is running at: "${
|
|
2622
|
-
/** @type {import("http").Server} */
|
|
2623
|
-
(this.server).address()
|
|
2624
|
-
}"`,
|
|
2625
|
-
);
|
|
2914
|
+
this.logger.info(`Project is running at: "${server.address()}"`);
|
|
2626
2915
|
} else {
|
|
2627
|
-
const protocol =
|
|
2628
|
-
/** @type {ServerConfiguration} */
|
|
2629
|
-
(this.options.server).type === "http" ? "http" : "https";
|
|
2916
|
+
const protocol = this.isTlsServer ? "https" : "http";
|
|
2630
2917
|
const { address, port } =
|
|
2631
2918
|
/** @type {import("net").AddressInfo} */
|
|
2632
|
-
(
|
|
2633
|
-
/** @type {import("http").Server} */
|
|
2634
|
-
(this.server).address()
|
|
2635
|
-
);
|
|
2919
|
+
(server.address());
|
|
2636
2920
|
/**
|
|
2637
2921
|
* @param {string} newHostname
|
|
2638
2922
|
* @returns {string}
|
|
@@ -2640,7 +2924,7 @@ class Server {
|
|
|
2640
2924
|
const prettyPrintURL = (newHostname) =>
|
|
2641
2925
|
url.format({ protocol, hostname: newHostname, port, pathname: "/" });
|
|
2642
2926
|
|
|
2643
|
-
let
|
|
2927
|
+
let host;
|
|
2644
2928
|
let localhost;
|
|
2645
2929
|
let loopbackIPv4;
|
|
2646
2930
|
let loopbackIPv6;
|
|
@@ -2660,7 +2944,7 @@ class Server {
|
|
|
2660
2944
|
}
|
|
2661
2945
|
|
|
2662
2946
|
if (!isIP) {
|
|
2663
|
-
|
|
2947
|
+
host = prettyPrintURL(this.options.host);
|
|
2664
2948
|
}
|
|
2665
2949
|
}
|
|
2666
2950
|
}
|
|
@@ -2669,14 +2953,15 @@ class Server {
|
|
|
2669
2953
|
|
|
2670
2954
|
if (parsedIP.range() === "unspecified") {
|
|
2671
2955
|
localhost = prettyPrintURL("localhost");
|
|
2956
|
+
loopbackIPv6 = prettyPrintURL("::1");
|
|
2672
2957
|
|
|
2673
|
-
const networkIPv4 =
|
|
2958
|
+
const networkIPv4 = Server.findIp("v4", false);
|
|
2674
2959
|
|
|
2675
2960
|
if (networkIPv4) {
|
|
2676
2961
|
networkUrlIPv4 = prettyPrintURL(networkIPv4);
|
|
2677
2962
|
}
|
|
2678
2963
|
|
|
2679
|
-
const networkIPv6 =
|
|
2964
|
+
const networkIPv6 = Server.findIp("v6", false);
|
|
2680
2965
|
|
|
2681
2966
|
if (networkIPv6) {
|
|
2682
2967
|
networkUrlIPv6 = prettyPrintURL(networkIPv6);
|
|
@@ -2705,8 +2990,8 @@ class Server {
|
|
|
2705
2990
|
|
|
2706
2991
|
this.logger.info("Project is running at:");
|
|
2707
2992
|
|
|
2708
|
-
if (
|
|
2709
|
-
this.logger.info(`Server: ${colors.info(useColor,
|
|
2993
|
+
if (host) {
|
|
2994
|
+
this.logger.info(`Server: ${colors.info(useColor, host)}`);
|
|
2710
2995
|
}
|
|
2711
2996
|
|
|
2712
2997
|
if (localhost || loopbackIPv4 || loopbackIPv6) {
|
|
@@ -2778,11 +3063,7 @@ class Server {
|
|
|
2778
3063
|
if (this.options.bonjour) {
|
|
2779
3064
|
const bonjourProtocol =
|
|
2780
3065
|
/** @type {BonjourOptions} */
|
|
2781
|
-
(this.options.bonjour).type ||
|
|
2782
|
-
/** @type {ServerConfiguration} */
|
|
2783
|
-
(this.options.server).type === "http"
|
|
2784
|
-
? "http"
|
|
2785
|
-
: "https";
|
|
3066
|
+
(this.options.bonjour).type || this.isTlsServer ? "https" : "http";
|
|
2786
3067
|
|
|
2787
3068
|
this.logger.info(
|
|
2788
3069
|
`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`,
|
|
@@ -2804,8 +3085,8 @@ class Server {
|
|
|
2804
3085
|
headers = headers(
|
|
2805
3086
|
req,
|
|
2806
3087
|
res,
|
|
2807
|
-
|
|
2808
|
-
|
|
3088
|
+
// eslint-disable-next-line no-undefined
|
|
3089
|
+
this.middleware ? this.middleware.context : undefined,
|
|
2809
3090
|
);
|
|
2810
3091
|
}
|
|
2811
3092
|
|
|
@@ -2824,14 +3105,9 @@ class Server {
|
|
|
2824
3105
|
headers = allHeaders;
|
|
2825
3106
|
}
|
|
2826
3107
|
|
|
2827
|
-
headers
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
*/
|
|
2831
|
-
(header) => {
|
|
2832
|
-
res.setHeader(header.key, header.value);
|
|
2833
|
-
},
|
|
2834
|
-
);
|
|
3108
|
+
for (const { key, value } of headers) {
|
|
3109
|
+
res.setHeader(key, value);
|
|
3110
|
+
}
|
|
2835
3111
|
}
|
|
2836
3112
|
|
|
2837
3113
|
next();
|
|
@@ -2839,100 +3115,176 @@ class Server {
|
|
|
2839
3115
|
|
|
2840
3116
|
/**
|
|
2841
3117
|
* @private
|
|
2842
|
-
* @param {
|
|
2843
|
-
* @param {string} headerToCheck
|
|
3118
|
+
* @param {string} value
|
|
2844
3119
|
* @returns {boolean}
|
|
2845
3120
|
*/
|
|
2846
|
-
|
|
3121
|
+
isHostAllowed(value) {
|
|
3122
|
+
const { allowedHosts } = this.options;
|
|
3123
|
+
|
|
2847
3124
|
// allow user to opt out of this security check, at their own risk
|
|
2848
3125
|
// by explicitly enabling allowedHosts
|
|
3126
|
+
if (allowedHosts === "all") {
|
|
3127
|
+
return true;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// always allow localhost host, for convenience
|
|
3131
|
+
// allow if value is in allowedHosts
|
|
3132
|
+
if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
|
|
3133
|
+
for (const allowedHost of allowedHosts) {
|
|
3134
|
+
if (allowedHost === value) {
|
|
3135
|
+
return true;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// support "." as a subdomain wildcard
|
|
3139
|
+
// e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
|
|
3140
|
+
if (allowedHost.startsWith(".")) {
|
|
3141
|
+
// "example.com" (value === allowedHost.substring(1))
|
|
3142
|
+
// "*.example.com" (value.endsWith(allowedHost))
|
|
3143
|
+
if (
|
|
3144
|
+
value === allowedHost.substring(1) ||
|
|
3145
|
+
/** @type {string} */
|
|
3146
|
+
(value).endsWith(allowedHost)
|
|
3147
|
+
) {
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// Also allow if `client.webSocketURL.hostname` provided
|
|
3155
|
+
if (
|
|
3156
|
+
this.options.client &&
|
|
3157
|
+
typeof (
|
|
3158
|
+
/** @type {ClientConfiguration} */
|
|
3159
|
+
(this.options.client).webSocketURL
|
|
3160
|
+
) !== "undefined"
|
|
3161
|
+
) {
|
|
3162
|
+
return (
|
|
3163
|
+
/** @type {WebSocketURL} */
|
|
3164
|
+
(/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
|
|
3165
|
+
.hostname === value
|
|
3166
|
+
);
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
/**
|
|
3173
|
+
* @private
|
|
3174
|
+
* @param {{ [key: string]: string | undefined }} headers
|
|
3175
|
+
* @param {string} headerToCheck
|
|
3176
|
+
* @param {boolean} validateHost
|
|
3177
|
+
* @returns {boolean}
|
|
3178
|
+
*/
|
|
3179
|
+
isValidHost(headers, headerToCheck, validateHost = true) {
|
|
2849
3180
|
if (this.options.allowedHosts === "all") {
|
|
2850
3181
|
return true;
|
|
2851
3182
|
}
|
|
2852
3183
|
|
|
2853
3184
|
// get the Host header and extract hostname
|
|
2854
3185
|
// we don't care about port not matching
|
|
2855
|
-
const
|
|
3186
|
+
const header = headers[headerToCheck];
|
|
2856
3187
|
|
|
2857
|
-
if (!
|
|
3188
|
+
if (!header) {
|
|
2858
3189
|
return false;
|
|
2859
3190
|
}
|
|
2860
3191
|
|
|
2861
|
-
if (
|
|
3192
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(header)) {
|
|
2862
3193
|
return true;
|
|
2863
3194
|
}
|
|
2864
3195
|
|
|
2865
3196
|
// use the node url-parser to retrieve the hostname from the host-header.
|
|
2866
3197
|
const hostname = url.parse(
|
|
2867
|
-
// if
|
|
2868
|
-
/^(.+:)?\/\//.test(
|
|
3198
|
+
// if header doesn't have scheme, add // for parsing.
|
|
3199
|
+
/^(.+:)?\/\//.test(header) ? header : `//${header}`,
|
|
2869
3200
|
false,
|
|
2870
3201
|
true,
|
|
2871
3202
|
).hostname;
|
|
2872
3203
|
|
|
3204
|
+
if (hostname === null) {
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
if (this.isHostAllowed(hostname)) {
|
|
3209
|
+
return true;
|
|
3210
|
+
}
|
|
3211
|
+
|
|
2873
3212
|
// always allow requests with explicit IPv4 or IPv6-address.
|
|
2874
3213
|
// A note on IPv6 addresses:
|
|
2875
|
-
//
|
|
3214
|
+
// header will always contain the brackets denoting
|
|
2876
3215
|
// an IPv6-address in URLs,
|
|
2877
3216
|
// these are removed from the hostname in url.parse(),
|
|
2878
3217
|
// so we have the pure IPv6-address in hostname.
|
|
2879
3218
|
// For convenience, always allow localhost (hostname === 'localhost')
|
|
2880
3219
|
// and its subdomains (hostname.endsWith(".localhost")).
|
|
2881
3220
|
// allow hostname of listening address (hostname === this.options.host)
|
|
2882
|
-
const isValidHostname =
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3221
|
+
const isValidHostname = validateHost
|
|
3222
|
+
? ipaddr.IPv4.isValid(hostname) ||
|
|
3223
|
+
ipaddr.IPv6.isValid(hostname) ||
|
|
3224
|
+
hostname === "localhost" ||
|
|
3225
|
+
hostname.endsWith(".localhost") ||
|
|
3226
|
+
hostname === this.options.host
|
|
3227
|
+
: false;
|
|
3228
|
+
|
|
3229
|
+
return isValidHostname;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
/**
|
|
3233
|
+
* @private
|
|
3234
|
+
* @param {{ [key: string]: string | undefined }} headers
|
|
3235
|
+
* @returns {boolean}
|
|
3236
|
+
*/
|
|
3237
|
+
isSameOrigin(headers) {
|
|
3238
|
+
if (this.options.allowedHosts === "all") {
|
|
2890
3239
|
return true;
|
|
2891
3240
|
}
|
|
2892
3241
|
|
|
2893
|
-
const
|
|
3242
|
+
const originHeader = headers.origin;
|
|
2894
3243
|
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
for (let hostIdx = 0; hostIdx < allowedHosts.length; hostIdx++) {
|
|
2899
|
-
const allowedHost = allowedHosts[hostIdx];
|
|
3244
|
+
if (!originHeader) {
|
|
3245
|
+
return this.options.allowedHosts === "all";
|
|
3246
|
+
}
|
|
2900
3247
|
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
3248
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(originHeader)) {
|
|
3249
|
+
return true;
|
|
3250
|
+
}
|
|
2904
3251
|
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
// "*.example.com" (hostname.endsWith(allowedHost))
|
|
2910
|
-
if (
|
|
2911
|
-
hostname === allowedHost.substring(1) ||
|
|
2912
|
-
/** @type {string} */ (hostname).endsWith(allowedHost)
|
|
2913
|
-
) {
|
|
2914
|
-
return true;
|
|
2915
|
-
}
|
|
2916
|
-
}
|
|
2917
|
-
}
|
|
3252
|
+
const origin = url.parse(originHeader, false, true).hostname;
|
|
3253
|
+
|
|
3254
|
+
if (origin === null) {
|
|
3255
|
+
return false;
|
|
2918
3256
|
}
|
|
2919
3257
|
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
this.options.client &&
|
|
2923
|
-
typeof (
|
|
2924
|
-
/** @type {ClientConfiguration} */ (this.options.client).webSocketURL
|
|
2925
|
-
) !== "undefined"
|
|
2926
|
-
) {
|
|
2927
|
-
return (
|
|
2928
|
-
/** @type {WebSocketURL} */
|
|
2929
|
-
(/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
|
|
2930
|
-
.hostname === hostname
|
|
2931
|
-
);
|
|
3258
|
+
if (this.isHostAllowed(origin)) {
|
|
3259
|
+
return true;
|
|
2932
3260
|
}
|
|
2933
3261
|
|
|
2934
|
-
|
|
2935
|
-
|
|
3262
|
+
const hostHeader = headers.host;
|
|
3263
|
+
|
|
3264
|
+
if (!hostHeader) {
|
|
3265
|
+
return this.options.allowedHosts === "all";
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(hostHeader)) {
|
|
3269
|
+
return true;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
const host = url.parse(
|
|
3273
|
+
// if hostHeader doesn't have scheme, add // for parsing.
|
|
3274
|
+
/^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`,
|
|
3275
|
+
false,
|
|
3276
|
+
true,
|
|
3277
|
+
).hostname;
|
|
3278
|
+
|
|
3279
|
+
if (host === null) {
|
|
3280
|
+
return false;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
if (this.isHostAllowed(host)) {
|
|
3284
|
+
return true;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
return origin === host;
|
|
2936
3288
|
}
|
|
2937
3289
|
|
|
2938
3290
|
/**
|
|
@@ -3104,7 +3456,7 @@ class Server {
|
|
|
3104
3456
|
|
|
3105
3457
|
await /** @type {Promise<void>} */ (
|
|
3106
3458
|
new Promise((resolve) => {
|
|
3107
|
-
/** @type {
|
|
3459
|
+
/** @type {S} */
|
|
3108
3460
|
(this.server).listen(listenOptions, () => {
|
|
3109
3461
|
resolve();
|
|
3110
3462
|
});
|
|
@@ -3190,10 +3542,10 @@ class Server {
|
|
|
3190
3542
|
if (this.server) {
|
|
3191
3543
|
await /** @type {Promise<void>} */ (
|
|
3192
3544
|
new Promise((resolve) => {
|
|
3193
|
-
/** @type {
|
|
3545
|
+
/** @type {S} */
|
|
3194
3546
|
(this.server).close(() => {
|
|
3195
|
-
|
|
3196
|
-
|
|
3547
|
+
// eslint-disable-next-line no-undefined
|
|
3548
|
+
this.server = undefined;
|
|
3197
3549
|
resolve();
|
|
3198
3550
|
});
|
|
3199
3551
|
|
|
@@ -3212,7 +3564,6 @@ class Server {
|
|
|
3212
3564
|
(this.middleware).close((error) => {
|
|
3213
3565
|
if (error) {
|
|
3214
3566
|
reject(error);
|
|
3215
|
-
|
|
3216
3567
|
return;
|
|
3217
3568
|
}
|
|
3218
3569
|
|
|
@@ -3221,7 +3572,8 @@ class Server {
|
|
|
3221
3572
|
})
|
|
3222
3573
|
);
|
|
3223
3574
|
|
|
3224
|
-
|
|
3575
|
+
// eslint-disable-next-line no-undefined
|
|
3576
|
+
this.middleware = undefined;
|
|
3225
3577
|
}
|
|
3226
3578
|
}
|
|
3227
3579
|
|