webpack-dev-server 4.0.0 → 4.1.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 CHANGED
@@ -132,6 +132,8 @@ Options:
132
132
  --ipc [value] Listen to a unix socket.
133
133
  --live-reload Enables reload/refresh the page(s) when file changes are detected (enabled by default).
134
134
  --no-live-reload Negative 'live-reload' option.
135
+ --magic-html Enables/Disables magic HTML routes (enabled by default).
136
+ --no-magic-html Negative 'magic-html' option.
135
137
  --open [value...] Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to
136
138
  true to open your default browser).
137
139
  --no-open Negative 'open' option.
package/bin/cli-flags.js CHANGED
@@ -534,6 +534,19 @@ module.exports = {
534
534
  simpleType: "boolean",
535
535
  multiple: false,
536
536
  },
537
+ "magic-html": {
538
+ configs: [
539
+ {
540
+ type: "boolean",
541
+ multiple: false,
542
+ description: "Enables/Disables magic HTML routes (enabled by default).",
543
+ path: "magicHtml",
544
+ },
545
+ ],
546
+ description: "Enables/Disables magic HTML routes (enabled by default).",
547
+ simpleType: "boolean",
548
+ multiple: false,
549
+ },
537
550
  open: {
538
551
  configs: [
539
552
  {
package/client/index.js CHANGED
@@ -23,6 +23,16 @@ var options = {
23
23
  };
24
24
  var parsedResourceQuery = parseURL(__resourceQuery);
25
25
 
26
+ if (parsedResourceQuery.hot === "true") {
27
+ options.hot = true;
28
+ log.info("Hot Module Replacement enabled.");
29
+ }
30
+
31
+ if (parsedResourceQuery["live-reload"] === "true") {
32
+ options.liveReload = true;
33
+ log.info("Live Reloading enabled.");
34
+ }
35
+
26
36
  if (parsedResourceQuery.logging) {
27
37
  options.logging = parsedResourceQuery.logging;
28
38
  }
@@ -158,7 +168,7 @@ var onSocketMessage = {
158
168
  log.error(_error);
159
169
  },
160
170
  close: function close() {
161
- log.error("Disconnected!");
171
+ log.info("Disconnected!");
162
172
  sendMessage("Close");
163
173
  }
164
174
  };
package/lib/Server.js CHANGED
@@ -126,6 +126,227 @@ class Server {
126
126
  return path.resolve(dir, "node_modules/.cache/webpack-dev-server");
127
127
  }
128
128
 
129
+ addAdditionalEntries(compiler) {
130
+ const additionalEntries = [];
131
+
132
+ const isWebTarget = compiler.options.externalsPresets
133
+ ? compiler.options.externalsPresets.web
134
+ : [
135
+ "web",
136
+ "webworker",
137
+ "electron-preload",
138
+ "electron-renderer",
139
+ "node-webkit",
140
+ // eslint-disable-next-line no-undefined
141
+ undefined,
142
+ null,
143
+ ].includes(compiler.options.target);
144
+
145
+ // TODO maybe empty empty client
146
+ if (this.options.client && isWebTarget) {
147
+ let webSocketURL = "";
148
+ if (this.options.webSocketServer) {
149
+ const searchParams = new URLSearchParams();
150
+
151
+ /** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
152
+ let protocol;
153
+
154
+ // We are proxying dev server and need to specify custom `hostname`
155
+ if (typeof this.options.client.webSocketURL.protocol !== "undefined") {
156
+ protocol = this.options.client.webSocketURL.protocol;
157
+ } else {
158
+ protocol = this.options.https ? "wss:" : "ws:";
159
+ }
160
+
161
+ searchParams.set("protocol", protocol);
162
+
163
+ if (typeof this.options.client.webSocketURL.username !== "undefined") {
164
+ searchParams.set(
165
+ "username",
166
+ this.options.client.webSocketURL.username
167
+ );
168
+ }
169
+
170
+ if (typeof this.options.client.webSocketURL.password !== "undefined") {
171
+ searchParams.set(
172
+ "password",
173
+ this.options.client.webSocketURL.password
174
+ );
175
+ }
176
+
177
+ /** @type {string} */
178
+ let hostname;
179
+
180
+ // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
181
+ // TODO show warning about this
182
+ const isSockJSType = this.options.webSocketServer.type === "sockjs";
183
+
184
+ // We are proxying dev server and need to specify custom `hostname`
185
+ if (typeof this.options.client.webSocketURL.hostname !== "undefined") {
186
+ hostname = this.options.client.webSocketURL.hostname;
187
+ }
188
+ // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
189
+ else if (
190
+ typeof this.options.webSocketServer.options.host !== "undefined" &&
191
+ !isSockJSType
192
+ ) {
193
+ hostname = this.options.webSocketServer.options.host;
194
+ }
195
+ // The `host` option is specified
196
+ else if (typeof this.options.host !== "undefined") {
197
+ hostname = this.options.host;
198
+ }
199
+ // The `port` option is not specified
200
+ else {
201
+ hostname = "0.0.0.0";
202
+ }
203
+
204
+ searchParams.set("hostname", hostname);
205
+
206
+ /** @type {number | string} */
207
+ let port;
208
+
209
+ // We are proxying dev server and need to specify custom `port`
210
+ if (typeof this.options.client.webSocketURL.port !== "undefined") {
211
+ port = this.options.client.webSocketURL.port;
212
+ }
213
+ // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
214
+ else if (
215
+ typeof this.options.webSocketServer.options.port !== "undefined" &&
216
+ !isSockJSType
217
+ ) {
218
+ port = this.options.webSocketServer.options.port;
219
+ }
220
+ // The `port` option is specified
221
+ else if (typeof this.options.port === "number") {
222
+ port = this.options.port;
223
+ }
224
+ // The `port` option is specified using `string`
225
+ else if (
226
+ typeof this.options.port === "string" &&
227
+ this.options.port !== "auto"
228
+ ) {
229
+ port = Number(this.options.port);
230
+ }
231
+ // The `port` option is not specified or set to `auto`
232
+ else {
233
+ port = "0";
234
+ }
235
+
236
+ searchParams.set("port", String(port));
237
+
238
+ /** @type {string} */
239
+ let pathname = "";
240
+
241
+ // We are proxying dev server and need to specify custom `pathname`
242
+ if (typeof this.options.client.webSocketURL.pathname !== "undefined") {
243
+ pathname = this.options.client.webSocketURL.pathname;
244
+ }
245
+ // Web socket server works on custom `path`
246
+ else if (
247
+ typeof this.options.webSocketServer.options.prefix !== "undefined" ||
248
+ typeof this.options.webSocketServer.options.path !== "undefined"
249
+ ) {
250
+ pathname =
251
+ this.options.webSocketServer.options.prefix ||
252
+ this.options.webSocketServer.options.path;
253
+ }
254
+
255
+ searchParams.set("pathname", pathname);
256
+
257
+ if (typeof this.options.client.logging !== "undefined") {
258
+ searchParams.set("logging", this.options.client.logging);
259
+ }
260
+
261
+ webSocketURL = searchParams.toString();
262
+ }
263
+
264
+ additionalEntries.push(
265
+ `${require.resolve("../client/index.js")}?${webSocketURL}`
266
+ );
267
+ }
268
+
269
+ if (this.options.hot) {
270
+ let hotEntry;
271
+
272
+ if (this.options.hot === "only") {
273
+ hotEntry = require.resolve("webpack/hot/only-dev-server");
274
+ } else if (this.options.hot) {
275
+ hotEntry = require.resolve("webpack/hot/dev-server");
276
+ }
277
+
278
+ additionalEntries.push(hotEntry);
279
+ }
280
+
281
+ const webpack = compiler.webpack || require("webpack");
282
+
283
+ // use a hook to add entries if available
284
+ if (typeof webpack.EntryPlugin !== "undefined") {
285
+ for (const additionalEntry of additionalEntries) {
286
+ new webpack.EntryPlugin(compiler.context, additionalEntry, {
287
+ // eslint-disable-next-line no-undefined
288
+ name: undefined,
289
+ }).apply(compiler);
290
+ }
291
+ }
292
+ // TODO remove after drop webpack v4 support
293
+ else {
294
+ /**
295
+ * prependEntry Method for webpack 4
296
+ * @param {Entry} originalEntry
297
+ * @param {Entry} newAdditionalEntries
298
+ * @returns {Entry}
299
+ */
300
+ const prependEntry = (originalEntry, newAdditionalEntries) => {
301
+ if (typeof originalEntry === "function") {
302
+ return () =>
303
+ Promise.resolve(originalEntry()).then((entry) =>
304
+ prependEntry(entry, newAdditionalEntries)
305
+ );
306
+ }
307
+
308
+ if (
309
+ typeof originalEntry === "object" &&
310
+ !Array.isArray(originalEntry)
311
+ ) {
312
+ /** @type {Object<string,string>} */
313
+ const clone = {};
314
+
315
+ Object.keys(originalEntry).forEach((key) => {
316
+ // entry[key] should be a string here
317
+ const entryDescription = originalEntry[key];
318
+
319
+ clone[key] = prependEntry(entryDescription, newAdditionalEntries);
320
+ });
321
+
322
+ return clone;
323
+ }
324
+
325
+ // in this case, entry is a string or an array.
326
+ // make sure that we do not add duplicates.
327
+ /** @type {Entry} */
328
+ const entriesClone = additionalEntries.slice(0);
329
+
330
+ [].concat(originalEntry).forEach((newEntry) => {
331
+ if (!entriesClone.includes(newEntry)) {
332
+ entriesClone.push(newEntry);
333
+ }
334
+ });
335
+
336
+ return entriesClone;
337
+ };
338
+
339
+ compiler.options.entry = prependEntry(
340
+ compiler.options.entry || "./src",
341
+ additionalEntries
342
+ );
343
+ compiler.hooks.entryOption.call(
344
+ compiler.options.context,
345
+ compiler.options.entry
346
+ );
347
+ }
348
+ }
349
+
129
350
  getCompilerOptions() {
130
351
  if (typeof this.compiler.compilers !== "undefined") {
131
352
  if (this.compiler.compilers.length === 1) {
@@ -190,19 +411,24 @@ class Server {
190
411
  };
191
412
 
192
413
  if (typeof options.allowedHosts === "undefined") {
193
- // allowedHosts allows some default hosts picked from
194
- // `options.host` or `webSocketURL.hostname` and `localhost`
414
+ // AllowedHosts allows some default hosts picked from `options.host` or `webSocketURL.hostname` and `localhost`
195
415
  options.allowedHosts = "auto";
196
416
  }
197
-
198
- if (
417
+ // We store allowedHosts as array when supplied as string
418
+ else if (
199
419
  typeof options.allowedHosts === "string" &&
200
420
  options.allowedHosts !== "auto" &&
201
421
  options.allowedHosts !== "all"
202
422
  ) {
203
- // we store allowedHosts as array when supplied as string
204
423
  options.allowedHosts = [options.allowedHosts];
205
424
  }
425
+ // CLI pass options as array, we should normalize them
426
+ else if (
427
+ Array.isArray(this.options.allowedHosts) &&
428
+ this.options.allowedHosts.includes("all")
429
+ ) {
430
+ options.allowedHosts = "all";
431
+ }
206
432
 
207
433
  if (typeof options.bonjour === "undefined") {
208
434
  options.bonjour = false;
@@ -316,11 +542,18 @@ class Server {
316
542
  if (!options.https.key || !options.https.cert) {
317
543
  const certificateDir = Server.findCacheDir();
318
544
  const certificatePath = path.join(certificateDir, "server.pem");
319
- let certificateExists = fs.existsSync(certificatePath);
545
+ let certificateExists;
546
+
547
+ try {
548
+ const certificate = await fs.promises.stat(certificatePath);
549
+ certificateExists = certificate.isFile();
550
+ } catch {
551
+ certificateExists = false;
552
+ }
320
553
 
321
554
  if (certificateExists) {
322
555
  const certificateTtl = 1000 * 60 * 60 * 24;
323
- const certificateStat = fs.statSync(certificatePath);
556
+ const certificateStat = await fs.promises.stat(certificatePath);
324
557
 
325
558
  const now = new Date();
326
559
 
@@ -332,7 +565,7 @@ class Server {
332
565
  "SSL Certificate is more than 30 days old. Removing..."
333
566
  );
334
567
 
335
- del.sync([certificatePath], { force: true });
568
+ await del([certificatePath], { force: true });
336
569
 
337
570
  certificateExists = false;
338
571
  }
@@ -405,13 +638,18 @@ class Server {
405
638
  ],
406
639
  });
407
640
 
408
- fs.mkdirSync(certificateDir, { recursive: true });
409
- fs.writeFileSync(certificatePath, pems.private + pems.cert, {
410
- encoding: "utf8",
411
- });
641
+ await fs.promises.mkdir(certificateDir, { recursive: true });
642
+
643
+ await fs.promises.writeFile(
644
+ certificatePath,
645
+ pems.private + pems.cert,
646
+ {
647
+ encoding: "utf8",
648
+ }
649
+ );
412
650
  }
413
651
 
414
- fakeCert = fs.readFileSync(certificatePath);
652
+ fakeCert = await fs.promises.readFile(certificatePath);
415
653
 
416
654
  this.logger.info(`SSL certificate: ${certificatePath}`);
417
655
  }
@@ -431,6 +669,9 @@ class Server {
431
669
  options.liveReload =
432
670
  typeof options.liveReload !== "undefined" ? options.liveReload : true;
433
671
 
672
+ options.magicHtml =
673
+ typeof options.magicHtml !== "undefined" ? options.magicHtml : true;
674
+
434
675
  // https://github.com/webpack/webpack-dev-server/issues/1990
435
676
  const defaultOpenOptions = { wait: false };
436
677
  const getOpenItemsFromObject = ({ target, ...rest }) => {
@@ -667,53 +908,103 @@ class Server {
667
908
  }
668
909
  }
669
910
 
670
- async initialize() {
671
- this.applyDevServerPlugin();
911
+ getClientTransport() {
912
+ let ClientImplementation;
913
+ let clientImplementationFound = true;
672
914
 
673
- if (this.options.client && this.options.client.progress) {
674
- this.setupProgressPlugin();
675
- }
915
+ const isKnownWebSocketServerImplementation =
916
+ this.options.webSocketServer &&
917
+ typeof this.options.webSocketServer.type === "string" &&
918
+ (this.options.webSocketServer.type === "ws" ||
919
+ this.options.webSocketServer.type === "sockjs");
676
920
 
677
- this.setupHooks();
678
- this.setupApp();
679
- this.setupHostHeaderCheck();
680
- this.setupDevMiddleware();
681
- // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
682
- this.setupBuiltInRoutes();
683
- this.setupWatchFiles();
684
- this.setupFeatures();
685
- this.createServer();
921
+ let clientTransport;
686
922
 
687
- if (this.options.setupExitSignals) {
688
- const signals = ["SIGINT", "SIGTERM"];
923
+ if (this.options.client) {
924
+ if (typeof this.options.client.webSocketTransport !== "undefined") {
925
+ clientTransport = this.options.client.webSocketTransport;
926
+ } else if (isKnownWebSocketServerImplementation) {
927
+ clientTransport = this.options.webSocketServer.type;
928
+ } else {
929
+ clientTransport = "ws";
930
+ }
931
+ } else {
932
+ clientTransport = "ws";
933
+ }
689
934
 
690
- signals.forEach((signal) => {
691
- process.on(signal, () => {
692
- this.stopCallback(() => {
693
- // eslint-disable-next-line no-process-exit
694
- process.exit();
695
- });
696
- });
697
- });
935
+ switch (typeof clientTransport) {
936
+ case "string":
937
+ // could be 'sockjs', 'ws', or a path that should be required
938
+ if (clientTransport === "sockjs") {
939
+ ClientImplementation = require.resolve(
940
+ "../client/clients/SockJSClient"
941
+ );
942
+ } else if (clientTransport === "ws") {
943
+ ClientImplementation = require.resolve(
944
+ "../client/clients/WebSocketClient"
945
+ );
946
+ } else {
947
+ try {
948
+ // eslint-disable-next-line import/no-dynamic-require
949
+ ClientImplementation = require.resolve(clientTransport);
950
+ } catch (e) {
951
+ clientImplementationFound = false;
952
+ }
953
+ }
954
+ break;
955
+ default:
956
+ clientImplementationFound = false;
698
957
  }
699
958
 
700
- // Proxy WebSocket without the initial http request
701
- // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
702
- // eslint-disable-next-line func-names
703
- this.webSocketProxies.forEach(function (webSocketProxy) {
704
- this.server.on("upgrade", webSocketProxy.upgrade);
705
- }, this);
959
+ if (!clientImplementationFound) {
960
+ throw new Error(
961
+ `${
962
+ !isKnownWebSocketServerImplementation
963
+ ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. "
964
+ : ""
965
+ }client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class `
966
+ );
967
+ }
968
+
969
+ return ClientImplementation;
706
970
  }
707
971
 
708
- applyDevServerPlugin() {
709
- const DevServerPlugin = require("./utils/DevServerPlugin");
972
+ getServerTransport() {
973
+ let implementation;
974
+ let implementationFound = true;
975
+
976
+ switch (typeof this.options.webSocketServer.type) {
977
+ case "string":
978
+ // Could be 'sockjs', in the future 'ws', or a path that should be required
979
+ if (this.options.webSocketServer.type === "sockjs") {
980
+ implementation = require("./servers/SockJSServer");
981
+ } else if (this.options.webSocketServer.type === "ws") {
982
+ implementation = require("./servers/WebsocketServer");
983
+ } else {
984
+ try {
985
+ // eslint-disable-next-line import/no-dynamic-require
986
+ implementation = require(this.options.webSocketServer.type);
987
+ } catch (error) {
988
+ implementationFound = false;
989
+ }
990
+ }
991
+ break;
992
+ case "function":
993
+ implementation = this.options.webSocketServer.type;
994
+ break;
995
+ default:
996
+ implementationFound = false;
997
+ }
710
998
 
711
- const compilers = this.compiler.compilers || [this.compiler];
999
+ if (!implementationFound) {
1000
+ throw new Error(
1001
+ "webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to " +
1002
+ "a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) " +
1003
+ "via require.resolve(...), or the class itself which extends BaseServer"
1004
+ );
1005
+ }
712
1006
 
713
- // eslint-disable-next-line no-shadow
714
- compilers.forEach((compiler) => {
715
- new DevServerPlugin(this.options).apply(compiler);
716
- });
1007
+ return implementation;
717
1008
  }
718
1009
 
719
1010
  setupProgressPlugin() {
@@ -744,6 +1035,76 @@ class Server {
744
1035
  }).apply(this.compiler);
745
1036
  }
746
1037
 
1038
+ async initialize() {
1039
+ const compilers = this.compiler.compilers || [this.compiler];
1040
+
1041
+ // eslint-disable-next-line no-shadow
1042
+ compilers.forEach((compiler) => {
1043
+ this.addAdditionalEntries(compiler);
1044
+
1045
+ const webpack = compiler.webpack || require("webpack");
1046
+
1047
+ const providePlugin = new webpack.ProvidePlugin({
1048
+ __webpack_dev_server_client__: this.getClientTransport(),
1049
+ });
1050
+
1051
+ providePlugin.apply(compiler);
1052
+
1053
+ // TODO remove after drop webpack v4 support
1054
+ compiler.options.plugins = compiler.options.plugins || [];
1055
+
1056
+ if (this.options.hot) {
1057
+ const HMRPluginExists = compiler.options.plugins.find(
1058
+ (p) => p.constructor === webpack.HotModuleReplacementPlugin
1059
+ );
1060
+
1061
+ if (HMRPluginExists) {
1062
+ this.logger.warn(
1063
+ `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
1064
+ );
1065
+ } else {
1066
+ // apply the HMR plugin
1067
+ const plugin = new webpack.HotModuleReplacementPlugin();
1068
+ plugin.apply(compiler);
1069
+ }
1070
+ }
1071
+ });
1072
+
1073
+ if (this.options.client && this.options.client.progress) {
1074
+ this.setupProgressPlugin();
1075
+ }
1076
+
1077
+ this.setupHooks();
1078
+ this.setupApp();
1079
+ this.setupHostHeaderCheck();
1080
+ this.setupDevMiddleware();
1081
+ // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1082
+ this.setupBuiltInRoutes();
1083
+ this.setupWatchFiles();
1084
+ this.setupFeatures();
1085
+ this.createServer();
1086
+
1087
+ if (this.options.setupExitSignals) {
1088
+ const signals = ["SIGINT", "SIGTERM"];
1089
+
1090
+ signals.forEach((signal) => {
1091
+ process.on(signal, () => {
1092
+ this.stopCallback(() => {
1093
+ // eslint-disable-next-line no-process-exit
1094
+ process.exit();
1095
+ });
1096
+ });
1097
+ });
1098
+ }
1099
+
1100
+ // Proxy WebSocket without the initial http request
1101
+ // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1102
+ // eslint-disable-next-line func-names
1103
+ this.webSocketProxies.forEach(function (webSocketProxy) {
1104
+ this.server.on("upgrade", webSocketProxy.upgrade);
1105
+ }, this);
1106
+ }
1107
+
747
1108
  setupApp() {
748
1109
  // Init express server
749
1110
  // eslint-disable-next-line new-cap
@@ -810,7 +1171,7 @@ class Server {
810
1171
  app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
811
1172
  res.setHeader("Content-Type", "application/javascript");
812
1173
 
813
- const { createReadStream } = require("graceful-fs");
1174
+ const { createReadStream } = fs;
814
1175
  const clientPath = path.join(__dirname, "..", "client");
815
1176
 
816
1177
  createReadStream(
@@ -879,15 +1240,17 @@ class Server {
879
1240
  const { createProxyMiddleware } = require("http-proxy-middleware");
880
1241
 
881
1242
  const getProxyMiddleware = (proxyConfig) => {
882
- const context = proxyConfig.context || proxyConfig.path;
883
-
884
- // It is possible to use the `bypass` method without a `target`.
1243
+ // It is possible to use the `bypass` method without a `target` or `router`.
885
1244
  // However, the proxy middleware has no use in this case, and will fail to instantiate.
886
- if (context) {
1245
+ if (proxyConfig.target) {
1246
+ const context = proxyConfig.context || proxyConfig.path;
1247
+
887
1248
  return createProxyMiddleware(context, proxyConfig);
888
1249
  }
889
1250
 
890
- return createProxyMiddleware(proxyConfig);
1251
+ if (proxyConfig.router) {
1252
+ return createProxyMiddleware(proxyConfig);
1253
+ }
891
1254
  };
892
1255
  /**
893
1256
  * Assume a proxy configuration specified as:
@@ -913,9 +1276,7 @@ class Server {
913
1276
  ? proxyConfigOrCallback()
914
1277
  : proxyConfigOrCallback;
915
1278
 
916
- if (!proxyConfig.bypass) {
917
- proxyMiddleware = getProxyMiddleware(proxyConfig);
918
- }
1279
+ proxyMiddleware = getProxyMiddleware(proxyConfig);
919
1280
 
920
1281
  if (proxyConfig.ws) {
921
1282
  this.webSocketProxies.push(proxyMiddleware);
@@ -1132,7 +1493,9 @@ class Server {
1132
1493
  runnableFeatures.push("staticServeIndex", "staticWatch");
1133
1494
  }
1134
1495
 
1135
- runnableFeatures.push("magicHtml");
1496
+ if (this.options.magicHtml) {
1497
+ runnableFeatures.push("magicHtml");
1498
+ }
1136
1499
 
1137
1500
  if (this.options.onAfterSetupMiddleware) {
1138
1501
  runnableFeatures.push("onAfterSetupMiddleware");
@@ -1182,46 +1545,8 @@ class Server {
1182
1545
  });
1183
1546
  }
1184
1547
 
1185
- getWebSocketServerImplementation() {
1186
- let implementation;
1187
- let implementationFound = true;
1188
-
1189
- switch (typeof this.options.webSocketServer.type) {
1190
- case "string":
1191
- // Could be 'sockjs', in the future 'ws', or a path that should be required
1192
- if (this.options.webSocketServer.type === "sockjs") {
1193
- implementation = require("./servers/SockJSServer");
1194
- } else if (this.options.webSocketServer.type === "ws") {
1195
- implementation = require("./servers/WebsocketServer");
1196
- } else {
1197
- try {
1198
- // eslint-disable-next-line import/no-dynamic-require
1199
- implementation = require(this.options.webSocketServer.type);
1200
- } catch (error) {
1201
- implementationFound = false;
1202
- }
1203
- }
1204
- break;
1205
- case "function":
1206
- implementation = this.options.webSocketServer.type;
1207
- break;
1208
- default:
1209
- implementationFound = false;
1210
- }
1211
-
1212
- if (!implementationFound) {
1213
- throw new Error(
1214
- "webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to " +
1215
- "a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) " +
1216
- "via require.resolve(...), or the class itself which extends BaseServer"
1217
- );
1218
- }
1219
-
1220
- return implementation;
1221
- }
1222
-
1223
1548
  createWebSocketServer() {
1224
- this.webSocketServer = new (this.getWebSocketServerImplementation())(this);
1549
+ this.webSocketServer = new (this.getServerTransport())(this);
1225
1550
  this.webSocketServer.implementation.on("connection", (client, request) => {
1226
1551
  const headers =
1227
1552
  // eslint-disable-next-line no-nested-ternary
@@ -1763,7 +2088,7 @@ class Server {
1763
2088
  // chmod 666 (rw rw rw)
1764
2089
  const READ_WRITE = 438;
1765
2090
 
1766
- fs.chmodSync(this.options.ipc, READ_WRITE);
2091
+ await fs.promises.chmod(this.options.ipc, READ_WRITE);
1767
2092
  }
1768
2093
 
1769
2094
  if (this.options.webSocketServer) {
package/lib/options.json CHANGED
@@ -71,12 +71,14 @@
71
71
  },
72
72
  "ClientLogging": {
73
73
  "enum": ["none", "error", "warn", "info", "log", "verbose"],
74
- "decription": "Allows to set log level in the browser."
74
+ "decription": "Allows to set log level in the browser.",
75
+ "link": "https://webpack.js.org/configuration/dev-server/#logging"
75
76
  },
76
77
  "ClientOverlay": {
77
78
  "anyOf": [
78
79
  {
79
80
  "description": "Enables a full-screen overlay in the browser when there are compiler errors or warnings.",
81
+ "link": "https://webpack.js.org/configuration/dev-server/#overlay",
80
82
  "type": "boolean"
81
83
  },
82
84
  {
@@ -97,6 +99,7 @@
97
99
  },
98
100
  "ClientProgress": {
99
101
  "description": "Prints compilation progress in percentage in the browser.",
102
+ "link": "https://webpack.js.org/configuration/dev-server/#progress",
100
103
  "type": "boolean"
101
104
  },
102
105
  "ClientWebSocketTransport": {
@@ -108,7 +111,8 @@
108
111
  "$ref": "#/definitions/ClientWebSocketTransportString"
109
112
  }
110
113
  ],
111
- "description": "Allows to set custom web socket transport to communicate with dev server."
114
+ "description": "Allows to set custom web socket transport to communicate with dev server.",
115
+ "link": "https://webpack.js.org/configuration/dev-server/#websockettransport"
112
116
  },
113
117
  "ClientWebSocketTransportEnum": {
114
118
  "enum": ["sockjs", "ws"]
@@ -119,6 +123,7 @@
119
123
  },
120
124
  "ClientWebSocketURL": {
121
125
  "description": "Allows to specify URL to web socket server (useful when you're proxying dev server and client script does not always know where to connect to).",
126
+ "link": "https://webpack.js.org/configuration/dev-server/#websocketurl",
122
127
  "anyOf": [
123
128
  {
124
129
  "type": "string",
@@ -326,6 +331,11 @@
326
331
  "description": "Enables reload/refresh the page(s) when file changes are detected (enabled by default).",
327
332
  "link": "https://webpack.js.org/configuration/dev-server/#devserverlivereload"
328
333
  },
334
+ "MagicHTML": {
335
+ "type": "boolean",
336
+ "description": "Enables/Disables magic HTML routes (enabled by default).",
337
+ "link": "https://webpack.js.org/configuration/dev-server/#devservermagichtml"
338
+ },
329
339
  "OnAfterSetupMiddleware": {
330
340
  "instanceof": "Function",
331
341
  "description": "Provides the ability to execute a custom function and apply custom middleware(s) after all other middlewares.",
@@ -514,10 +524,12 @@
514
524
  "directory": {
515
525
  "type": "string",
516
526
  "minLength": 1,
517
- "description": "Directory for static contents."
527
+ "description": "Directory for static contents.",
528
+ "link": "https://webpack.js.org/configuration/dev-server/#directory"
518
529
  },
519
530
  "staticOptions": {
520
531
  "type": "object",
532
+ "link": "https://webpack.js.org/configuration/dev-server/#staticoptions",
521
533
  "additionalProperties": true
522
534
  },
523
535
  "publicPath": {
@@ -533,7 +545,8 @@
533
545
  "type": "string"
534
546
  }
535
547
  ],
536
- "description": "The static files will be available in the browser under this public path."
548
+ "description": "The static files will be available in the browser under this public path.",
549
+ "link": "https://webpack.js.org/configuration/dev-server/#publicpath"
537
550
  },
538
551
  "serveIndex": {
539
552
  "anyOf": [
@@ -545,7 +558,8 @@
545
558
  "additionalProperties": true
546
559
  }
547
560
  ],
548
- "description": "Tells dev server to use serveIndex middleware when enabled."
561
+ "description": "Tells dev server to use serveIndex middleware when enabled.",
562
+ "link": "https://webpack.js.org/configuration/dev-server/#serveindex"
549
563
  },
550
564
  "watch": {
551
565
  "anyOf": [
@@ -558,7 +572,8 @@
558
572
  "link": "https://github.com/paulmillr/chokidar#api"
559
573
  }
560
574
  ],
561
- "description": "Watches for files in static content directory."
575
+ "description": "Watches for files in static content directory.",
576
+ "link": "https://webpack.js.org/configuration/dev-server/#watch"
562
577
  }
563
578
  }
564
579
  },
@@ -719,6 +734,9 @@
719
734
  "liveReload": {
720
735
  "$ref": "#/definitions/LiveReload"
721
736
  },
737
+ "magicHtml": {
738
+ "$ref": "#/definitions/MagicHTML"
739
+ },
722
740
  "onAfterSetupMiddleware": {
723
741
  "$ref": "#/definitions/OnAfterSetupMiddleware"
724
742
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack-dev-server",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Serves a webpack app. Updates the browser on changes.",
5
5
  "bin": "bin/webpack-dev-server.js",
6
6
  "main": "lib/Server.js",
@@ -1,352 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * An Entry, it can be of type string or string[] or Object<string | string[],string>
5
- * @typedef {(string[] | string | Object<string | string[],string>)} Entry
6
- */
7
-
8
- class DevServerPlugin {
9
- /**
10
- * @param {Object} options - Dev-Server options
11
- */
12
- constructor(options) {
13
- this.options = options;
14
- }
15
-
16
- getWebsocketTransport() {
17
- let ClientImplementation;
18
- let clientImplementationFound = true;
19
-
20
- const isKnownWebSocketServerImplementation =
21
- this.options.webSocketServer &&
22
- typeof this.options.webSocketServer.type === "string" &&
23
- (this.options.webSocketServer.type === "ws" ||
24
- this.options.webSocketServer.type === "sockjs");
25
-
26
- let clientTransport;
27
-
28
- if (this.options.client) {
29
- if (typeof this.options.client.webSocketTransport !== "undefined") {
30
- clientTransport = this.options.client.webSocketTransport;
31
- } else if (isKnownWebSocketServerImplementation) {
32
- clientTransport = this.options.webSocketServer.type;
33
- } else {
34
- clientTransport = "ws";
35
- }
36
- } else {
37
- clientTransport = "ws";
38
- }
39
-
40
- switch (typeof clientTransport) {
41
- case "string":
42
- // could be 'sockjs', 'ws', or a path that should be required
43
- if (clientTransport === "sockjs") {
44
- ClientImplementation = require.resolve(
45
- "../../client/clients/SockJSClient"
46
- );
47
- } else if (clientTransport === "ws") {
48
- ClientImplementation = require.resolve(
49
- "../../client/clients/WebSocketClient"
50
- );
51
- } else {
52
- try {
53
- // eslint-disable-next-line import/no-dynamic-require
54
- ClientImplementation = require.resolve(clientTransport);
55
- } catch (e) {
56
- clientImplementationFound = false;
57
- }
58
- }
59
- break;
60
- default:
61
- clientImplementationFound = false;
62
- }
63
-
64
- if (!clientImplementationFound) {
65
- throw new Error(
66
- `${
67
- !isKnownWebSocketServerImplementation
68
- ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. "
69
- : ""
70
- }client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class `
71
- );
72
- }
73
-
74
- return ClientImplementation;
75
- }
76
-
77
- /**
78
- * @returns {string}
79
- */
80
- getWebSocketURL() {
81
- const { options } = this;
82
- const searchParams = new URLSearchParams();
83
-
84
- /** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
85
- let protocol;
86
-
87
- // We are proxying dev server and need to specify custom `hostname`
88
- if (typeof options.client.webSocketURL.protocol !== "undefined") {
89
- protocol = options.client.webSocketURL.protocol;
90
- } else {
91
- protocol = options.https ? "wss:" : "ws:";
92
- }
93
-
94
- searchParams.set("protocol", protocol);
95
-
96
- if (typeof options.client.webSocketURL.username !== "undefined") {
97
- searchParams.set("username", options.client.webSocketURL.username);
98
- }
99
-
100
- if (typeof options.client.webSocketURL.password !== "undefined") {
101
- searchParams.set("password", options.client.webSocketURL.password);
102
- }
103
-
104
- /** @type {string} */
105
- let hostname;
106
-
107
- // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
108
- // TODO show warning about this
109
- const isSockJSType = options.webSocketServer.type === "sockjs";
110
-
111
- // We are proxying dev server and need to specify custom `hostname`
112
- if (typeof options.client.webSocketURL.hostname !== "undefined") {
113
- hostname = options.client.webSocketURL.hostname;
114
- }
115
- // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
116
- else if (
117
- typeof options.webSocketServer.options.host !== "undefined" &&
118
- !isSockJSType
119
- ) {
120
- hostname = options.webSocketServer.options.host;
121
- }
122
- // The `host` option is specified
123
- else if (typeof this.options.host !== "undefined") {
124
- hostname = this.options.host;
125
- }
126
- // The `port` option is not specified
127
- else {
128
- hostname = "0.0.0.0";
129
- }
130
-
131
- searchParams.set("hostname", hostname);
132
-
133
- /** @type {number | string} */
134
- let port;
135
-
136
- // We are proxying dev server and need to specify custom `port`
137
- if (typeof options.client.webSocketURL.port !== "undefined") {
138
- port = options.client.webSocketURL.port;
139
- }
140
- // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
141
- else if (
142
- typeof options.webSocketServer.options.port !== "undefined" &&
143
- !isSockJSType
144
- ) {
145
- port = options.webSocketServer.options.port;
146
- }
147
- // The `port` option is specified
148
- else if (typeof options.port === "number") {
149
- port = options.port;
150
- }
151
- // The `port` option is specified using `string`
152
- else if (typeof options.port === "string" && options.port !== "auto") {
153
- port = Number(options.port);
154
- }
155
- // The `port` option is not specified or set to `auto`
156
- else {
157
- port = "0";
158
- }
159
-
160
- searchParams.set("port", String(port));
161
-
162
- /** @type {string} */
163
- let pathname = "";
164
-
165
- // We are proxying dev server and need to specify custom `pathname`
166
- if (typeof options.client.webSocketURL.pathname !== "undefined") {
167
- pathname = options.client.webSocketURL.pathname;
168
- }
169
- // Web socket server works on custom `path`
170
- else if (
171
- typeof options.webSocketServer.options.prefix !== "undefined" ||
172
- typeof options.webSocketServer.options.path !== "undefined"
173
- ) {
174
- pathname =
175
- options.webSocketServer.options.prefix ||
176
- options.webSocketServer.options.path;
177
- }
178
-
179
- searchParams.set("pathname", pathname);
180
-
181
- if (typeof options.client.logging !== "undefined") {
182
- searchParams.set("logging", options.client.logging);
183
- }
184
-
185
- return searchParams.toString();
186
- }
187
-
188
- /**
189
- * @returns {string}
190
- */
191
- getClientEntry() {
192
- /** @type {string} */
193
- const webSocketURL = this.options.webSocketServer
194
- ? this.getWebSocketURL()
195
- : "";
196
-
197
- return `${require.resolve("../../client/index.js")}?${webSocketURL}`;
198
- }
199
-
200
- getHotEntry() {
201
- const { options } = this;
202
-
203
- /** @type {(string[] | string)} */
204
- let hotEntry;
205
-
206
- if (options.hot === "only") {
207
- hotEntry = require.resolve("webpack/hot/only-dev-server");
208
- } else if (options.hot) {
209
- hotEntry = require.resolve("webpack/hot/dev-server");
210
- }
211
-
212
- return hotEntry;
213
- }
214
-
215
- /**
216
- * @param {Object} compilerOptions
217
- * @returns {boolean}
218
- */
219
- // eslint-disable-next-line class-methods-use-this
220
- isWebTarget(compilerOptions) {
221
- return compilerOptions.externalsPresets
222
- ? compilerOptions.externalsPresets.web
223
- : [
224
- "web",
225
- "webworker",
226
- "electron-preload",
227
- "electron-renderer",
228
- "node-webkit",
229
- // eslint-disable-next-line no-undefined
230
- undefined,
231
- null,
232
- ].includes(compilerOptions.target);
233
- }
234
-
235
- /**
236
- * Apply the plugin
237
- * @param {Object} compiler the compiler instance
238
- * @returns {void}
239
- */
240
- apply(compiler) {
241
- /**
242
- *
243
- * Description of the option for checkInject method
244
- * @typedef {Function} checkInjectOptionsParam
245
- * @param {Object} _config - compilerConfig
246
- * @return {Boolean}
247
- */
248
-
249
- const additionalEntries = [];
250
-
251
- // TODO maybe empty empty client
252
- if (this.options.client && this.isWebTarget(compiler.options)) {
253
- const clientEntry = this.getClientEntry();
254
-
255
- additionalEntries.push(clientEntry);
256
- }
257
-
258
- if (this.options.hot) {
259
- const hotEntry = this.getHotEntry();
260
-
261
- additionalEntries.push(hotEntry);
262
- }
263
-
264
- const webpack = compiler.webpack || require("webpack");
265
-
266
- // use a hook to add entries if available
267
- if (typeof webpack.EntryPlugin !== "undefined") {
268
- for (const additionalEntry of additionalEntries) {
269
- new webpack.EntryPlugin(compiler.context, additionalEntry, {
270
- // eslint-disable-next-line no-undefined
271
- name: undefined,
272
- }).apply(compiler);
273
- }
274
- } else {
275
- /**
276
- * prependEntry Method for webpack 4
277
- * @param {Entry} originalEntry
278
- * @param {Entry} newAdditionalEntries
279
- * @returns {Entry}
280
- */
281
- const prependEntry = (originalEntry, newAdditionalEntries) => {
282
- if (typeof originalEntry === "function") {
283
- return () =>
284
- Promise.resolve(originalEntry()).then((entry) =>
285
- prependEntry(entry, newAdditionalEntries)
286
- );
287
- }
288
-
289
- if (
290
- typeof originalEntry === "object" &&
291
- !Array.isArray(originalEntry)
292
- ) {
293
- /** @type {Object<string,string>} */
294
- const clone = {};
295
-
296
- Object.keys(originalEntry).forEach((key) => {
297
- // entry[key] should be a string here
298
- const entryDescription = originalEntry[key];
299
-
300
- clone[key] = prependEntry(entryDescription, newAdditionalEntries);
301
- });
302
-
303
- return clone;
304
- }
305
-
306
- // in this case, entry is a string or an array.
307
- // make sure that we do not add duplicates.
308
- /** @type {Entry} */
309
- const entriesClone = additionalEntries.slice(0);
310
-
311
- [].concat(originalEntry).forEach((newEntry) => {
312
- if (!entriesClone.includes(newEntry)) {
313
- entriesClone.push(newEntry);
314
- }
315
- });
316
-
317
- return entriesClone;
318
- };
319
-
320
- compiler.options.entry = prependEntry(
321
- compiler.options.entry || "./src",
322
- additionalEntries
323
- );
324
- compiler.hooks.entryOption.call(
325
- compiler.options.context,
326
- compiler.options.entry
327
- );
328
- }
329
-
330
- const providePlugin = new webpack.ProvidePlugin({
331
- __webpack_dev_server_client__: this.getWebsocketTransport(),
332
- });
333
-
334
- providePlugin.apply(compiler);
335
-
336
- compiler.options.plugins = compiler.options.plugins || [];
337
-
338
- if (
339
- this.options.hot &&
340
- !compiler.options.plugins.find(
341
- (p) => p.constructor === webpack.HotModuleReplacementPlugin
342
- )
343
- ) {
344
- // apply the HMR plugin, if it didn't exist before.
345
- const plugin = new webpack.HotModuleReplacementPlugin();
346
-
347
- plugin.apply(compiler);
348
- }
349
- }
350
- }
351
-
352
- module.exports = DevServerPlugin;