webpack-dev-server 4.3.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -18
- package/bin/cli-flags.js +317 -35
- package/client/index.js +18 -4
- package/client/modules/logger/index.js +43 -14
- package/client/modules/sockjs-client/index.js +20 -17
- package/client/socket.js +9 -5
- package/lib/Server.js +337 -161
- package/lib/options.json +255 -16
- package/package.json +10 -10
package/lib/Server.js
CHANGED
|
@@ -6,7 +6,7 @@ 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
|
|
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");
|
|
@@ -32,24 +32,24 @@ class Server {
|
|
|
32
32
|
|
|
33
33
|
this.options = options;
|
|
34
34
|
this.staticWatchers = [];
|
|
35
|
+
this.listeners = [];
|
|
35
36
|
// Keep track of websocket proxies for external websocket upgrade.
|
|
36
37
|
this.webSocketProxies = [];
|
|
37
38
|
this.sockets = [];
|
|
38
39
|
this.compiler = compiler;
|
|
40
|
+
this.currentHash = null;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
static get DEFAULT_STATS() {
|
|
42
44
|
return {
|
|
43
45
|
all: false,
|
|
44
46
|
hash: true,
|
|
45
|
-
assets: true,
|
|
46
47
|
warnings: true,
|
|
47
48
|
errors: true,
|
|
48
49
|
errorDetails: false,
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
// eslint-disable-next-line class-methods-use-this
|
|
53
53
|
static isAbsoluteURL(URL) {
|
|
54
54
|
// Don't match Windows paths `c:\`
|
|
55
55
|
if (/^[a-zA-Z]:\\/.test(URL)) {
|
|
@@ -61,13 +61,54 @@ class Server {
|
|
|
61
61
|
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
static findIp(gateway) {
|
|
65
|
+
const gatewayIp = ipaddr.parse(gateway);
|
|
66
|
+
|
|
67
|
+
// Look for the matching interface in all local interfaces.
|
|
68
|
+
for (const addresses of Object.values(os.networkInterfaces())) {
|
|
69
|
+
for (const { cidr } of addresses) {
|
|
70
|
+
const net = ipaddr.parseCIDR(cidr);
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
net[0] &&
|
|
74
|
+
net[0].kind() === gatewayIp.kind() &&
|
|
75
|
+
gatewayIp.match(net)
|
|
76
|
+
) {
|
|
77
|
+
return net[0].toString();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async internalIP(family) {
|
|
84
|
+
try {
|
|
85
|
+
const { gateway } = await defaultGateway[family]();
|
|
86
|
+
return Server.findIp(gateway);
|
|
87
|
+
} catch {
|
|
88
|
+
// ignore
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static internalIPSync(family) {
|
|
93
|
+
try {
|
|
94
|
+
const { gateway } = defaultGateway[family].sync();
|
|
95
|
+
return Server.findIp(gateway);
|
|
96
|
+
} catch {
|
|
97
|
+
// ignore
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
64
101
|
static async getHostname(hostname) {
|
|
65
102
|
if (hostname === "local-ip") {
|
|
66
|
-
return (
|
|
103
|
+
return (
|
|
104
|
+
(await Server.internalIP("v4")) ||
|
|
105
|
+
(await Server.internalIP("v6")) ||
|
|
106
|
+
"0.0.0.0"
|
|
107
|
+
);
|
|
67
108
|
} else if (hostname === "local-ipv4") {
|
|
68
|
-
return (await
|
|
109
|
+
return (await Server.internalIP("v4")) || "0.0.0.0";
|
|
69
110
|
} else if (hostname === "local-ipv6") {
|
|
70
|
-
return (await
|
|
111
|
+
return (await Server.internalIP("v6")) || "::";
|
|
71
112
|
}
|
|
72
113
|
|
|
73
114
|
return hostname;
|
|
@@ -155,7 +196,7 @@ class Server {
|
|
|
155
196
|
if (typeof this.options.client.webSocketURL.protocol !== "undefined") {
|
|
156
197
|
protocol = this.options.client.webSocketURL.protocol;
|
|
157
198
|
} else {
|
|
158
|
-
protocol = this.options.
|
|
199
|
+
protocol = this.options.server.type === "http" ? "ws:" : "wss:";
|
|
159
200
|
}
|
|
160
201
|
|
|
161
202
|
searchParams.set("protocol", protocol);
|
|
@@ -258,6 +299,10 @@ class Server {
|
|
|
258
299
|
searchParams.set("logging", this.options.client.logging);
|
|
259
300
|
}
|
|
260
301
|
|
|
302
|
+
if (typeof this.options.client.reconnect !== "undefined") {
|
|
303
|
+
searchParams.set("reconnect", this.options.client.reconnect);
|
|
304
|
+
}
|
|
305
|
+
|
|
261
306
|
webSocketURL = searchParams.toString();
|
|
262
307
|
}
|
|
263
308
|
|
|
@@ -390,7 +435,6 @@ class Server {
|
|
|
390
435
|
return this.compiler.options;
|
|
391
436
|
}
|
|
392
437
|
|
|
393
|
-
// eslint-disable-next-line class-methods-use-this
|
|
394
438
|
async normalizeOptions() {
|
|
395
439
|
const { options } = this;
|
|
396
440
|
|
|
@@ -400,14 +444,125 @@ class Server {
|
|
|
400
444
|
|
|
401
445
|
const compilerOptions = this.getCompilerOptions();
|
|
402
446
|
// TODO remove `{}` after drop webpack v4 support
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
447
|
+
const compilerWatchOptions = compilerOptions.watchOptions || {};
|
|
448
|
+
const getWatchOptions = (watchOptions = {}) => {
|
|
449
|
+
const getPolling = () => {
|
|
450
|
+
if (typeof watchOptions.usePolling !== "undefined") {
|
|
451
|
+
return watchOptions.usePolling;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (typeof watchOptions.poll !== "undefined") {
|
|
455
|
+
return Boolean(watchOptions.poll);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (typeof compilerWatchOptions.poll !== "undefined") {
|
|
459
|
+
return Boolean(compilerWatchOptions.poll);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return false;
|
|
463
|
+
};
|
|
464
|
+
const getInterval = () => {
|
|
465
|
+
if (typeof watchOptions.interval !== "undefined") {
|
|
466
|
+
return watchOptions.interval;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (typeof watchOptions.poll === "number") {
|
|
470
|
+
return watchOptions.poll;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (typeof compilerWatchOptions.poll === "number") {
|
|
474
|
+
return compilerWatchOptions.poll;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const usePolling = getPolling();
|
|
479
|
+
const interval = getInterval();
|
|
480
|
+
const { poll, ...rest } = watchOptions;
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
ignoreInitial: true,
|
|
484
|
+
persistent: true,
|
|
485
|
+
followSymlinks: false,
|
|
486
|
+
atomic: false,
|
|
487
|
+
alwaysStat: true,
|
|
488
|
+
ignorePermissionErrors: true,
|
|
489
|
+
// Respect options from compiler watchOptions
|
|
490
|
+
usePolling,
|
|
491
|
+
interval,
|
|
492
|
+
ignored: watchOptions.ignored,
|
|
493
|
+
// 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
|
|
494
|
+
...rest,
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
const getStaticItem = (optionsForStatic) => {
|
|
498
|
+
const getDefaultStaticOptions = () => {
|
|
499
|
+
return {
|
|
500
|
+
directory: path.join(process.cwd(), "public"),
|
|
501
|
+
staticOptions: {},
|
|
502
|
+
publicPath: ["/"],
|
|
503
|
+
serveIndex: { icons: true },
|
|
504
|
+
watch: getWatchOptions(),
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
let item;
|
|
509
|
+
|
|
510
|
+
if (typeof optionsForStatic === "undefined") {
|
|
511
|
+
item = getDefaultStaticOptions();
|
|
512
|
+
} else if (typeof optionsForStatic === "string") {
|
|
513
|
+
item = {
|
|
514
|
+
...getDefaultStaticOptions(),
|
|
515
|
+
directory: optionsForStatic,
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
const def = getDefaultStaticOptions();
|
|
519
|
+
|
|
520
|
+
item = {
|
|
521
|
+
directory:
|
|
522
|
+
typeof optionsForStatic.directory !== "undefined"
|
|
523
|
+
? optionsForStatic.directory
|
|
524
|
+
: def.directory,
|
|
525
|
+
// TODO: do merge in the next major release
|
|
526
|
+
staticOptions:
|
|
527
|
+
typeof optionsForStatic.staticOptions !== "undefined"
|
|
528
|
+
? optionsForStatic.staticOptions
|
|
529
|
+
: def.staticOptions,
|
|
530
|
+
publicPath:
|
|
531
|
+
typeof optionsForStatic.publicPath !== "undefined"
|
|
532
|
+
? optionsForStatic.publicPath
|
|
533
|
+
: def.publicPath,
|
|
534
|
+
// TODO: do merge in the next major release
|
|
535
|
+
serveIndex:
|
|
536
|
+
// eslint-disable-next-line no-nested-ternary
|
|
537
|
+
typeof optionsForStatic.serveIndex !== "undefined"
|
|
538
|
+
? typeof optionsForStatic.serveIndex === "boolean" &&
|
|
539
|
+
optionsForStatic.serveIndex
|
|
540
|
+
? def.serveIndex
|
|
541
|
+
: optionsForStatic.serveIndex
|
|
542
|
+
: def.serveIndex,
|
|
543
|
+
watch:
|
|
544
|
+
// eslint-disable-next-line no-nested-ternary
|
|
545
|
+
typeof optionsForStatic.watch !== "undefined"
|
|
546
|
+
? // eslint-disable-next-line no-nested-ternary
|
|
547
|
+
typeof optionsForStatic.watch === "boolean"
|
|
548
|
+
? optionsForStatic.watch
|
|
549
|
+
? def.watch
|
|
550
|
+
: false
|
|
551
|
+
: getWatchOptions(optionsForStatic.watch)
|
|
552
|
+
: def.watch,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (Server.isAbsoluteURL(item.directory)) {
|
|
557
|
+
throw new Error("Using a URL as static.directory is not supported");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ensure that publicPath is an array
|
|
561
|
+
if (typeof item.publicPath === "string") {
|
|
562
|
+
item.publicPath = [item.publicPath];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return item;
|
|
411
566
|
};
|
|
412
567
|
|
|
413
568
|
if (typeof options.allowedHosts === "undefined") {
|
|
@@ -474,6 +629,14 @@ class Server {
|
|
|
474
629
|
};
|
|
475
630
|
}
|
|
476
631
|
|
|
632
|
+
if (typeof options.client.reconnect === "undefined") {
|
|
633
|
+
options.client.reconnect = 10;
|
|
634
|
+
} else if (options.client.reconnect === true) {
|
|
635
|
+
options.client.reconnect = Infinity;
|
|
636
|
+
} else if (options.client.reconnect === false) {
|
|
637
|
+
options.client.reconnect = 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
477
640
|
// Respect infrastructureLogging.level
|
|
478
641
|
if (typeof options.client.logging === "undefined") {
|
|
479
642
|
options.client.logging = compilerOptions.infrastructureLogging
|
|
@@ -508,23 +671,62 @@ class Server {
|
|
|
508
671
|
? options.hot
|
|
509
672
|
: true;
|
|
510
673
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
674
|
+
const isHTTPs = Boolean(options.https);
|
|
675
|
+
const isSPDY = Boolean(options.http2);
|
|
676
|
+
|
|
677
|
+
if (isHTTPs || isSPDY) {
|
|
678
|
+
// TODO: remove in the next major release
|
|
679
|
+
util.deprecate(
|
|
680
|
+
() => {},
|
|
681
|
+
`'${
|
|
682
|
+
isHTTPs ? "https" : "http2"
|
|
683
|
+
}' option is deprecated. Please use the 'server' option.`,
|
|
684
|
+
`DEP_WEBPACK_DEV_SERVER_${isHTTPs ? "HTTPS" : "HTTP2"}`
|
|
685
|
+
)();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
options.server = {
|
|
689
|
+
type:
|
|
690
|
+
// eslint-disable-next-line no-nested-ternary
|
|
691
|
+
typeof options.server === "string"
|
|
692
|
+
? options.server
|
|
693
|
+
: // eslint-disable-next-line no-nested-ternary
|
|
694
|
+
typeof (options.server || {}).type === "string"
|
|
695
|
+
? options.server.type
|
|
696
|
+
: // eslint-disable-next-line no-nested-ternary
|
|
697
|
+
isSPDY
|
|
698
|
+
? "spdy"
|
|
699
|
+
: isHTTPs
|
|
700
|
+
? "https"
|
|
701
|
+
: "http",
|
|
702
|
+
options: {
|
|
703
|
+
...options.https,
|
|
704
|
+
...(options.server || {}).options,
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
if (
|
|
709
|
+
options.server.type === "spdy" &&
|
|
710
|
+
typeof options.server.options.spdy === "undefined"
|
|
711
|
+
) {
|
|
712
|
+
options.server.options.spdy = {
|
|
713
|
+
protocols: ["h2", "http/1.1"],
|
|
515
714
|
};
|
|
516
715
|
}
|
|
517
716
|
|
|
518
|
-
|
|
519
|
-
|
|
717
|
+
if (options.server.type === "https" || options.server.type === "spdy") {
|
|
718
|
+
if (typeof options.server.options.requestCert === "undefined") {
|
|
719
|
+
options.server.options.requestCert = false;
|
|
720
|
+
}
|
|
721
|
+
|
|
520
722
|
// TODO remove the `cacert` option in favor `ca` in the next major release
|
|
521
723
|
for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) {
|
|
522
|
-
if (typeof options.
|
|
724
|
+
if (typeof options.server.options[property] === "undefined") {
|
|
523
725
|
// eslint-disable-next-line no-continue
|
|
524
726
|
continue;
|
|
525
727
|
}
|
|
526
728
|
|
|
527
|
-
const value = options.
|
|
729
|
+
const value = options.server.options[property];
|
|
528
730
|
const readFile = (item) => {
|
|
529
731
|
if (
|
|
530
732
|
Buffer.isBuffer(item) ||
|
|
@@ -547,14 +749,14 @@ class Server {
|
|
|
547
749
|
}
|
|
548
750
|
};
|
|
549
751
|
|
|
550
|
-
options.
|
|
752
|
+
options.server.options[property] = Array.isArray(value)
|
|
551
753
|
? value.map((item) => readFile(item))
|
|
552
754
|
: readFile(value);
|
|
553
755
|
}
|
|
554
756
|
|
|
555
757
|
let fakeCert;
|
|
556
758
|
|
|
557
|
-
if (!options.
|
|
759
|
+
if (!options.server.options.key || !options.server.options.cert) {
|
|
558
760
|
const certificateDir = Server.findCacheDir();
|
|
559
761
|
const certificatePath = path.join(certificateDir, "server.pem");
|
|
560
762
|
let certificateExists;
|
|
@@ -577,7 +779,7 @@ class Server {
|
|
|
577
779
|
const del = require("del");
|
|
578
780
|
|
|
579
781
|
this.logger.info(
|
|
580
|
-
"SSL
|
|
782
|
+
"SSL certificate is more than 30 days old. Removing..."
|
|
581
783
|
);
|
|
582
784
|
|
|
583
785
|
await del([certificatePath], { force: true });
|
|
@@ -587,7 +789,7 @@ class Server {
|
|
|
587
789
|
}
|
|
588
790
|
|
|
589
791
|
if (!certificateExists) {
|
|
590
|
-
this.logger.info("Generating SSL
|
|
792
|
+
this.logger.info("Generating SSL certificate...");
|
|
591
793
|
|
|
592
794
|
const selfsigned = require("selfsigned");
|
|
593
795
|
const attributes = [{ name: "commonName", value: "localhost" }];
|
|
@@ -669,20 +871,20 @@ class Server {
|
|
|
669
871
|
this.logger.info(`SSL certificate: ${certificatePath}`);
|
|
670
872
|
}
|
|
671
873
|
|
|
672
|
-
if (options.
|
|
673
|
-
if (options.
|
|
874
|
+
if (options.server.options.cacert) {
|
|
875
|
+
if (options.server.options.ca) {
|
|
674
876
|
this.logger.warn(
|
|
675
|
-
"Do not specify '
|
|
877
|
+
"Do not specify 'ca' and 'cacert' options together, the 'ca' option will be used."
|
|
676
878
|
);
|
|
677
879
|
} else {
|
|
678
|
-
options.
|
|
880
|
+
options.server.options.ca = options.server.options.cacert;
|
|
679
881
|
}
|
|
680
882
|
|
|
681
|
-
delete options.
|
|
883
|
+
delete options.server.options.cacert;
|
|
682
884
|
}
|
|
683
885
|
|
|
684
|
-
options.
|
|
685
|
-
options.
|
|
886
|
+
options.server.options.key = options.server.options.key || fakeCert;
|
|
887
|
+
options.server.options.cert = options.server.options.cert || fakeCert;
|
|
686
888
|
}
|
|
687
889
|
|
|
688
890
|
if (typeof options.ipc === "boolean") {
|
|
@@ -830,50 +1032,27 @@ class Server {
|
|
|
830
1032
|
}
|
|
831
1033
|
|
|
832
1034
|
if (typeof options.static === "undefined") {
|
|
833
|
-
options.static = [
|
|
1035
|
+
options.static = [getStaticItem()];
|
|
834
1036
|
} else if (typeof options.static === "boolean") {
|
|
835
|
-
options.static = options.static ? [
|
|
1037
|
+
options.static = options.static ? [getStaticItem()] : false;
|
|
836
1038
|
} else if (typeof options.static === "string") {
|
|
837
|
-
options.static = [
|
|
838
|
-
{ ...defaultOptionsForStatic, directory: options.static },
|
|
839
|
-
];
|
|
1039
|
+
options.static = [getStaticItem(options.static)];
|
|
840
1040
|
} else if (Array.isArray(options.static)) {
|
|
841
1041
|
options.static = options.static.map((item) => {
|
|
842
1042
|
if (typeof item === "string") {
|
|
843
|
-
return
|
|
1043
|
+
return getStaticItem(item);
|
|
844
1044
|
}
|
|
845
1045
|
|
|
846
|
-
return
|
|
1046
|
+
return getStaticItem(item);
|
|
847
1047
|
});
|
|
848
1048
|
} else {
|
|
849
|
-
options.static = [
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
if (options.static) {
|
|
853
|
-
options.static.forEach((staticOption) => {
|
|
854
|
-
if (Server.isAbsoluteURL(staticOption.directory)) {
|
|
855
|
-
throw new Error("Using a URL as static.directory is not supported");
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// ensure that publicPath is an array
|
|
859
|
-
if (typeof staticOption.publicPath === "string") {
|
|
860
|
-
staticOption.publicPath = [staticOption.publicPath];
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// ensure that watch is an object if true
|
|
864
|
-
if (staticOption.watch === true) {
|
|
865
|
-
staticOption.watch = defaultOptionsForStatic.watch;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// ensure that serveIndex is an object if true
|
|
869
|
-
if (staticOption.serveIndex === true) {
|
|
870
|
-
staticOption.serveIndex = defaultOptionsForStatic.serveIndex;
|
|
871
|
-
}
|
|
872
|
-
});
|
|
1049
|
+
options.static = [getStaticItem(options.static)];
|
|
873
1050
|
}
|
|
874
1051
|
|
|
875
1052
|
if (typeof options.watchFiles === "string") {
|
|
876
|
-
options.watchFiles = [
|
|
1053
|
+
options.watchFiles = [
|
|
1054
|
+
{ paths: options.watchFiles, options: getWatchOptions() },
|
|
1055
|
+
];
|
|
877
1056
|
} else if (
|
|
878
1057
|
typeof options.watchFiles === "object" &&
|
|
879
1058
|
options.watchFiles !== null &&
|
|
@@ -882,16 +1061,19 @@ class Server {
|
|
|
882
1061
|
options.watchFiles = [
|
|
883
1062
|
{
|
|
884
1063
|
paths: options.watchFiles.paths,
|
|
885
|
-
options: options.watchFiles.options || {},
|
|
1064
|
+
options: getWatchOptions(options.watchFiles.options || {}),
|
|
886
1065
|
},
|
|
887
1066
|
];
|
|
888
1067
|
} else if (Array.isArray(options.watchFiles)) {
|
|
889
1068
|
options.watchFiles = options.watchFiles.map((item) => {
|
|
890
1069
|
if (typeof item === "string") {
|
|
891
|
-
return { paths: item, options:
|
|
1070
|
+
return { paths: item, options: getWatchOptions() };
|
|
892
1071
|
}
|
|
893
1072
|
|
|
894
|
-
return {
|
|
1073
|
+
return {
|
|
1074
|
+
paths: item.paths,
|
|
1075
|
+
options: getWatchOptions(item.options || {}),
|
|
1076
|
+
};
|
|
895
1077
|
});
|
|
896
1078
|
} else {
|
|
897
1079
|
options.watchFiles = [];
|
|
@@ -1066,7 +1248,6 @@ class Server {
|
|
|
1066
1248
|
if (this.options.webSocketServer) {
|
|
1067
1249
|
const compilers = this.compiler.compilers || [this.compiler];
|
|
1068
1250
|
|
|
1069
|
-
// eslint-disable-next-line no-shadow
|
|
1070
1251
|
compilers.forEach((compiler) => {
|
|
1071
1252
|
this.addAdditionalEntries(compiler);
|
|
1072
1253
|
|
|
@@ -1117,15 +1298,11 @@ class Server {
|
|
|
1117
1298
|
|
|
1118
1299
|
let needForceShutdown = false;
|
|
1119
1300
|
|
|
1120
|
-
const exitProcess = () => {
|
|
1121
|
-
// eslint-disable-next-line no-process-exit
|
|
1122
|
-
process.exit();
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
1301
|
signals.forEach((signal) => {
|
|
1126
|
-
|
|
1302
|
+
const listener = () => {
|
|
1127
1303
|
if (needForceShutdown) {
|
|
1128
|
-
|
|
1304
|
+
// eslint-disable-next-line no-process-exit
|
|
1305
|
+
process.exit();
|
|
1129
1306
|
}
|
|
1130
1307
|
|
|
1131
1308
|
this.logger.info(
|
|
@@ -1136,19 +1313,26 @@ class Server {
|
|
|
1136
1313
|
|
|
1137
1314
|
this.stopCallback(() => {
|
|
1138
1315
|
if (typeof this.compiler.close === "function") {
|
|
1139
|
-
this.compiler.close(
|
|
1316
|
+
this.compiler.close(() => {
|
|
1317
|
+
// eslint-disable-next-line no-process-exit
|
|
1318
|
+
process.exit();
|
|
1319
|
+
});
|
|
1140
1320
|
} else {
|
|
1141
|
-
|
|
1321
|
+
// eslint-disable-next-line no-process-exit
|
|
1322
|
+
process.exit();
|
|
1142
1323
|
}
|
|
1143
1324
|
});
|
|
1144
|
-
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
this.listeners.push({ name: signal, listener });
|
|
1328
|
+
|
|
1329
|
+
process.on(signal, listener);
|
|
1145
1330
|
});
|
|
1146
1331
|
}
|
|
1147
1332
|
|
|
1148
1333
|
// Proxy WebSocket without the initial http request
|
|
1149
1334
|
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
1150
|
-
|
|
1151
|
-
this.webSocketProxies.forEach(function (webSocketProxy) {
|
|
1335
|
+
this.webSocketProxies.forEach((webSocketProxy) => {
|
|
1152
1336
|
this.server.on("upgrade", webSocketProxy.upgrade);
|
|
1153
1337
|
}, this);
|
|
1154
1338
|
}
|
|
@@ -1547,28 +1731,11 @@ class Server {
|
|
|
1547
1731
|
}
|
|
1548
1732
|
|
|
1549
1733
|
createServer() {
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
...this.options.https,
|
|
1556
|
-
spdy: {
|
|
1557
|
-
protocols: ["h2", "http/1.1"],
|
|
1558
|
-
},
|
|
1559
|
-
},
|
|
1560
|
-
this.app
|
|
1561
|
-
);
|
|
1562
|
-
} else {
|
|
1563
|
-
const https = require("https");
|
|
1564
|
-
|
|
1565
|
-
this.server = https.createServer(this.options.https, this.app);
|
|
1566
|
-
}
|
|
1567
|
-
} else {
|
|
1568
|
-
const http = require("http");
|
|
1569
|
-
|
|
1570
|
-
this.server = http.createServer(this.app);
|
|
1571
|
-
}
|
|
1734
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
1735
|
+
this.server = require(this.options.server.type).createServer(
|
|
1736
|
+
this.options.server.options,
|
|
1737
|
+
this.app
|
|
1738
|
+
);
|
|
1572
1739
|
|
|
1573
1740
|
this.server.on("connection", (socket) => {
|
|
1574
1741
|
// Add socket to list
|
|
@@ -1585,6 +1752,7 @@ class Server {
|
|
|
1585
1752
|
});
|
|
1586
1753
|
}
|
|
1587
1754
|
|
|
1755
|
+
// TODO: remove `--web-socket-server` in favor of `--web-socket-server-type`
|
|
1588
1756
|
createWebSocketServer() {
|
|
1589
1757
|
this.webSocketServer = new (this.getServerTransport())(this);
|
|
1590
1758
|
this.webSocketServer.implementation.on("connection", (client, request) => {
|
|
@@ -1610,7 +1778,9 @@ class Server {
|
|
|
1610
1778
|
) {
|
|
1611
1779
|
this.sendMessage([client], "error", "Invalid Host/Origin header");
|
|
1612
1780
|
|
|
1613
|
-
|
|
1781
|
+
// With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
|
|
1782
|
+
// Terminate would prevent it sending, so use close to allow it to be sent
|
|
1783
|
+
client.close();
|
|
1614
1784
|
|
|
1615
1785
|
return;
|
|
1616
1786
|
}
|
|
@@ -1627,6 +1797,10 @@ class Server {
|
|
|
1627
1797
|
this.sendMessage([client], "progress", this.options.client.progress);
|
|
1628
1798
|
}
|
|
1629
1799
|
|
|
1800
|
+
if (this.options.client && this.options.client.reconnect) {
|
|
1801
|
+
this.sendMessage([client], "reconnect", this.options.client.reconnect);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1630
1804
|
if (this.options.client && this.options.client.overlay) {
|
|
1631
1805
|
this.sendMessage([client], "overlay", this.options.client.overlay);
|
|
1632
1806
|
}
|
|
@@ -1674,22 +1848,25 @@ class Server {
|
|
|
1674
1848
|
);
|
|
1675
1849
|
}
|
|
1676
1850
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1851
|
+
stopBonjour(callback = () => {}) {
|
|
1852
|
+
this.bonjour.unpublishAll(() => {
|
|
1853
|
+
this.bonjour.destroy();
|
|
1679
1854
|
|
|
1680
|
-
|
|
1855
|
+
if (callback) {
|
|
1856
|
+
callback();
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
runBonjour() {
|
|
1862
|
+
this.bonjour = require("bonjour")();
|
|
1863
|
+
this.bonjour.publish({
|
|
1681
1864
|
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
|
|
1682
1865
|
port: this.options.port,
|
|
1683
|
-
type: this.options.
|
|
1866
|
+
type: this.options.server.type === "http" ? "http" : "https",
|
|
1684
1867
|
subtypes: ["webpack"],
|
|
1685
1868
|
...this.options.bonjour,
|
|
1686
1869
|
});
|
|
1687
|
-
|
|
1688
|
-
process.on("exit", () => {
|
|
1689
|
-
bonjour.unpublishAll(() => {
|
|
1690
|
-
bonjour.destroy();
|
|
1691
|
-
});
|
|
1692
|
-
});
|
|
1693
1870
|
}
|
|
1694
1871
|
|
|
1695
1872
|
logStatus() {
|
|
@@ -1731,7 +1908,7 @@ class Server {
|
|
|
1731
1908
|
if (this.options.ipc) {
|
|
1732
1909
|
this.logger.info(`Project is running at: "${this.server.address()}"`);
|
|
1733
1910
|
} else {
|
|
1734
|
-
const protocol = this.options.
|
|
1911
|
+
const protocol = this.options.server.type === "http" ? "http" : "https";
|
|
1735
1912
|
const { address, port } = this.server.address();
|
|
1736
1913
|
const prettyPrintURL = (newHostname) =>
|
|
1737
1914
|
url.format({ protocol, hostname: newHostname, port, pathname: "/" });
|
|
@@ -1766,13 +1943,13 @@ class Server {
|
|
|
1766
1943
|
if (parsedIP.range() === "unspecified") {
|
|
1767
1944
|
localhost = prettyPrintURL("localhost");
|
|
1768
1945
|
|
|
1769
|
-
const networkIPv4 =
|
|
1946
|
+
const networkIPv4 = Server.internalIPSync("v4");
|
|
1770
1947
|
|
|
1771
1948
|
if (networkIPv4) {
|
|
1772
1949
|
networkUrlIPv4 = prettyPrintURL(networkIPv4);
|
|
1773
1950
|
}
|
|
1774
1951
|
|
|
1775
|
-
const networkIPv6 =
|
|
1952
|
+
const networkIPv6 = Server.internalIPSync("v6");
|
|
1776
1953
|
|
|
1777
1954
|
if (networkIPv6) {
|
|
1778
1955
|
networkUrlIPv6 = prettyPrintURL(networkIPv6);
|
|
@@ -1850,7 +2027,9 @@ class Server {
|
|
|
1850
2027
|
|
|
1851
2028
|
if (this.options.bonjour) {
|
|
1852
2029
|
const bonjourProtocol =
|
|
1853
|
-
this.options.bonjour.type || this.options.
|
|
2030
|
+
this.options.bonjour.type || this.options.server.type === "http"
|
|
2031
|
+
? "http"
|
|
2032
|
+
: "https";
|
|
1854
2033
|
|
|
1855
2034
|
this.logger.info(
|
|
1856
2035
|
`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`
|
|
@@ -1969,14 +2148,14 @@ class Server {
|
|
|
1969
2148
|
}
|
|
1970
2149
|
|
|
1971
2150
|
// eslint-disable-next-line class-methods-use-this
|
|
1972
|
-
sendMessage(clients, type, data) {
|
|
1973
|
-
|
|
2151
|
+
sendMessage(clients, type, data, params) {
|
|
2152
|
+
for (const client of clients) {
|
|
1974
2153
|
// `sockjs` uses `1` to indicate client is ready to accept data
|
|
1975
2154
|
// `ws` uses `WebSocket.OPEN`, but it is mean `1` too
|
|
1976
2155
|
if (client.readyState === 1) {
|
|
1977
|
-
client.send(JSON.stringify({ type, data }));
|
|
2156
|
+
client.send(JSON.stringify({ type, data, params }));
|
|
1978
2157
|
}
|
|
1979
|
-
}
|
|
2158
|
+
}
|
|
1980
2159
|
}
|
|
1981
2160
|
|
|
1982
2161
|
serveMagicHtml(req, res, next) {
|
|
@@ -2011,8 +2190,7 @@ class Server {
|
|
|
2011
2190
|
stats &&
|
|
2012
2191
|
(!stats.errors || stats.errors.length === 0) &&
|
|
2013
2192
|
(!stats.warnings || stats.warnings.length === 0) &&
|
|
2014
|
-
stats.
|
|
2015
|
-
stats.assets.every((asset) => !asset.emitted);
|
|
2193
|
+
this.currentHash === stats.hash;
|
|
2016
2194
|
|
|
2017
2195
|
if (shouldEmit) {
|
|
2018
2196
|
this.sendMessage(clients, "still-ok");
|
|
@@ -2020,11 +2198,20 @@ class Server {
|
|
|
2020
2198
|
return;
|
|
2021
2199
|
}
|
|
2022
2200
|
|
|
2201
|
+
this.currentHash = stats.hash;
|
|
2023
2202
|
this.sendMessage(clients, "hash", stats.hash);
|
|
2024
2203
|
|
|
2025
2204
|
if (stats.errors.length > 0 || stats.warnings.length > 0) {
|
|
2205
|
+
const hasErrors = stats.errors.length > 0;
|
|
2206
|
+
|
|
2026
2207
|
if (stats.warnings.length > 0) {
|
|
2027
|
-
|
|
2208
|
+
let params;
|
|
2209
|
+
|
|
2210
|
+
if (hasErrors) {
|
|
2211
|
+
params = { preventReloading: true };
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
this.sendMessage(clients, "warnings", stats.warnings, params);
|
|
2028
2215
|
}
|
|
2029
2216
|
|
|
2030
2217
|
if (stats.errors.length > 0) {
|
|
@@ -2036,37 +2223,8 @@ class Server {
|
|
|
2036
2223
|
}
|
|
2037
2224
|
|
|
2038
2225
|
watchFiles(watchPath, watchOptions) {
|
|
2039
|
-
// duplicate the same massaging of options that watchpack performs
|
|
2040
|
-
// https://github.com/webpack/watchpack/blob/master/lib/DirectoryWatcher.js#L49
|
|
2041
|
-
// this isn't an elegant solution, but we'll improve it in the future
|
|
2042
|
-
// eslint-disable-next-line no-undefined
|
|
2043
|
-
const usePolling =
|
|
2044
|
-
typeof watchOptions.usePolling !== "undefined"
|
|
2045
|
-
? watchOptions.usePolling
|
|
2046
|
-
: Boolean(watchOptions.poll);
|
|
2047
|
-
const interval =
|
|
2048
|
-
// eslint-disable-next-line no-nested-ternary
|
|
2049
|
-
typeof watchOptions.interval !== "undefined"
|
|
2050
|
-
? watchOptions.interval
|
|
2051
|
-
: typeof watchOptions.poll === "number"
|
|
2052
|
-
? watchOptions.poll
|
|
2053
|
-
: // eslint-disable-next-line no-undefined
|
|
2054
|
-
undefined;
|
|
2055
|
-
|
|
2056
|
-
const finalWatchOptions = {
|
|
2057
|
-
ignoreInitial: true,
|
|
2058
|
-
persistent: true,
|
|
2059
|
-
followSymlinks: false,
|
|
2060
|
-
atomic: false,
|
|
2061
|
-
alwaysStat: true,
|
|
2062
|
-
ignorePermissionErrors: true,
|
|
2063
|
-
ignored: watchOptions.ignored,
|
|
2064
|
-
usePolling,
|
|
2065
|
-
interval,
|
|
2066
|
-
};
|
|
2067
|
-
|
|
2068
2226
|
const chokidar = require("chokidar");
|
|
2069
|
-
const watcher = chokidar.watch(watchPath,
|
|
2227
|
+
const watcher = chokidar.watch(watchPath, watchOptions);
|
|
2070
2228
|
|
|
2071
2229
|
// disabling refreshing on changing the content
|
|
2072
2230
|
if (this.options.liveReload) {
|
|
@@ -2158,11 +2316,21 @@ class Server {
|
|
|
2158
2316
|
}
|
|
2159
2317
|
}
|
|
2160
2318
|
|
|
2161
|
-
startCallback(callback) {
|
|
2162
|
-
this.start()
|
|
2319
|
+
startCallback(callback = () => {}) {
|
|
2320
|
+
this.start()
|
|
2321
|
+
.then(() => callback(null), callback)
|
|
2322
|
+
.catch(callback);
|
|
2163
2323
|
}
|
|
2164
2324
|
|
|
2165
2325
|
async stop() {
|
|
2326
|
+
if (this.bonjour) {
|
|
2327
|
+
await new Promise((resolve) => {
|
|
2328
|
+
this.stopBonjour(() => {
|
|
2329
|
+
resolve();
|
|
2330
|
+
});
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2166
2334
|
this.webSocketProxies = [];
|
|
2167
2335
|
|
|
2168
2336
|
await Promise.all(this.staticWatchers.map((watcher) => watcher.close()));
|
|
@@ -2216,10 +2384,18 @@ class Server {
|
|
|
2216
2384
|
this.middleware = null;
|
|
2217
2385
|
}
|
|
2218
2386
|
}
|
|
2387
|
+
|
|
2388
|
+
// We add listeners to signals when creating a new Server instance
|
|
2389
|
+
// So ensure they are removed to prevent EventEmitter memory leak warnings
|
|
2390
|
+
for (const item of this.listeners) {
|
|
2391
|
+
process.removeListener(item.name, item.listener);
|
|
2392
|
+
}
|
|
2219
2393
|
}
|
|
2220
2394
|
|
|
2221
|
-
stopCallback(callback) {
|
|
2222
|
-
this.stop()
|
|
2395
|
+
stopCallback(callback = () => {}) {
|
|
2396
|
+
this.stop()
|
|
2397
|
+
.then(() => callback(null), callback)
|
|
2398
|
+
.catch(callback);
|
|
2223
2399
|
}
|
|
2224
2400
|
|
|
2225
2401
|
// TODO remove in the next major release
|