webpack-dev-server 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Server.js CHANGED
@@ -36,13 +36,13 @@ class Server {
36
36
  this.webSocketProxies = [];
37
37
  this.sockets = [];
38
38
  this.compiler = compiler;
39
+ this.currentHash = null;
39
40
  }
40
41
 
41
42
  static get DEFAULT_STATS() {
42
43
  return {
43
44
  all: false,
44
45
  hash: true,
45
- assets: true,
46
46
  warnings: true,
47
47
  errors: true,
48
48
  errorDetails: false,
@@ -74,7 +74,7 @@ class Server {
74
74
  }
75
75
 
76
76
  static async getFreePort(port) {
77
- if (port && port !== "auto") {
77
+ if (typeof port !== "undefined" && port !== null && port !== "auto") {
78
78
  return port;
79
79
  }
80
80
 
@@ -155,7 +155,7 @@ class Server {
155
155
  if (typeof this.options.client.webSocketURL.protocol !== "undefined") {
156
156
  protocol = this.options.client.webSocketURL.protocol;
157
157
  } else {
158
- protocol = this.options.https ? "wss:" : "ws:";
158
+ protocol = this.options.server.type === "http" ? "ws:" : "wss:";
159
159
  }
160
160
 
161
161
  searchParams.set("protocol", protocol);
@@ -258,6 +258,10 @@ class Server {
258
258
  searchParams.set("logging", this.options.client.logging);
259
259
  }
260
260
 
261
+ if (typeof this.options.client.reconnect !== "undefined") {
262
+ searchParams.set("reconnect", this.options.client.reconnect);
263
+ }
264
+
261
265
  webSocketURL = searchParams.toString();
262
266
  }
263
267
 
@@ -474,6 +478,14 @@ class Server {
474
478
  };
475
479
  }
476
480
 
481
+ if (typeof options.client.reconnect === "undefined") {
482
+ options.client.reconnect = 10;
483
+ } else if (options.client.reconnect === true) {
484
+ options.client.reconnect = Infinity;
485
+ } else if (options.client.reconnect === false) {
486
+ options.client.reconnect = 0;
487
+ }
488
+
477
489
  // Respect infrastructureLogging.level
478
490
  if (typeof options.client.logging === "undefined") {
479
491
  options.client.logging = compilerOptions.infrastructureLogging
@@ -508,23 +520,51 @@ class Server {
508
520
  ? options.hot
509
521
  : true;
510
522
 
511
- // if the user enables http2, we can safely enable https
512
- if ((options.http2 && !options.https) || options.https === true) {
513
- options.https = {
514
- requestCert: false,
523
+ const isHTTPs = Boolean(options.https);
524
+ const isSPDY = Boolean(options.http2);
525
+
526
+ options.server = {
527
+ type:
528
+ // eslint-disable-next-line no-nested-ternary
529
+ typeof options.server === "string"
530
+ ? options.server
531
+ : // eslint-disable-next-line no-nested-ternary
532
+ typeof (options.server || {}).type === "string"
533
+ ? options.server.type
534
+ : // eslint-disable-next-line no-nested-ternary
535
+ isSPDY
536
+ ? "spdy"
537
+ : isHTTPs
538
+ ? "https"
539
+ : "http",
540
+ options: {
541
+ ...options.https,
542
+ ...(options.server || {}).options,
543
+ },
544
+ };
545
+
546
+ if (
547
+ options.server.type === "spdy" &&
548
+ typeof options.server.options.spdy === "undefined"
549
+ ) {
550
+ options.server.options.spdy = {
551
+ protocols: ["h2", "http/1.1"],
515
552
  };
516
553
  }
517
554
 
518
- // https option
519
- if (options.https) {
555
+ if (options.server.type === "https" || options.server.type === "spdy") {
556
+ if (typeof options.server.options.requestCert === "undefined") {
557
+ options.server.options.requestCert = false;
558
+ }
559
+
520
560
  // TODO remove the `cacert` option in favor `ca` in the next major release
521
561
  for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) {
522
- if (typeof options.https[property] === "undefined") {
562
+ if (typeof options.server.options[property] === "undefined") {
523
563
  // eslint-disable-next-line no-continue
524
564
  continue;
525
565
  }
526
566
 
527
- const value = options.https[property];
567
+ const value = options.server.options[property];
528
568
  const readFile = (item) => {
529
569
  if (
530
570
  Buffer.isBuffer(item) ||
@@ -547,14 +587,14 @@ class Server {
547
587
  }
548
588
  };
549
589
 
550
- options.https[property] = Array.isArray(value)
590
+ options.server.options[property] = Array.isArray(value)
551
591
  ? value.map((item) => readFile(item))
552
592
  : readFile(value);
553
593
  }
554
594
 
555
595
  let fakeCert;
556
596
 
557
- if (!options.https.key || !options.https.cert) {
597
+ if (!options.server.options.key || !options.server.options.cert) {
558
598
  const certificateDir = Server.findCacheDir();
559
599
  const certificatePath = path.join(certificateDir, "server.pem");
560
600
  let certificateExists;
@@ -577,7 +617,7 @@ class Server {
577
617
  const del = require("del");
578
618
 
579
619
  this.logger.info(
580
- "SSL Certificate is more than 30 days old. Removing..."
620
+ "SSL certificate is more than 30 days old. Removing..."
581
621
  );
582
622
 
583
623
  await del([certificatePath], { force: true });
@@ -587,7 +627,7 @@ class Server {
587
627
  }
588
628
 
589
629
  if (!certificateExists) {
590
- this.logger.info("Generating SSL Certificate...");
630
+ this.logger.info("Generating SSL certificate...");
591
631
 
592
632
  const selfsigned = require("selfsigned");
593
633
  const attributes = [{ name: "commonName", value: "localhost" }];
@@ -669,20 +709,20 @@ class Server {
669
709
  this.logger.info(`SSL certificate: ${certificatePath}`);
670
710
  }
671
711
 
672
- if (options.https.cacert) {
673
- if (options.https.ca) {
712
+ if (options.server.options.cacert) {
713
+ if (options.server.options.ca) {
674
714
  this.logger.warn(
675
- "Do not specify 'https.ca' and 'https.cacert' options together, the 'https.ca' option will be used."
715
+ "Do not specify 'ca' and 'cacert' options together, the 'ca' option will be used."
676
716
  );
677
717
  } else {
678
- options.https.ca = options.https.cacert;
718
+ options.server.options.ca = options.server.options.cacert;
679
719
  }
680
720
 
681
- delete options.https.cacert;
721
+ delete options.server.options.cacert;
682
722
  }
683
723
 
684
- options.https.key = options.https.key || fakeCert;
685
- options.https.cert = options.https.cert || fakeCert;
724
+ options.server.options.key = options.server.options.key || fakeCert;
725
+ options.server.options.cert = options.server.options.cert || fakeCert;
686
726
  }
687
727
 
688
728
  if (typeof options.ipc === "boolean") {
@@ -1063,42 +1103,43 @@ class Server {
1063
1103
  }
1064
1104
 
1065
1105
  async initialize() {
1066
- const compilers = this.compiler.compilers || [this.compiler];
1106
+ if (this.options.webSocketServer) {
1107
+ const compilers = this.compiler.compilers || [this.compiler];
1067
1108
 
1068
- // eslint-disable-next-line no-shadow
1069
- compilers.forEach((compiler) => {
1070
- this.addAdditionalEntries(compiler);
1109
+ // eslint-disable-next-line no-shadow
1110
+ compilers.forEach((compiler) => {
1111
+ this.addAdditionalEntries(compiler);
1071
1112
 
1072
- const webpack = compiler.webpack || require("webpack");
1113
+ const webpack = compiler.webpack || require("webpack");
1073
1114
 
1074
- const providePlugin = new webpack.ProvidePlugin({
1075
- __webpack_dev_server_client__: this.getClientTransport(),
1076
- });
1115
+ new webpack.ProvidePlugin({
1116
+ __webpack_dev_server_client__: this.getClientTransport(),
1117
+ }).apply(compiler);
1077
1118
 
1078
- providePlugin.apply(compiler);
1119
+ // TODO remove after drop webpack v4 support
1120
+ compiler.options.plugins = compiler.options.plugins || [];
1079
1121
 
1080
- // TODO remove after drop webpack v4 support
1081
- compiler.options.plugins = compiler.options.plugins || [];
1122
+ if (this.options.hot) {
1123
+ const HMRPluginExists = compiler.options.plugins.find(
1124
+ (p) => p.constructor === webpack.HotModuleReplacementPlugin
1125
+ );
1082
1126
 
1083
- if (this.options.hot) {
1084
- const HMRPluginExists = compiler.options.plugins.find(
1085
- (p) => p.constructor === webpack.HotModuleReplacementPlugin
1086
- );
1127
+ if (HMRPluginExists) {
1128
+ this.logger.warn(
1129
+ `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
1130
+ );
1131
+ } else {
1132
+ // Apply the HMR plugin
1133
+ const plugin = new webpack.HotModuleReplacementPlugin();
1087
1134
 
1088
- if (HMRPluginExists) {
1089
- this.logger.warn(
1090
- `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
1091
- );
1092
- } else {
1093
- // apply the HMR plugin
1094
- const plugin = new webpack.HotModuleReplacementPlugin();
1095
- plugin.apply(compiler);
1135
+ plugin.apply(compiler);
1136
+ }
1096
1137
  }
1097
- }
1098
- });
1138
+ });
1099
1139
 
1100
- if (this.options.client && this.options.client.progress) {
1101
- this.setupProgressPlugin();
1140
+ if (this.options.client && this.options.client.progress) {
1141
+ this.setupProgressPlugin();
1142
+ }
1102
1143
  }
1103
1144
 
1104
1145
  this.setupHooks();
@@ -1114,11 +1155,31 @@ class Server {
1114
1155
  if (this.options.setupExitSignals) {
1115
1156
  const signals = ["SIGINT", "SIGTERM"];
1116
1157
 
1158
+ let needForceShutdown = false;
1159
+
1160
+ const exitProcess = () => {
1161
+ // eslint-disable-next-line no-process-exit
1162
+ process.exit();
1163
+ };
1164
+
1117
1165
  signals.forEach((signal) => {
1118
1166
  process.on(signal, () => {
1167
+ if (needForceShutdown) {
1168
+ exitProcess();
1169
+ }
1170
+
1171
+ this.logger.info(
1172
+ "Gracefully shutting down. To force exit, press ^C again. Please wait..."
1173
+ );
1174
+
1175
+ needForceShutdown = true;
1176
+
1119
1177
  this.stopCallback(() => {
1120
- // eslint-disable-next-line no-process-exit
1121
- process.exit();
1178
+ if (typeof this.compiler.close === "function") {
1179
+ this.compiler.close(exitProcess);
1180
+ } else {
1181
+ exitProcess();
1182
+ }
1122
1183
  });
1123
1184
  });
1124
1185
  });
@@ -1150,26 +1211,18 @@ class Server {
1150
1211
  }
1151
1212
 
1152
1213
  setupHooks() {
1153
- const addHooks = (compiler) => {
1154
- compiler.hooks.invalid.tap("webpack-dev-server", () => {
1155
- if (this.webSocketServer) {
1156
- this.sendMessage(this.webSocketServer.clients, "invalid");
1157
- }
1158
- });
1159
- compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1160
- if (this.webSocketServer) {
1161
- this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1162
- }
1163
-
1164
- this.stats = stats;
1165
- });
1166
- };
1214
+ this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
1215
+ if (this.webSocketServer) {
1216
+ this.sendMessage(this.webSocketServer.clients, "invalid");
1217
+ }
1218
+ });
1219
+ this.compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1220
+ if (this.webSocketServer) {
1221
+ this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1222
+ }
1167
1223
 
1168
- if (this.compiler.compilers) {
1169
- this.compiler.compilers.forEach(addHooks);
1170
- } else {
1171
- addHooks(this.compiler);
1172
- }
1224
+ this.stats = stats;
1225
+ });
1173
1226
  }
1174
1227
 
1175
1228
  setupHostHeaderCheck() {
@@ -1534,28 +1587,11 @@ class Server {
1534
1587
  }
1535
1588
 
1536
1589
  createServer() {
1537
- if (this.options.https) {
1538
- if (this.options.http2) {
1539
- // TODO: we need to replace spdy with http2 which is an internal module
1540
- this.server = require("spdy").createServer(
1541
- {
1542
- ...this.options.https,
1543
- spdy: {
1544
- protocols: ["h2", "http/1.1"],
1545
- },
1546
- },
1547
- this.app
1548
- );
1549
- } else {
1550
- const https = require("https");
1551
-
1552
- this.server = https.createServer(this.options.https, this.app);
1553
- }
1554
- } else {
1555
- const http = require("http");
1556
-
1557
- this.server = http.createServer(this.app);
1558
- }
1590
+ // eslint-disable-next-line import/no-dynamic-require
1591
+ this.server = require(this.options.server.type).createServer(
1592
+ this.options.server.options,
1593
+ this.app
1594
+ );
1559
1595
 
1560
1596
  this.server.on("connection", (socket) => {
1561
1597
  // Add socket to list
@@ -1614,6 +1650,10 @@ class Server {
1614
1650
  this.sendMessage([client], "progress", this.options.client.progress);
1615
1651
  }
1616
1652
 
1653
+ if (this.options.client && this.options.client.reconnect) {
1654
+ this.sendMessage([client], "reconnect", this.options.client.reconnect);
1655
+ }
1656
+
1617
1657
  if (this.options.client && this.options.client.overlay) {
1618
1658
  this.sendMessage([client], "overlay", this.options.client.overlay);
1619
1659
  }
@@ -1667,7 +1707,7 @@ class Server {
1667
1707
  bonjour.publish({
1668
1708
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
1669
1709
  port: this.options.port,
1670
- type: this.options.https ? "https" : "http",
1710
+ type: this.options.server.type === "http" ? "http" : "https",
1671
1711
  subtypes: ["webpack"],
1672
1712
  ...this.options.bonjour,
1673
1713
  });
@@ -1680,7 +1720,7 @@ class Server {
1680
1720
  }
1681
1721
 
1682
1722
  logStatus() {
1683
- const colorette = require("colorette");
1723
+ const { isColorSupported, cyan, red } = require("colorette");
1684
1724
 
1685
1725
  const getColorsOption = (compilerOptions) => {
1686
1726
  let colorsEnabled;
@@ -1691,7 +1731,7 @@ class Server {
1691
1731
  ) {
1692
1732
  colorsEnabled = compilerOptions.stats;
1693
1733
  } else {
1694
- colorsEnabled = colorette.options.enabled;
1734
+ colorsEnabled = isColorSupported;
1695
1735
  }
1696
1736
 
1697
1737
  return colorsEnabled;
@@ -1700,14 +1740,14 @@ class Server {
1700
1740
  const colors = {
1701
1741
  info(useColor, msg) {
1702
1742
  if (useColor) {
1703
- return colorette.cyan(msg);
1743
+ return cyan(msg);
1704
1744
  }
1705
1745
 
1706
1746
  return msg;
1707
1747
  },
1708
1748
  error(useColor, msg) {
1709
1749
  if (useColor) {
1710
- return colorette.red(msg);
1750
+ return red(msg);
1711
1751
  }
1712
1752
 
1713
1753
  return msg;
@@ -1718,7 +1758,7 @@ class Server {
1718
1758
  if (this.options.ipc) {
1719
1759
  this.logger.info(`Project is running at: "${this.server.address()}"`);
1720
1760
  } else {
1721
- const protocol = this.options.https ? "https" : "http";
1761
+ const protocol = this.options.server.type === "http" ? "http" : "https";
1722
1762
  const { address, port } = this.server.address();
1723
1763
  const prettyPrintURL = (newHostname) =>
1724
1764
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
@@ -1837,7 +1877,9 @@ class Server {
1837
1877
 
1838
1878
  if (this.options.bonjour) {
1839
1879
  const bonjourProtocol =
1840
- this.options.bonjour.type || this.options.https ? "https" : "http";
1880
+ this.options.bonjour.type || this.options.server.type === "http"
1881
+ ? "http"
1882
+ : "https";
1841
1883
 
1842
1884
  this.logger.info(
1843
1885
  `Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`
@@ -1853,10 +1895,19 @@ class Server {
1853
1895
  headers = headers(req, res, this.middleware.context);
1854
1896
  }
1855
1897
 
1856
- // eslint-disable-next-line guard-for-in
1857
- for (const name in headers) {
1858
- res.setHeader(name, headers[name]);
1898
+ const allHeaders = [];
1899
+
1900
+ if (!Array.isArray(headers)) {
1901
+ // eslint-disable-next-line guard-for-in
1902
+ for (const name in headers) {
1903
+ allHeaders.push({ key: name, value: headers[name] });
1904
+ }
1905
+ headers = allHeaders;
1859
1906
  }
1907
+
1908
+ headers.forEach((header) => {
1909
+ res.setHeader(header.key, header.value);
1910
+ });
1860
1911
  }
1861
1912
 
1862
1913
  next();
@@ -1948,13 +1999,13 @@ class Server {
1948
1999
 
1949
2000
  // eslint-disable-next-line class-methods-use-this
1950
2001
  sendMessage(clients, type, data) {
1951
- clients.forEach((client) => {
2002
+ for (const client of clients) {
1952
2003
  // `sockjs` uses `1` to indicate client is ready to accept data
1953
2004
  // `ws` uses `WebSocket.OPEN`, but it is mean `1` too
1954
2005
  if (client.readyState === 1) {
1955
2006
  client.send(JSON.stringify({ type, data }));
1956
2007
  }
1957
- });
2008
+ }
1958
2009
  }
1959
2010
 
1960
2011
  serveMagicHtml(req, res, next) {
@@ -1989,8 +2040,7 @@ class Server {
1989
2040
  stats &&
1990
2041
  (!stats.errors || stats.errors.length === 0) &&
1991
2042
  (!stats.warnings || stats.warnings.length === 0) &&
1992
- stats.assets &&
1993
- stats.assets.every((asset) => !asset.emitted);
2043
+ this.currentHash === stats.hash;
1994
2044
 
1995
2045
  if (shouldEmit) {
1996
2046
  this.sendMessage(clients, "still-ok");
@@ -1998,6 +2048,7 @@ class Server {
1998
2048
  return;
1999
2049
  }
2000
2050
 
2051
+ this.currentHash = stats.hash;
2001
2052
  this.sendMessage(clients, "hash", stats.hash);
2002
2053
 
2003
2054
  if (stats.errors.length > 0 || stats.warnings.length > 0) {
@@ -2044,7 +2095,6 @@ class Server {
2044
2095
  };
2045
2096
 
2046
2097
  const chokidar = require("chokidar");
2047
-
2048
2098
  const watcher = chokidar.watch(watchPath, finalWatchOptions);
2049
2099
 
2050
2100
  // disabling refreshing on changing the content
@@ -2138,7 +2188,9 @@ class Server {
2138
2188
  }
2139
2189
 
2140
2190
  startCallback(callback) {
2141
- this.start().then(() => callback(null), callback);
2191
+ this.start()
2192
+ .then(() => callback(null), callback)
2193
+ .catch(callback);
2142
2194
  }
2143
2195
 
2144
2196
  async stop() {
@@ -2198,7 +2250,9 @@ class Server {
2198
2250
  }
2199
2251
 
2200
2252
  stopCallback(callback) {
2201
- this.stop().then(() => callback(null), callback);
2253
+ this.stop()
2254
+ .then(() => callback(null), callback)
2255
+ .catch(callback);
2202
2256
  }
2203
2257
 
2204
2258
  // TODO remove in the next major release