webpack-dev-server 2.2.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,69 +2,7 @@
2
2
 
3
3
  const optionsSchema = require("./optionsSchema.json");
4
4
 
5
- function OptionsValidationError(validationErrors) {
6
- Error.call(this);
7
- Error.captureStackTrace(this, OptionsValidationError);
8
- this.name = "WebpackDevServerOptionsValidationError";
9
- this.message = `${"Invalid configuration object. " +
10
- "webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n"}${
11
- validationErrors.map(function(err) {
12
- return ` - ${indent(OptionsValidationError.formatValidationError(err), " ", false)}`;
13
- }).join("\n")}`;
14
- this.validationErrors = validationErrors;
15
- }
16
- module.exports = OptionsValidationError;
17
-
18
- OptionsValidationError.prototype = Object.create(Error.prototype);
19
- OptionsValidationError.prototype.constructor = OptionsValidationError;
20
-
21
- OptionsValidationError.formatValidationError = function formatValidationError(err) {
22
- const dataPath = `configuration${err.dataPath}`;
23
- switch(err.keyword) {
24
- case "additionalProperties":
25
- return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${
26
- getSchemaPartText(err.parentSchema)}`;
27
- case "oneOf":
28
- case "anyOf":
29
- case "enum":
30
- return `${dataPath} should be one of these:\n${
31
- getSchemaPartText(err.parentSchema)}`;
32
- case "allOf":
33
- return `${dataPath} should be:\n${
34
- getSchemaPartText(err.parentSchema)}`;
35
- case "type":
36
- switch(err.params.type) {
37
- case "object":
38
- return `${dataPath} should be an object.`;
39
- case "array":
40
- return `${dataPath} should be an array.`;
41
- case "string":
42
- return `${dataPath} should be a string.`;
43
- case "boolean":
44
- return `${dataPath} should be a boolean.`;
45
- case "number":
46
- return `${dataPath} should be a number.`;
47
- }
48
- return `${dataPath} should be ${err.params.type}:\n${
49
- getSchemaPartText(err.parentSchema)}`;
50
- case "instanceof":
51
- return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`;
52
- case "required": // eslint-disable-line no-case-declarations
53
- const missingProperty = err.params.missingProperty.replace(/^\./, "");
54
- return `${dataPath} misses the property '${missingProperty}'.\n${
55
- getSchemaPartText(err.parentSchema, ["properties", missingProperty])}`;
56
- case "minLength":
57
- if(err.params.limit === 1)
58
- return `${dataPath} should not be empty.`;
59
- else
60
- return `${dataPath} ${err.message}`;
61
- default:
62
- return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${
63
- getSchemaPartText(err.parentSchema)}`;
64
- }
65
- }
66
-
67
- function getSchemaPart(path, parents, additionalPath) {
5
+ const getSchemaPart = (path, parents, additionalPath) => {
68
6
  parents = parents || 0;
69
7
  path = path.split("/");
70
8
  path = path.slice(0, path.length - parents);
@@ -79,9 +17,9 @@ function getSchemaPart(path, parents, additionalPath) {
79
17
  schemaPart = inner;
80
18
  }
81
19
  return schemaPart;
82
- }
20
+ };
83
21
 
84
- function getSchemaPartText(schemaPart, additionalPath) {
22
+ const getSchemaPartText = (schemaPart, additionalPath) => {
85
23
  if(additionalPath) {
86
24
  for(let i = 0; i < additionalPath.length; i++) {
87
25
  const inner = schemaPart[additionalPath[i]];
@@ -94,28 +32,56 @@ function getSchemaPartText(schemaPart, additionalPath) {
94
32
  if(schemaPart.description)
95
33
  schemaText += `\n${schemaPart.description}`;
96
34
  return schemaText;
97
- }
35
+ };
98
36
 
99
- function formatSchema(schema, prevSchemas) {
100
- prevSchemas = prevSchemas || [];
37
+ const indent = (str, prefix, firstLine) => {
38
+ if(firstLine) {
39
+ return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
40
+ } else {
41
+ return str.replace(/\n(?!$)/g, `\n${prefix}`);
42
+ }
43
+ };
44
+
45
+ class OptionsValidationError extends Error {
46
+
47
+ constructor(validationErrors) {
48
+ super();
49
+
50
+ if(Error.hasOwnProperty("captureStackTrace")) {
51
+ Error.captureStackTrace(this, this.constructor);
52
+ }
53
+ this.name = "WebpackDevServerOptionsValidationError";
101
54
 
102
- function formatInnerSchema(innerSchema, addSelf) {
103
- if(!addSelf) return formatSchema(innerSchema, prevSchemas);
104
- if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
105
- return formatSchema(innerSchema, prevSchemas.concat(schema));
55
+ this.message = "Invalid configuration object. " +
56
+ "webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n" +
57
+ validationErrors.map(err => " - " + indent(OptionsValidationError.formatValidationError(err), " ", false)).join("\n");
58
+ this.validationErrors = validationErrors;
106
59
  }
107
- switch(schema.type) {
108
- case "string":
60
+
61
+ static formatSchema(schema, prevSchemas) {
62
+ prevSchemas = prevSchemas || [];
63
+
64
+ const formatInnerSchema = (innerSchema, addSelf) => {
65
+ if(!addSelf) return OptionsValidationError.formatSchema(innerSchema, prevSchemas);
66
+ if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
67
+ return OptionsValidationError.formatSchema(innerSchema, prevSchemas.concat(schema));
68
+ };
69
+
70
+ if(schema.type === "string") {
71
+ if(schema.minLength === 1)
72
+ return "non-empty string";
73
+ else if(schema.minLength > 1)
74
+ return `string (min length ${schema.minLength})`;
109
75
  return "string";
110
- case "boolean":
76
+ } else if(schema.type === "boolean") {
111
77
  return "boolean";
112
- case "number":
78
+ } else if(schema.type === "number") {
113
79
  return "number";
114
- case "object":
80
+ } else if(schema.type === "object") {
115
81
  if(schema.properties) {
116
82
  const required = schema.required || [];
117
- return `object { ${Object.keys(schema.properties).map(function(property) {
118
- if(required.indexOf(property) < 0) return `${property}?`;
83
+ return `object { ${Object.keys(schema.properties).map(property => {
84
+ if(required.indexOf(property) < 0) return property + "?";
119
85
  return property;
120
86
  }).concat(schema.additionalProperties ? ["..."] : []).join(", ")} }`;
121
87
  }
@@ -123,31 +89,71 @@ function formatSchema(schema, prevSchemas) {
123
89
  return `object { <key>: ${formatInnerSchema(schema.additionalProperties)} }`;
124
90
  }
125
91
  return "object";
126
- case "array":
92
+ } else if(schema.type === "array") {
127
93
  return `[${formatInnerSchema(schema.items)}]`;
94
+ }
95
+
96
+ switch(schema.instanceof) {
97
+ case "Function":
98
+ return "function";
99
+ case "RegExp":
100
+ return "RegExp";
101
+ }
102
+ if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
103
+ if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
104
+ if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
105
+ if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
106
+ if(schema.enum) return schema.enum.map(item => JSON.stringify(item)).join(" | ");
107
+ return JSON.stringify(schema, 0, 2);
128
108
  }
129
- switch(schema.instanceof) {
130
- case "Function":
131
- return "function";
132
- case "RegExp":
133
- return "RegExp";
134
- }
135
- if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
136
- if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
137
- if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
138
- if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
139
- if(schema.enum) return schema.enum.map(function(item) {
140
- return JSON.stringify(item);
141
- }).join(" | ");
142
- return JSON.stringify(schema, 0, 2);
143
- }
144
109
 
145
- function indent(str, prefix, firstLine) {
146
- if(firstLine) {
147
- return prefix + str.replace(/\n(?!$)/g, `\n${prefix}`);
148
- } else {
149
- return str.replace(/\n(?!$)/g, `\n${prefix}`);
110
+ static formatValidationError(err) {
111
+ const dataPath = `configuration${err.dataPath}`;
112
+ if(err.keyword === "additionalProperties") {
113
+ return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
114
+ } else if(err.keyword === "oneOf" || err.keyword === "anyOf") {
115
+ if(err.children && err.children.length > 0) {
116
+ return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}\n` +
117
+ `Details:\n${err.children.map(err => " * " + indent(OptionsValidationError.formatValidationError(err), " ", false)).join("\n")}`;
118
+ }
119
+ return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
120
+
121
+ } else if(err.keyword === "enum") {
122
+ if(err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
123
+ return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
124
+ }
125
+ return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
126
+ } else if(err.keyword === "allOf") {
127
+ return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
128
+ } else if(err.keyword === "type") {
129
+ switch(err.params.type) {
130
+ case "object":
131
+ return `${dataPath} should be an object.`;
132
+ case "string":
133
+ return `${dataPath} should be a string.`;
134
+ case "boolean":
135
+ return `${dataPath} should be a boolean.`;
136
+ case "number":
137
+ return `${dataPath} should be a number.`;
138
+ case "array":
139
+ return `${dataPath} should be an array:\n${getSchemaPartText(err.parentSchema)}`;
140
+ }
141
+ return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(err.parentSchema)}`;
142
+ } else if(err.keyword === "instanceof") {
143
+ return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`;
144
+ } else if(err.keyword === "required") {
145
+ const missingProperty = err.params.missingProperty.replace(/^\./, "");
146
+ return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(err.parentSchema, ["properties", missingProperty])}`;
147
+ } else if(err.keyword === "minLength" || err.keyword === "minItems") {
148
+ if(err.params.limit === 1)
149
+ return `${dataPath} should not be empty.`;
150
+ else
151
+ return `${dataPath} ${err.message}`;
152
+ } else {
153
+ // eslint-disable-line no-fallthrough
154
+ return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${getSchemaPartText(err.parentSchema)}`;
155
+ }
150
156
  }
151
157
  }
152
158
 
153
- OptionsValidationError.formatSchema = formatSchema;
159
+ module.exports = OptionsValidationError;
package/lib/Server.js CHANGED
@@ -34,19 +34,20 @@ function Server(compiler, options) {
34
34
  this.hot = options.hot || options.hotOnly;
35
35
  this.headers = options.headers;
36
36
  this.clientLogLevel = options.clientLogLevel;
37
+ this.clientOverlay = options.overlay;
37
38
  this.sockets = [];
38
39
  this.contentBaseWatchers = [];
39
40
 
40
41
  // Listening for events
41
- const invalidPlugin = function() {
42
+ const invalidPlugin = () => {
42
43
  this.sockWrite(this.sockets, "invalid");
43
- }.bind(this);
44
+ };
44
45
  compiler.plugin("compile", invalidPlugin);
45
46
  compiler.plugin("invalid", invalidPlugin);
46
- compiler.plugin("done", function(stats) {
47
+ compiler.plugin("done", (stats) => {
47
48
  this._sendStats(this.sockets, stats.toJson(clientStats));
48
49
  this._stats = stats;
49
- }.bind(this));
50
+ });
50
51
 
51
52
  // Init express server
52
53
  const app = this.app = new express();
@@ -54,27 +55,27 @@ function Server(compiler, options) {
54
55
  // middleware for serving webpack bundle
55
56
  this.middleware = webpackDevMiddleware(compiler, options);
56
57
 
57
- app.get("/__webpack_dev_server__/live.bundle.js", function(req, res) {
58
+ app.get("/__webpack_dev_server__/live.bundle.js", (req, res) => {
58
59
  res.setHeader("Content-Type", "application/javascript");
59
60
  fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(res);
60
61
  });
61
62
 
62
- app.get("/__webpack_dev_server__/sockjs.bundle.js", function(req, res) {
63
+ app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
63
64
  res.setHeader("Content-Type", "application/javascript");
64
65
  fs.createReadStream(path.join(__dirname, "..", "client", "sockjs.bundle.js")).pipe(res);
65
66
  });
66
67
 
67
- app.get("/webpack-dev-server.js", function(req, res) {
68
+ app.get("/webpack-dev-server.js", (req, res) => {
68
69
  res.setHeader("Content-Type", "application/javascript");
69
70
  fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(res);
70
71
  });
71
72
 
72
- app.get("/webpack-dev-server/*", function(req, res) {
73
+ app.get("/webpack-dev-server/*", (req, res) => {
73
74
  res.setHeader("Content-Type", "text/html");
74
75
  fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(res);
75
76
  });
76
77
 
77
- app.get("/webpack-dev-server", function(req, res) {
78
+ app.get("/webpack-dev-server", (req, res) => {
78
79
  res.setHeader("Content-Type", "text/html");
79
80
  /* eslint-disable quotes */
80
81
  res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
@@ -117,7 +118,7 @@ function Server(compiler, options) {
117
118
  /* eslint-enable quotes */
118
119
  writeDirectory(options.publicPath || "/", path);
119
120
  res.end("</body></html>");
120
- }.bind(this));
121
+ });
121
122
 
122
123
  let contentBase;
123
124
  if(options.contentBase !== undefined) {
@@ -126,15 +127,18 @@ function Server(compiler, options) {
126
127
  contentBase = process.cwd();
127
128
  }
128
129
 
130
+ // Keep track of websocket proxies for external websocket upgrade.
131
+ const websocketProxies = [];
132
+
129
133
  const features = {
130
- compress: function() {
134
+ compress() {
131
135
  if(options.compress) {
132
136
  // Enable gzip compression.
133
137
  app.use(compress());
134
138
  }
135
139
  },
136
140
 
137
- proxy: function() {
141
+ proxy() {
138
142
  if(options.proxy) {
139
143
  /**
140
144
  * Assume a proxy configuration specified as:
@@ -147,7 +151,7 @@ function Server(compiler, options) {
147
151
  * }
148
152
  */
149
153
  if(!Array.isArray(options.proxy)) {
150
- options.proxy = Object.keys(options.proxy).map(function(context) {
154
+ options.proxy = Object.keys(options.proxy).map((context) => {
151
155
  let proxyOptions;
152
156
  // For backwards compatibility reasons.
153
157
  const correctedContext = context.replace(/^\*$/, "**").replace(/\/\*$/, "");
@@ -158,7 +162,7 @@ function Server(compiler, options) {
158
162
  target: options.proxy[context]
159
163
  };
160
164
  } else {
161
- proxyOptions = options.proxy[context];
165
+ proxyOptions = Object.assign({}, options.proxy[context]);
162
166
  proxyOptions.context = correctedContext;
163
167
  }
164
168
  proxyOptions.logLevel = proxyOptions.logLevel || "warn";
@@ -167,7 +171,7 @@ function Server(compiler, options) {
167
171
  });
168
172
  }
169
173
 
170
- const getProxyMiddleware = function(proxyConfig) {
174
+ const getProxyMiddleware = (proxyConfig) => {
171
175
  const context = proxyConfig.context || proxyConfig.path;
172
176
 
173
177
  // It is possible to use the `bypass` method without a `target`.
@@ -193,7 +197,7 @@ function Server(compiler, options) {
193
197
  * }
194
198
  * ]
195
199
  */
196
- options.proxy.forEach(function(proxyConfigOrCallback) {
200
+ options.proxy.forEach((proxyConfigOrCallback) => {
197
201
  let proxyConfig;
198
202
  let proxyMiddleware;
199
203
 
@@ -204,8 +208,11 @@ function Server(compiler, options) {
204
208
  }
205
209
 
206
210
  proxyMiddleware = getProxyMiddleware(proxyConfig);
211
+ if(proxyConfig.ws) {
212
+ websocketProxies.push(proxyMiddleware);
213
+ }
207
214
 
208
- app.use(function(req, res, next) {
215
+ app.use((req, res, next) => {
209
216
  if(typeof proxyConfigOrCallback === "function") {
210
217
  const newProxyConfig = proxyConfigOrCallback();
211
218
  if(newProxyConfig !== proxyConfig) {
@@ -229,7 +236,7 @@ function Server(compiler, options) {
229
236
  }
230
237
  },
231
238
 
232
- historyApiFallback: function() {
239
+ historyApiFallback() {
233
240
  if(options.historyApiFallback) {
234
241
  // Fall back to /index.html if nothing else matches.
235
242
  app.use(
@@ -238,16 +245,16 @@ function Server(compiler, options) {
238
245
  }
239
246
  },
240
247
 
241
- contentBaseFiles: function() {
248
+ contentBaseFiles() {
242
249
  if(Array.isArray(contentBase)) {
243
- contentBase.forEach(function(item) {
250
+ contentBase.forEach((item) => {
244
251
  app.get("*", express.static(item));
245
252
  });
246
253
  } else if(/^(https?:)?\/\//.test(contentBase)) {
247
254
  console.log("Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
248
255
  console.log('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
249
256
  // Redirect every request to contentBase
250
- app.get("*", function(req, res) {
257
+ app.get("*", (req, res) => {
251
258
  res.writeHead(302, {
252
259
  "Location": contentBase + req.path + (req._parsedUrl.search || "")
253
260
  });
@@ -257,7 +264,7 @@ function Server(compiler, options) {
257
264
  console.log("Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
258
265
  console.log('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
259
266
  // Redirect every request to the port contentBase
260
- app.get("*", function(req, res) {
267
+ app.get("*", (req, res) => {
261
268
  res.writeHead(302, {
262
269
  "Location": `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ""}`
263
270
  });
@@ -269,9 +276,9 @@ function Server(compiler, options) {
269
276
  }
270
277
  },
271
278
 
272
- contentBaseIndex: function() {
279
+ contentBaseIndex() {
273
280
  if(Array.isArray(contentBase)) {
274
- contentBase.forEach(function(item) {
281
+ contentBase.forEach((item) => {
275
282
  app.get("*", serveIndex(item));
276
283
  });
277
284
  } else if(!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== "number") {
@@ -279,35 +286,35 @@ function Server(compiler, options) {
279
286
  }
280
287
  },
281
288
 
282
- watchContentBase: function() {
289
+ watchContentBase: () => {
283
290
  if(/^(https?:)?\/\//.test(contentBase) || typeof contentBase === "number") {
284
291
  throw new Error("Watching remote files is not supported.");
285
292
  } else if(Array.isArray(contentBase)) {
286
- contentBase.forEach(function(item) {
293
+ contentBase.forEach((item) => {
287
294
  this._watch(item);
288
- }.bind(this));
295
+ });
289
296
  } else {
290
297
  this._watch(contentBase);
291
298
  }
292
- }.bind(this),
299
+ },
293
300
 
294
- middleware: function() {
301
+ middleware: () => {
295
302
  // include our middleware to ensure it is able to handle '/index.html' request after redirect
296
303
  app.use(this.middleware);
297
- }.bind(this),
304
+ },
298
305
 
299
- headers: function() {
306
+ headers: () => {
300
307
  app.all("*", this.setContentHeaders.bind(this));
301
- }.bind(this),
308
+ },
302
309
 
303
- magicHtml: function() {
310
+ magicHtml: () => {
304
311
  app.get("*", this.serveMagicHtml.bind(this));
305
- }.bind(this),
312
+ },
306
313
 
307
- setup: function() {
314
+ setup: () => {
308
315
  if(typeof options.setup === "function")
309
316
  options.setup(app, this);
310
- }.bind(this)
317
+ }
311
318
  };
312
319
 
313
320
  const defaultFeatures = ["setup", "headers", "middleware"];
@@ -317,8 +324,11 @@ function Server(compiler, options) {
317
324
  defaultFeatures.push("contentBaseFiles");
318
325
  if(options.watchContentBase)
319
326
  defaultFeatures.push("watchContentBase");
320
- if(options.historyApiFallback)
321
- defaultFeatures.push("historyApiFallback", "middleware", "contentBaseFiles");
327
+ if(options.historyApiFallback) {
328
+ defaultFeatures.push("historyApiFallback", "middleware");
329
+ if(contentBase !== false)
330
+ defaultFeatures.push("contentBaseFiles");
331
+ }
322
332
  defaultFeatures.push("magicHtml");
323
333
  if(contentBase !== false)
324
334
  defaultFeatures.push("contentBaseIndex");
@@ -326,9 +336,9 @@ function Server(compiler, options) {
326
336
  if(options.compress)
327
337
  defaultFeatures.unshift("compress");
328
338
 
329
- (options.features || defaultFeatures).forEach(function(feature) {
339
+ (options.features || defaultFeatures).forEach((feature) => {
330
340
  features[feature]();
331
- }, this);
341
+ });
332
342
 
333
343
  if(options.https) {
334
344
  // for keep supporting CLI parameters
@@ -357,6 +367,12 @@ function Server(compiler, options) {
357
367
  } else {
358
368
  this.listeningApp = http.createServer(app);
359
369
  }
370
+
371
+ // Proxy websockets without the initial http request
372
+ // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
373
+ websocketProxies.forEach(function(wsProxy) {
374
+ this.listeningApp.on("upgrade", wsProxy.upgrade);
375
+ }, this);
360
376
  }
361
377
 
362
378
  Server.prototype.use = function() {
@@ -386,25 +402,28 @@ Server.prototype.listen = function() {
386
402
  }
387
403
  }
388
404
  });
389
- sockServer.on("connection", function(conn) {
405
+ sockServer.on("connection", (conn) => {
390
406
  if(!conn) return;
391
407
  this.sockets.push(conn);
392
408
 
393
- conn.on("close", function() {
409
+ conn.on("close", () => {
394
410
  const connIndex = this.sockets.indexOf(conn);
395
411
  if(connIndex >= 0) {
396
412
  this.sockets.splice(connIndex, 1);
397
413
  }
398
- }.bind(this));
414
+ });
399
415
 
400
416
  if(this.clientLogLevel)
401
417
  this.sockWrite([conn], "log-level", this.clientLogLevel);
402
418
 
419
+ if(this.clientOverlay)
420
+ this.sockWrite([conn], "overlay", this.clientOverlay);
421
+
403
422
  if(this.hot) this.sockWrite([conn], "hot");
404
423
 
405
424
  if(!this._stats) return;
406
425
  this._sendStats([conn], this._stats.toJson(clientStats), true);
407
- }.bind(this));
426
+ });
408
427
 
409
428
  sockServer.installHandlers(this.listeningApp, {
410
429
  prefix: "/sockjs-node"
@@ -413,22 +432,22 @@ Server.prototype.listen = function() {
413
432
  }
414
433
 
415
434
  Server.prototype.close = function(callback) {
416
- this.sockets.forEach(function(sock) {
435
+ this.sockets.forEach((sock) => {
417
436
  sock.close();
418
437
  });
419
438
  this.sockets = [];
420
- this.listeningApp.close(function() {
439
+ this.listeningApp.close(() => {
421
440
  this.middleware.close(callback);
422
- }.bind(this));
441
+ });
423
442
 
424
- this.contentBaseWatchers.forEach(function(watcher) {
443
+ this.contentBaseWatchers.forEach((watcher) => {
425
444
  watcher.close();
426
445
  });
427
446
  this.contentBaseWatchers = [];
428
447
  }
429
448
 
430
449
  Server.prototype.sockWrite = function(sockets, type, data) {
431
- sockets.forEach(function(sock) {
450
+ sockets.forEach((sock) => {
432
451
  sock.write(JSON.stringify({
433
452
  type: type,
434
453
  data: data
@@ -460,9 +479,7 @@ Server.prototype._sendStats = function(sockets, stats, force) {
460
479
  stats &&
461
480
  (!stats.errors || stats.errors.length === 0) &&
462
481
  stats.assets &&
463
- stats.assets.every(function(asset) {
464
- return !asset.emitted;
465
- })
482
+ stats.assets.every((asset) => !asset.emitted)
466
483
  )
467
484
  return this.sockWrite(sockets, "still-ok");
468
485
  this.sockWrite(sockets, "hash", stats.hash);
@@ -475,9 +492,9 @@ Server.prototype._sendStats = function(sockets, stats, force) {
475
492
  }
476
493
 
477
494
  Server.prototype._watch = function(path) {
478
- const watcher = chokidar.watch(path).on("change", function() {
495
+ const watcher = chokidar.watch(path).on("change", () => {
479
496
  this.sockWrite(this.sockets, "content-changed");
480
- }.bind(this))
497
+ });
481
498
 
482
499
  this.contentBaseWatchers.push(watcher);
483
500
  }
@@ -486,4 +503,7 @@ Server.prototype.invalidate = function() {
486
503
  if(this.middleware) this.middleware.invalidate();
487
504
  }
488
505
 
506
+ // Export this logic, so that other implementations, like task-runners can use it
507
+ Server.addDevServerEntrypoints = require("./util/addDevServerEntrypoints");
508
+
489
509
  module.exports = Server;
@@ -34,7 +34,14 @@
34
34
  },
35
35
  "port": {
36
36
  "description": "The port the server listens to.",
37
- "type": "number"
37
+ "anyOf": [
38
+ {
39
+ "type": "number"
40
+ },
41
+ {
42
+ "type": "string"
43
+ }
44
+ ]
38
45
  },
39
46
  "socket": {
40
47
  "description": "The Unix socket to listen to (instead of on a host).",
@@ -60,6 +67,25 @@
60
67
  "error"
61
68
  ]
62
69
  },
70
+ "overlay": {
71
+ "description": "Shows an error overlay in browser.",
72
+ "anyOf": [
73
+ {
74
+ "type": "boolean"
75
+ },
76
+ {
77
+ "type": "object",
78
+ "properties": {
79
+ "errors": {
80
+ "type": "boolean"
81
+ },
82
+ "warnings": {
83
+ "type": "boolean"
84
+ }
85
+ }
86
+ }
87
+ ]
88
+ },
63
89
  "key": {
64
90
  "description": "The contents of a SSL key.",
65
91
  "anyOf": [
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ const createDomain = require("./createDomain");
3
+
4
+ module.exports = function addDevServerEntrypoints(webpackOptions, devServerOptions) {
5
+ if(devServerOptions.inline !== false) {
6
+ const domain = createDomain(devServerOptions);
7
+ const devClient = [`${require.resolve("../../client/")}?${domain}`];
8
+
9
+ if(devServerOptions.hotOnly)
10
+ devClient.push("webpack/hot/only-dev-server");
11
+ else if(devServerOptions.hot)
12
+ devClient.push("webpack/hot/dev-server");
13
+
14
+ [].concat(webpackOptions).forEach((wpOpt) => {
15
+ if(typeof wpOpt.entry === "object" && !Array.isArray(wpOpt.entry)) {
16
+ Object.keys(wpOpt.entry).forEach((key) => {
17
+ wpOpt.entry[key] = devClient.concat(wpOpt.entry[key]);
18
+ });
19
+ } else if(typeof wpOpt.entry === "function") {
20
+ wpOpt.entry = wpOpt.entry(devClient);
21
+ } else {
22
+ wpOpt.entry = devClient.concat(wpOpt.entry);
23
+ }
24
+ });
25
+ }
26
+ };