webpack-dev-server 4.0.0 → 4.2.1

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
@@ -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(options.allowedHosts) &&
428
+ options.allowedHosts.includes("all")
429
+ ) {
430
+ options.allowedHosts = "all";
431
+ }
206
432
 
207
433
  if (typeof options.bonjour === "undefined") {
208
434
  options.bonjour = false;
@@ -291,24 +517,39 @@ class Server {
291
517
 
292
518
  // https option
293
519
  if (options.https) {
294
- for (const property of ["cacert", "pfx", "key", "cert"]) {
520
+ // TODO remove the `cacert` option in favor `ca` in the next major release
521
+ for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) {
522
+ if (typeof options.https[property] === "undefined") {
523
+ // eslint-disable-next-line no-continue
524
+ continue;
525
+ }
526
+
295
527
  const value = options.https[property];
296
- const isBuffer = value instanceof Buffer;
528
+ const readFile = (item) => {
529
+ if (
530
+ Buffer.isBuffer(item) ||
531
+ (typeof item === "object" && item !== null && !Array.isArray(item))
532
+ ) {
533
+ return item;
534
+ }
297
535
 
298
- if (value && !isBuffer) {
299
- let stats = null;
536
+ if (item) {
537
+ let stats = null;
300
538
 
301
- try {
302
- stats = fs.lstatSync(fs.realpathSync(value)).isFile();
303
- } catch (error) {
304
- // ignore error
539
+ try {
540
+ stats = fs.lstatSync(fs.realpathSync(item)).isFile();
541
+ } catch (error) {
542
+ // Ignore error
543
+ }
544
+
545
+ // It is file
546
+ return stats ? fs.readFileSync(item) : item;
305
547
  }
548
+ };
306
549
 
307
- // It is file
308
- options.https[property] = stats
309
- ? fs.readFileSync(path.resolve(value))
310
- : value;
311
- }
550
+ options.https[property] = Array.isArray(value)
551
+ ? value.map((item) => readFile(item))
552
+ : readFile(value);
312
553
  }
313
554
 
314
555
  let fakeCert;
@@ -316,11 +557,18 @@ class Server {
316
557
  if (!options.https.key || !options.https.cert) {
317
558
  const certificateDir = Server.findCacheDir();
318
559
  const certificatePath = path.join(certificateDir, "server.pem");
319
- let certificateExists = fs.existsSync(certificatePath);
560
+ let certificateExists;
561
+
562
+ try {
563
+ const certificate = await fs.promises.stat(certificatePath);
564
+ certificateExists = certificate.isFile();
565
+ } catch {
566
+ certificateExists = false;
567
+ }
320
568
 
321
569
  if (certificateExists) {
322
570
  const certificateTtl = 1000 * 60 * 60 * 24;
323
- const certificateStat = fs.statSync(certificatePath);
571
+ const certificateStat = await fs.promises.stat(certificatePath);
324
572
 
325
573
  const now = new Date();
326
574
 
@@ -332,7 +580,7 @@ class Server {
332
580
  "SSL Certificate is more than 30 days old. Removing..."
333
581
  );
334
582
 
335
- del.sync([certificatePath], { force: true });
583
+ await del([certificatePath], { force: true });
336
584
 
337
585
  certificateExists = false;
338
586
  }
@@ -405,17 +653,34 @@ class Server {
405
653
  ],
406
654
  });
407
655
 
408
- fs.mkdirSync(certificateDir, { recursive: true });
409
- fs.writeFileSync(certificatePath, pems.private + pems.cert, {
410
- encoding: "utf8",
411
- });
656
+ await fs.promises.mkdir(certificateDir, { recursive: true });
657
+
658
+ await fs.promises.writeFile(
659
+ certificatePath,
660
+ pems.private + pems.cert,
661
+ {
662
+ encoding: "utf8",
663
+ }
664
+ );
412
665
  }
413
666
 
414
- fakeCert = fs.readFileSync(certificatePath);
667
+ fakeCert = await fs.promises.readFile(certificatePath);
415
668
 
416
669
  this.logger.info(`SSL certificate: ${certificatePath}`);
417
670
  }
418
671
 
672
+ if (options.https.cacert) {
673
+ if (options.https.ca) {
674
+ this.logger.warn(
675
+ "Do not specify 'https.ca' and 'https.cacert' options together, the 'https.ca' option will be used."
676
+ );
677
+ } else {
678
+ options.https.ca = options.https.cacert;
679
+ }
680
+
681
+ delete options.https.cacert;
682
+ }
683
+
419
684
  options.https.key = options.https.key || fakeCert;
420
685
  options.https.cert = options.https.cert || fakeCert;
421
686
  }
@@ -431,6 +696,9 @@ class Server {
431
696
  options.liveReload =
432
697
  typeof options.liveReload !== "undefined" ? options.liveReload : true;
433
698
 
699
+ options.magicHtml =
700
+ typeof options.magicHtml !== "undefined" ? options.magicHtml : true;
701
+
434
702
  // https://github.com/webpack/webpack-dev-server/issues/1990
435
703
  const defaultOpenOptions = { wait: false };
436
704
  const getOpenItemsFromObject = ({ target, ...rest }) => {
@@ -667,53 +935,103 @@ class Server {
667
935
  }
668
936
  }
669
937
 
670
- async initialize() {
671
- this.applyDevServerPlugin();
938
+ getClientTransport() {
939
+ let ClientImplementation;
940
+ let clientImplementationFound = true;
672
941
 
673
- if (this.options.client && this.options.client.progress) {
674
- this.setupProgressPlugin();
675
- }
942
+ const isKnownWebSocketServerImplementation =
943
+ this.options.webSocketServer &&
944
+ typeof this.options.webSocketServer.type === "string" &&
945
+ (this.options.webSocketServer.type === "ws" ||
946
+ this.options.webSocketServer.type === "sockjs");
676
947
 
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();
948
+ let clientTransport;
686
949
 
687
- if (this.options.setupExitSignals) {
688
- const signals = ["SIGINT", "SIGTERM"];
950
+ if (this.options.client) {
951
+ if (typeof this.options.client.webSocketTransport !== "undefined") {
952
+ clientTransport = this.options.client.webSocketTransport;
953
+ } else if (isKnownWebSocketServerImplementation) {
954
+ clientTransport = this.options.webSocketServer.type;
955
+ } else {
956
+ clientTransport = "ws";
957
+ }
958
+ } else {
959
+ clientTransport = "ws";
960
+ }
689
961
 
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
- });
962
+ switch (typeof clientTransport) {
963
+ case "string":
964
+ // could be 'sockjs', 'ws', or a path that should be required
965
+ if (clientTransport === "sockjs") {
966
+ ClientImplementation = require.resolve(
967
+ "../client/clients/SockJSClient"
968
+ );
969
+ } else if (clientTransport === "ws") {
970
+ ClientImplementation = require.resolve(
971
+ "../client/clients/WebSocketClient"
972
+ );
973
+ } else {
974
+ try {
975
+ // eslint-disable-next-line import/no-dynamic-require
976
+ ClientImplementation = require.resolve(clientTransport);
977
+ } catch (e) {
978
+ clientImplementationFound = false;
979
+ }
980
+ }
981
+ break;
982
+ default:
983
+ clientImplementationFound = false;
698
984
  }
699
985
 
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);
986
+ if (!clientImplementationFound) {
987
+ throw new Error(
988
+ `${
989
+ !isKnownWebSocketServerImplementation
990
+ ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. "
991
+ : ""
992
+ }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 `
993
+ );
994
+ }
995
+
996
+ return ClientImplementation;
706
997
  }
707
998
 
708
- applyDevServerPlugin() {
709
- const DevServerPlugin = require("./utils/DevServerPlugin");
999
+ getServerTransport() {
1000
+ let implementation;
1001
+ let implementationFound = true;
710
1002
 
711
- const compilers = this.compiler.compilers || [this.compiler];
1003
+ switch (typeof this.options.webSocketServer.type) {
1004
+ case "string":
1005
+ // Could be 'sockjs', in the future 'ws', or a path that should be required
1006
+ if (this.options.webSocketServer.type === "sockjs") {
1007
+ implementation = require("./servers/SockJSServer");
1008
+ } else if (this.options.webSocketServer.type === "ws") {
1009
+ implementation = require("./servers/WebsocketServer");
1010
+ } else {
1011
+ try {
1012
+ // eslint-disable-next-line import/no-dynamic-require
1013
+ implementation = require(this.options.webSocketServer.type);
1014
+ } catch (error) {
1015
+ implementationFound = false;
1016
+ }
1017
+ }
1018
+ break;
1019
+ case "function":
1020
+ implementation = this.options.webSocketServer.type;
1021
+ break;
1022
+ default:
1023
+ implementationFound = false;
1024
+ }
712
1025
 
713
- // eslint-disable-next-line no-shadow
714
- compilers.forEach((compiler) => {
715
- new DevServerPlugin(this.options).apply(compiler);
716
- });
1026
+ if (!implementationFound) {
1027
+ throw new Error(
1028
+ "webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to " +
1029
+ "a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) " +
1030
+ "via require.resolve(...), or the class itself which extends BaseServer"
1031
+ );
1032
+ }
1033
+
1034
+ return implementation;
717
1035
  }
718
1036
 
719
1037
  setupProgressPlugin() {
@@ -744,6 +1062,76 @@ class Server {
744
1062
  }).apply(this.compiler);
745
1063
  }
746
1064
 
1065
+ async initialize() {
1066
+ const compilers = this.compiler.compilers || [this.compiler];
1067
+
1068
+ // eslint-disable-next-line no-shadow
1069
+ compilers.forEach((compiler) => {
1070
+ this.addAdditionalEntries(compiler);
1071
+
1072
+ const webpack = compiler.webpack || require("webpack");
1073
+
1074
+ const providePlugin = new webpack.ProvidePlugin({
1075
+ __webpack_dev_server_client__: this.getClientTransport(),
1076
+ });
1077
+
1078
+ providePlugin.apply(compiler);
1079
+
1080
+ // TODO remove after drop webpack v4 support
1081
+ compiler.options.plugins = compiler.options.plugins || [];
1082
+
1083
+ if (this.options.hot) {
1084
+ const HMRPluginExists = compiler.options.plugins.find(
1085
+ (p) => p.constructor === webpack.HotModuleReplacementPlugin
1086
+ );
1087
+
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);
1096
+ }
1097
+ }
1098
+ });
1099
+
1100
+ if (this.options.client && this.options.client.progress) {
1101
+ this.setupProgressPlugin();
1102
+ }
1103
+
1104
+ this.setupHooks();
1105
+ this.setupApp();
1106
+ this.setupHostHeaderCheck();
1107
+ this.setupDevMiddleware();
1108
+ // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1109
+ this.setupBuiltInRoutes();
1110
+ this.setupWatchFiles();
1111
+ this.setupFeatures();
1112
+ this.createServer();
1113
+
1114
+ if (this.options.setupExitSignals) {
1115
+ const signals = ["SIGINT", "SIGTERM"];
1116
+
1117
+ signals.forEach((signal) => {
1118
+ process.on(signal, () => {
1119
+ this.stopCallback(() => {
1120
+ // eslint-disable-next-line no-process-exit
1121
+ process.exit();
1122
+ });
1123
+ });
1124
+ });
1125
+ }
1126
+
1127
+ // Proxy WebSocket without the initial http request
1128
+ // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1129
+ // eslint-disable-next-line func-names
1130
+ this.webSocketProxies.forEach(function (webSocketProxy) {
1131
+ this.server.on("upgrade", webSocketProxy.upgrade);
1132
+ }, this);
1133
+ }
1134
+
747
1135
  setupApp() {
748
1136
  // Init express server
749
1137
  // eslint-disable-next-line new-cap
@@ -762,26 +1150,18 @@ class Server {
762
1150
  }
763
1151
 
764
1152
  setupHooks() {
765
- const addHooks = (compiler) => {
766
- compiler.hooks.invalid.tap("webpack-dev-server", () => {
767
- if (this.webSocketServer) {
768
- this.sendMessage(this.webSocketServer.clients, "invalid");
769
- }
770
- });
771
- compiler.hooks.done.tap("webpack-dev-server", (stats) => {
772
- if (this.webSocketServer) {
773
- this.sendStats(this.webSocketServer.clients, this.getStats(stats));
774
- }
775
-
776
- this.stats = stats;
777
- });
778
- };
1153
+ this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
1154
+ if (this.webSocketServer) {
1155
+ this.sendMessage(this.webSocketServer.clients, "invalid");
1156
+ }
1157
+ });
1158
+ this.compiler.hooks.done.tap("webpack-dev-server", (stats) => {
1159
+ if (this.webSocketServer) {
1160
+ this.sendStats(this.webSocketServer.clients, this.getStats(stats));
1161
+ }
779
1162
 
780
- if (this.compiler.compilers) {
781
- this.compiler.compilers.forEach(addHooks);
782
- } else {
783
- addHooks(this.compiler);
784
- }
1163
+ this.stats = stats;
1164
+ });
785
1165
  }
786
1166
 
787
1167
  setupHostHeaderCheck() {
@@ -810,7 +1190,7 @@ class Server {
810
1190
  app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
811
1191
  res.setHeader("Content-Type", "application/javascript");
812
1192
 
813
- const { createReadStream } = require("graceful-fs");
1193
+ const { createReadStream } = fs;
814
1194
  const clientPath = path.join(__dirname, "..", "client");
815
1195
 
816
1196
  createReadStream(
@@ -879,15 +1259,17 @@ class Server {
879
1259
  const { createProxyMiddleware } = require("http-proxy-middleware");
880
1260
 
881
1261
  const getProxyMiddleware = (proxyConfig) => {
882
- const context = proxyConfig.context || proxyConfig.path;
883
-
884
- // It is possible to use the `bypass` method without a `target`.
1262
+ // It is possible to use the `bypass` method without a `target` or `router`.
885
1263
  // However, the proxy middleware has no use in this case, and will fail to instantiate.
886
- if (context) {
1264
+ if (proxyConfig.target) {
1265
+ const context = proxyConfig.context || proxyConfig.path;
1266
+
887
1267
  return createProxyMiddleware(context, proxyConfig);
888
1268
  }
889
1269
 
890
- return createProxyMiddleware(proxyConfig);
1270
+ if (proxyConfig.router) {
1271
+ return createProxyMiddleware(proxyConfig);
1272
+ }
891
1273
  };
892
1274
  /**
893
1275
  * Assume a proxy configuration specified as:
@@ -913,9 +1295,7 @@ class Server {
913
1295
  ? proxyConfigOrCallback()
914
1296
  : proxyConfigOrCallback;
915
1297
 
916
- if (!proxyConfig.bypass) {
917
- proxyMiddleware = getProxyMiddleware(proxyConfig);
918
- }
1298
+ proxyMiddleware = getProxyMiddleware(proxyConfig);
919
1299
 
920
1300
  if (proxyConfig.ws) {
921
1301
  this.webSocketProxies.push(proxyMiddleware);
@@ -1132,7 +1512,9 @@ class Server {
1132
1512
  runnableFeatures.push("staticServeIndex", "staticWatch");
1133
1513
  }
1134
1514
 
1135
- runnableFeatures.push("magicHtml");
1515
+ if (this.options.magicHtml) {
1516
+ runnableFeatures.push("magicHtml");
1517
+ }
1136
1518
 
1137
1519
  if (this.options.onAfterSetupMiddleware) {
1138
1520
  runnableFeatures.push("onAfterSetupMiddleware");
@@ -1182,46 +1564,8 @@ class Server {
1182
1564
  });
1183
1565
  }
1184
1566
 
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
1567
  createWebSocketServer() {
1224
- this.webSocketServer = new (this.getWebSocketServerImplementation())(this);
1568
+ this.webSocketServer = new (this.getServerTransport())(this);
1225
1569
  this.webSocketServer.implementation.on("connection", (client, request) => {
1226
1570
  const headers =
1227
1571
  // eslint-disable-next-line no-nested-ternary
@@ -1525,6 +1869,10 @@ class Server {
1525
1869
  return false;
1526
1870
  }
1527
1871
 
1872
+ if (/^(file|.+-extension):/i.test(hostHeader)) {
1873
+ return true;
1874
+ }
1875
+
1528
1876
  // use the node url-parser to retrieve the hostname from the host-header.
1529
1877
  const hostname = url.parse(
1530
1878
  // if hostHeader doesn't have scheme, add // for parsing.
@@ -1763,7 +2111,7 @@ class Server {
1763
2111
  // chmod 666 (rw rw rw)
1764
2112
  const READ_WRITE = 438;
1765
2113
 
1766
- fs.chmodSync(this.options.ipc, READ_WRITE);
2114
+ await fs.promises.chmod(this.options.ipc, READ_WRITE);
1767
2115
  }
1768
2116
 
1769
2117
  if (this.options.webSocketServer) {