webpack-dev-server 2.4.1 → 2.4.5

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
@@ -1,499 +1,551 @@
1
- "use strict";
2
-
3
- const fs = require("fs");
4
- const chokidar = require("chokidar");
5
- const path = require("path");
6
- const webpackDevMiddleware = require("webpack-dev-middleware");
7
- const express = require("express");
8
- const compress = require("compression");
9
- const sockjs = require("sockjs");
10
- const http = require("http");
11
- const spdy = require("spdy");
12
- const httpProxyMiddleware = require("http-proxy-middleware");
13
- const serveIndex = require("serve-index");
14
- const historyApiFallback = require("connect-history-api-fallback");
15
- const webpack = require("webpack");
16
- const OptionsValidationError = require("./OptionsValidationError");
17
- const optionsSchema = require("./optionsSchema.json");
18
-
19
- const clientStats = { errorDetails: false };
20
-
21
- function Server(compiler, options) {
22
- // Default options
23
- if(!options) options = {};
24
-
25
- const validationErrors = webpack.validateSchema(optionsSchema, options);
26
- if(validationErrors.length) {
27
- throw new OptionsValidationError(validationErrors);
28
- }
29
-
30
- if(options.lazy && !options.filename) {
31
- throw new Error("'filename' option must be set in lazy mode.");
32
- }
33
-
34
- this.hot = options.hot || options.hotOnly;
35
- this.headers = options.headers;
36
- this.clientLogLevel = options.clientLogLevel;
37
- this.clientOverlay = options.overlay;
38
- this.sockets = [];
39
- this.contentBaseWatchers = [];
40
-
41
- // Listening for events
42
- const invalidPlugin = function() {
43
- this.sockWrite(this.sockets, "invalid");
44
- }.bind(this);
45
- compiler.plugin("compile", invalidPlugin);
46
- compiler.plugin("invalid", invalidPlugin);
47
- compiler.plugin("done", function(stats) {
48
- this._sendStats(this.sockets, stats.toJson(clientStats));
49
- this._stats = stats;
50
- }.bind(this));
51
-
52
- // Init express server
53
- const app = this.app = new express();
54
-
55
- // middleware for serving webpack bundle
56
- this.middleware = webpackDevMiddleware(compiler, options);
57
-
58
- app.get("/__webpack_dev_server__/live.bundle.js", function(req, res) {
59
- res.setHeader("Content-Type", "application/javascript");
60
- fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(res);
61
- });
62
-
63
- app.get("/__webpack_dev_server__/sockjs.bundle.js", function(req, res) {
64
- res.setHeader("Content-Type", "application/javascript");
65
- fs.createReadStream(path.join(__dirname, "..", "client", "sockjs.bundle.js")).pipe(res);
66
- });
67
-
68
- app.get("/webpack-dev-server.js", function(req, res) {
69
- res.setHeader("Content-Type", "application/javascript");
70
- fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(res);
71
- });
72
-
73
- app.get("/webpack-dev-server/*", function(req, res) {
74
- res.setHeader("Content-Type", "text/html");
75
- fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(res);
76
- });
77
-
78
- app.get("/webpack-dev-server", function(req, res) {
79
- res.setHeader("Content-Type", "text/html");
80
- /* eslint-disable quotes */
81
- res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
82
- const path = this.middleware.getFilenameFromUrl(options.publicPath || "/");
83
- const fs = this.middleware.fileSystem;
84
-
85
- function writeDirectory(baseUrl, basePath) {
86
- const content = fs.readdirSync(basePath);
87
- res.write("<ul>");
88
- content.forEach(function(item) {
89
- const p = `${basePath}/${item}`;
90
- if(fs.statSync(p).isFile()) {
91
- res.write('<li><a href="');
92
- res.write(baseUrl + item);
93
- res.write('">');
94
- res.write(item);
95
- res.write('</a></li>');
96
- if(/\.js$/.test(item)) {
97
- const htmlItem = item.substr(0, item.length - 3);
98
- res.write('<li><a href="');
99
- res.write(baseUrl + htmlItem);
100
- res.write('">');
101
- res.write(htmlItem);
102
- res.write('</a> (magic html for ');
103
- res.write(item);
104
- res.write(') (<a href="');
105
- res.write(baseUrl.replace(/(^(https?:\/\/[^\/]+)?\/)/, "$1webpack-dev-server/") + htmlItem);
106
- res.write('">webpack-dev-server</a>)</li>');
107
- }
108
- } else {
109
- res.write('<li>');
110
- res.write(item);
111
- res.write('<br>');
112
- writeDirectory(`${baseUrl + item}/`, p);
113
- res.write('</li>');
114
- }
115
- });
116
- res.write("</ul>");
117
- }
118
- /* eslint-enable quotes */
119
- writeDirectory(options.publicPath || "/", path);
120
- res.end("</body></html>");
121
- }.bind(this));
122
-
123
- let contentBase;
124
- if(options.contentBase !== undefined) {
125
- contentBase = options.contentBase;
126
- } else {
127
- contentBase = process.cwd();
128
- }
129
-
130
- const features = {
131
- compress: function() {
132
- if(options.compress) {
133
- // Enable gzip compression.
134
- app.use(compress());
135
- }
136
- },
137
-
138
- proxy: function() {
139
- if(options.proxy) {
140
- /**
141
- * Assume a proxy configuration specified as:
142
- * proxy: {
143
- * 'context': { options }
144
- * }
145
- * OR
146
- * proxy: {
147
- * 'context': 'target'
148
- * }
149
- */
150
- if(!Array.isArray(options.proxy)) {
151
- options.proxy = Object.keys(options.proxy).map(function(context) {
152
- let proxyOptions;
153
- // For backwards compatibility reasons.
154
- const correctedContext = context.replace(/^\*$/, "**").replace(/\/\*$/, "");
155
-
156
- if(typeof options.proxy[context] === "string") {
157
- proxyOptions = {
158
- context: correctedContext,
159
- target: options.proxy[context]
160
- };
161
- } else {
162
- proxyOptions = options.proxy[context];
163
- proxyOptions.context = correctedContext;
164
- }
165
- proxyOptions.logLevel = proxyOptions.logLevel || "warn";
166
-
167
- return proxyOptions;
168
- });
169
- }
170
-
171
- const getProxyMiddleware = function(proxyConfig) {
172
- const context = proxyConfig.context || proxyConfig.path;
173
-
174
- // It is possible to use the `bypass` method without a `target`.
175
- // However, the proxy middleware has no use in this case, and will fail to instantiate.
176
- if(proxyConfig.target) {
177
- return httpProxyMiddleware(context, proxyConfig);
178
- }
179
- }
180
-
181
- /**
182
- * Assume a proxy configuration specified as:
183
- * proxy: [
184
- * {
185
- * context: ...,
186
- * ...options...
187
- * },
188
- * // or:
189
- * function() {
190
- * return {
191
- * context: ...,
192
- * ...options...
193
- * };
194
- * }
195
- * ]
196
- */
197
- options.proxy.forEach(function(proxyConfigOrCallback) {
198
- let proxyConfig;
199
- let proxyMiddleware;
200
-
201
- if(typeof proxyConfigOrCallback === "function") {
202
- proxyConfig = proxyConfigOrCallback();
203
- } else {
204
- proxyConfig = proxyConfigOrCallback;
205
- }
206
-
207
- proxyMiddleware = getProxyMiddleware(proxyConfig);
208
-
209
- app.use(function(req, res, next) {
210
- if(typeof proxyConfigOrCallback === "function") {
211
- const newProxyConfig = proxyConfigOrCallback();
212
- if(newProxyConfig !== proxyConfig) {
213
- proxyConfig = newProxyConfig;
214
- proxyMiddleware = getProxyMiddleware(proxyConfig);
215
- }
216
- }
217
- const bypass = typeof proxyConfig.bypass === "function";
218
- const bypassUrl = bypass && proxyConfig.bypass(req, res, proxyConfig) || false;
219
-
220
- if(bypassUrl) {
221
- req.url = bypassUrl;
222
- next();
223
- } else if(proxyMiddleware) {
224
- return proxyMiddleware(req, res, next);
225
- } else {
226
- next();
227
- }
228
- });
229
- });
230
- }
231
- },
232
-
233
- historyApiFallback: function() {
234
- if(options.historyApiFallback) {
235
- // Fall back to /index.html if nothing else matches.
236
- app.use(
237
- historyApiFallback(typeof options.historyApiFallback === "object" ? options.historyApiFallback : null)
238
- );
239
- }
240
- },
241
-
242
- contentBaseFiles: function() {
243
- if(Array.isArray(contentBase)) {
244
- contentBase.forEach(function(item) {
245
- app.get("*", express.static(item));
246
- });
247
- } else if(/^(https?:)?\/\//.test(contentBase)) {
248
- console.log("Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
249
- console.log('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
250
- // Redirect every request to contentBase
251
- app.get("*", function(req, res) {
252
- res.writeHead(302, {
253
- "Location": contentBase + req.path + (req._parsedUrl.search || "")
254
- });
255
- res.end();
256
- });
257
- } else if(typeof contentBase === "number") {
258
- console.log("Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
259
- console.log('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
260
- // Redirect every request to the port contentBase
261
- app.get("*", function(req, res) {
262
- res.writeHead(302, {
263
- "Location": `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ""}`
264
- });
265
- res.end();
266
- });
267
- } else {
268
- // route content request
269
- app.get("*", express.static(contentBase, options.staticOptions));
270
- }
271
- },
272
-
273
- contentBaseIndex: function() {
274
- if(Array.isArray(contentBase)) {
275
- contentBase.forEach(function(item) {
276
- app.get("*", serveIndex(item));
277
- });
278
- } else if(!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== "number") {
279
- app.get("*", serveIndex(contentBase));
280
- }
281
- },
282
-
283
- watchContentBase: function() {
284
- if(/^(https?:)?\/\//.test(contentBase) || typeof contentBase === "number") {
285
- throw new Error("Watching remote files is not supported.");
286
- } else if(Array.isArray(contentBase)) {
287
- contentBase.forEach(function(item) {
288
- this._watch(item);
289
- }.bind(this));
290
- } else {
291
- this._watch(contentBase);
292
- }
293
- }.bind(this),
294
-
295
- middleware: function() {
296
- // include our middleware to ensure it is able to handle '/index.html' request after redirect
297
- app.use(this.middleware);
298
- }.bind(this),
299
-
300
- headers: function() {
301
- app.all("*", this.setContentHeaders.bind(this));
302
- }.bind(this),
303
-
304
- magicHtml: function() {
305
- app.get("*", this.serveMagicHtml.bind(this));
306
- }.bind(this),
307
-
308
- setup: function() {
309
- if(typeof options.setup === "function")
310
- options.setup(app, this);
311
- }.bind(this)
312
- };
313
-
314
- const defaultFeatures = ["setup", "headers", "middleware"];
315
- if(options.proxy)
316
- defaultFeatures.push("proxy", "middleware");
317
- if(contentBase !== false)
318
- defaultFeatures.push("contentBaseFiles");
319
- if(options.watchContentBase)
320
- defaultFeatures.push("watchContentBase");
321
- if(options.historyApiFallback) {
322
- defaultFeatures.push("historyApiFallback", "middleware");
323
- if(contentBase !== false)
324
- defaultFeatures.push("contentBaseFiles");
325
- }
326
- defaultFeatures.push("magicHtml");
327
- if(contentBase !== false)
328
- defaultFeatures.push("contentBaseIndex");
329
- // compress is placed last and uses unshift so that it will be the first middleware used
330
- if(options.compress)
331
- defaultFeatures.unshift("compress");
332
-
333
- (options.features || defaultFeatures).forEach(function(feature) {
334
- features[feature]();
335
- }, this);
336
-
337
- if(options.https) {
338
- // for keep supporting CLI parameters
339
- if(typeof options.https === "boolean") {
340
- options.https = {
341
- key: options.key,
342
- cert: options.cert,
343
- ca: options.ca,
344
- pfx: options.pfx,
345
- passphrase: options.pfxPassphrase
346
- };
347
- }
348
-
349
- // Use built-in self-signed certificate if no certificate was configured
350
- const fakeCert = fs.readFileSync(path.join(__dirname, "../ssl/server.pem"));
351
- options.https.key = options.https.key || fakeCert;
352
- options.https.cert = options.https.cert || fakeCert;
353
-
354
- if(!options.https.spdy) {
355
- options.https.spdy = {
356
- protocols: ["h2", "http/1.1"]
357
- };
358
- }
359
-
360
- this.listeningApp = spdy.createServer(options.https, app);
361
- } else {
362
- this.listeningApp = http.createServer(app);
363
- }
364
- }
365
-
366
- Server.prototype.use = function() {
367
- this.app.use.apply(this.app, arguments);
368
- }
369
-
370
- Server.prototype.setContentHeaders = function(req, res, next) {
371
- if(this.headers) {
372
- for(const name in this.headers) {
373
- res.setHeader(name, this.headers[name]);
374
- }
375
- }
376
-
377
- next();
378
- }
379
-
380
- // delegate listen call and init sockjs
381
- Server.prototype.listen = function() {
382
- const returnValue = this.listeningApp.listen.apply(this.listeningApp, arguments);
383
- const sockServer = sockjs.createServer({
384
- // Use provided up-to-date sockjs-client
385
- sockjs_url: "/__webpack_dev_server__/sockjs.bundle.js",
386
- // Limit useless logs
387
- log: function(severity, line) {
388
- if(severity === "error") {
389
- console.log(line);
390
- }
391
- }
392
- });
393
- sockServer.on("connection", function(conn) {
394
- if(!conn) return;
395
- this.sockets.push(conn);
396
-
397
- conn.on("close", function() {
398
- const connIndex = this.sockets.indexOf(conn);
399
- if(connIndex >= 0) {
400
- this.sockets.splice(connIndex, 1);
401
- }
402
- }.bind(this));
403
-
404
- if(this.clientLogLevel)
405
- this.sockWrite([conn], "log-level", this.clientLogLevel);
406
-
407
- if(this.clientOverlay)
408
- this.sockWrite([conn], "overlay", this.clientOverlay);
409
-
410
- if(this.hot) this.sockWrite([conn], "hot");
411
-
412
- if(!this._stats) return;
413
- this._sendStats([conn], this._stats.toJson(clientStats), true);
414
- }.bind(this));
415
-
416
- sockServer.installHandlers(this.listeningApp, {
417
- prefix: "/sockjs-node"
418
- });
419
- return returnValue;
420
- }
421
-
422
- Server.prototype.close = function(callback) {
423
- this.sockets.forEach(function(sock) {
424
- sock.close();
425
- });
426
- this.sockets = [];
427
- this.listeningApp.close(function() {
428
- this.middleware.close(callback);
429
- }.bind(this));
430
-
431
- this.contentBaseWatchers.forEach(function(watcher) {
432
- watcher.close();
433
- });
434
- this.contentBaseWatchers = [];
435
- }
436
-
437
- Server.prototype.sockWrite = function(sockets, type, data) {
438
- sockets.forEach(function(sock) {
439
- sock.write(JSON.stringify({
440
- type: type,
441
- data: data
442
- }));
443
- });
444
- }
445
-
446
- Server.prototype.serveMagicHtml = function(req, res, next) {
447
- const _path = req.path;
448
- try {
449
- if(!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(`${_path}.js`)).isFile())
450
- return next();
451
- // Serve a page that executes the javascript
452
- /* eslint-disable quotes */
453
- res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="');
454
- res.write(_path);
455
- res.write('.js');
456
- res.write(req._parsedUrl.search || "");
457
- res.end('"></script></body></html>');
458
- /* eslint-enable quotes */
459
- } catch(e) {
460
- return next();
461
- }
462
- }
463
-
464
- // send stats to a socket or multiple sockets
465
- Server.prototype._sendStats = function(sockets, stats, force) {
466
- if(!force &&
467
- stats &&
468
- (!stats.errors || stats.errors.length === 0) &&
469
- stats.assets &&
470
- stats.assets.every(function(asset) {
471
- return !asset.emitted;
472
- })
473
- )
474
- return this.sockWrite(sockets, "still-ok");
475
- this.sockWrite(sockets, "hash", stats.hash);
476
- if(stats.errors.length > 0)
477
- this.sockWrite(sockets, "errors", stats.errors);
478
- else if(stats.warnings.length > 0)
479
- this.sockWrite(sockets, "warnings", stats.warnings);
480
- else
481
- this.sockWrite(sockets, "ok");
482
- }
483
-
484
- Server.prototype._watch = function(path) {
485
- const watcher = chokidar.watch(path).on("change", function() {
486
- this.sockWrite(this.sockets, "content-changed");
487
- }.bind(this))
488
-
489
- this.contentBaseWatchers.push(watcher);
490
- }
491
-
492
- Server.prototype.invalidate = function() {
493
- if(this.middleware) this.middleware.invalidate();
494
- }
495
-
496
- // Export this logic, so that other implementations, like task-runners can use it
497
- Server.addDevServerEntrypoints = require("./util/addDevServerEntrypoints");
498
-
499
- module.exports = Server;
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const chokidar = require("chokidar");
5
+ const path = require("path");
6
+ const webpackDevMiddleware = require("webpack-dev-middleware");
7
+ const express = require("express");
8
+ const compress = require("compression");
9
+ const sockjs = require("sockjs");
10
+ const http = require("http");
11
+ const spdy = require("spdy");
12
+ const httpProxyMiddleware = require("http-proxy-middleware");
13
+ const serveIndex = require("serve-index");
14
+ const historyApiFallback = require("connect-history-api-fallback");
15
+ const webpack = require("webpack");
16
+ const OptionsValidationError = require("./OptionsValidationError");
17
+ const optionsSchema = require("./optionsSchema.json");
18
+
19
+ const clientStats = { errorDetails: false };
20
+
21
+ function Server(compiler, options) {
22
+ // Default options
23
+ if(!options) options = {};
24
+
25
+ const validationErrors = webpack.validateSchema(optionsSchema, options);
26
+ if(validationErrors.length) {
27
+ throw new OptionsValidationError(validationErrors);
28
+ }
29
+
30
+ if(options.lazy && !options.filename) {
31
+ throw new Error("'filename' option must be set in lazy mode.");
32
+ }
33
+
34
+ this.hot = options.hot || options.hotOnly;
35
+ this.headers = options.headers;
36
+ this.clientLogLevel = options.clientLogLevel;
37
+ this.clientOverlay = options.overlay;
38
+ this.disableHostCheck = !!options.disableHostCheck;
39
+ this.publicHost = options.public;
40
+ this.sockets = [];
41
+ this.contentBaseWatchers = [];
42
+
43
+ // Listening for events
44
+ const invalidPlugin = () => {
45
+ this.sockWrite(this.sockets, "invalid");
46
+ };
47
+ compiler.plugin("compile", invalidPlugin);
48
+ compiler.plugin("invalid", invalidPlugin);
49
+ compiler.plugin("done", (stats) => {
50
+ this._sendStats(this.sockets, stats.toJson(clientStats));
51
+ this._stats = stats;
52
+ });
53
+
54
+ // Init express server
55
+ const app = this.app = new express();
56
+
57
+ app.all("*", (req, res, next) => {
58
+ if(this.checkHost(req.headers))
59
+ return next();
60
+ res.send("Invalid Host header");
61
+ });
62
+
63
+ // middleware for serving webpack bundle
64
+ this.middleware = webpackDevMiddleware(compiler, options);
65
+
66
+ app.get("/__webpack_dev_server__/live.bundle.js", (req, res) => {
67
+ res.setHeader("Content-Type", "application/javascript");
68
+ fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(res);
69
+ });
70
+
71
+ app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
72
+ res.setHeader("Content-Type", "application/javascript");
73
+ fs.createReadStream(path.join(__dirname, "..", "client", "sockjs.bundle.js")).pipe(res);
74
+ });
75
+
76
+ app.get("/webpack-dev-server.js", (req, res) => {
77
+ res.setHeader("Content-Type", "application/javascript");
78
+ fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(res);
79
+ });
80
+
81
+ app.get("/webpack-dev-server/*", (req, res) => {
82
+ res.setHeader("Content-Type", "text/html");
83
+ fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(res);
84
+ });
85
+
86
+ app.get("/webpack-dev-server", (req, res) => {
87
+ res.setHeader("Content-Type", "text/html");
88
+ /* eslint-disable quotes */
89
+ res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
90
+ const path = this.middleware.getFilenameFromUrl(options.publicPath || "/");
91
+ const fs = this.middleware.fileSystem;
92
+
93
+ function writeDirectory(baseUrl, basePath) {
94
+ const content = fs.readdirSync(basePath);
95
+ res.write("<ul>");
96
+ content.forEach(function(item) {
97
+ const p = `${basePath}/${item}`;
98
+ if(fs.statSync(p).isFile()) {
99
+ res.write('<li><a href="');
100
+ res.write(baseUrl + item);
101
+ res.write('">');
102
+ res.write(item);
103
+ res.write('</a></li>');
104
+ if(/\.js$/.test(item)) {
105
+ const htmlItem = item.substr(0, item.length - 3);
106
+ res.write('<li><a href="');
107
+ res.write(baseUrl + htmlItem);
108
+ res.write('">');
109
+ res.write(htmlItem);
110
+ res.write('</a> (magic html for ');
111
+ res.write(item);
112
+ res.write(') (<a href="');
113
+ res.write(baseUrl.replace(/(^(https?:\/\/[^\/]+)?\/)/, "$1webpack-dev-server/") + htmlItem);
114
+ res.write('">webpack-dev-server</a>)</li>');
115
+ }
116
+ } else {
117
+ res.write('<li>');
118
+ res.write(item);
119
+ res.write('<br>');
120
+ writeDirectory(`${baseUrl + item}/`, p);
121
+ res.write('</li>');
122
+ }
123
+ });
124
+ res.write("</ul>");
125
+ }
126
+ /* eslint-enable quotes */
127
+ writeDirectory(options.publicPath || "/", path);
128
+ res.end("</body></html>");
129
+ });
130
+
131
+ let contentBase;
132
+ if(options.contentBase !== undefined) {
133
+ contentBase = options.contentBase;
134
+ } else {
135
+ contentBase = process.cwd();
136
+ }
137
+
138
+ // Keep track of websocket proxies for external websocket upgrade.
139
+ const websocketProxies = [];
140
+
141
+ const features = {
142
+ compress() {
143
+ if(options.compress) {
144
+ // Enable gzip compression.
145
+ app.use(compress());
146
+ }
147
+ },
148
+
149
+ proxy() {
150
+ if(options.proxy) {
151
+ /**
152
+ * Assume a proxy configuration specified as:
153
+ * proxy: {
154
+ * 'context': { options }
155
+ * }
156
+ * OR
157
+ * proxy: {
158
+ * 'context': 'target'
159
+ * }
160
+ */
161
+ if(!Array.isArray(options.proxy)) {
162
+ options.proxy = Object.keys(options.proxy).map((context) => {
163
+ let proxyOptions;
164
+ // For backwards compatibility reasons.
165
+ const correctedContext = context.replace(/^\*$/, "**").replace(/\/\*$/, "");
166
+
167
+ if(typeof options.proxy[context] === "string") {
168
+ proxyOptions = {
169
+ context: correctedContext,
170
+ target: options.proxy[context]
171
+ };
172
+ } else {
173
+ proxyOptions = Object.assign({}, options.proxy[context]);
174
+ proxyOptions.context = correctedContext;
175
+ }
176
+ proxyOptions.logLevel = proxyOptions.logLevel || "warn";
177
+
178
+ return proxyOptions;
179
+ });
180
+ }
181
+
182
+ const getProxyMiddleware = (proxyConfig) => {
183
+ const context = proxyConfig.context || proxyConfig.path;
184
+
185
+ // It is possible to use the `bypass` method without a `target`.
186
+ // However, the proxy middleware has no use in this case, and will fail to instantiate.
187
+ if(proxyConfig.target) {
188
+ return httpProxyMiddleware(context, proxyConfig);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Assume a proxy configuration specified as:
194
+ * proxy: [
195
+ * {
196
+ * context: ...,
197
+ * ...options...
198
+ * },
199
+ * // or:
200
+ * function() {
201
+ * return {
202
+ * context: ...,
203
+ * ...options...
204
+ * };
205
+ * }
206
+ * ]
207
+ */
208
+ options.proxy.forEach((proxyConfigOrCallback) => {
209
+ let proxyConfig;
210
+ let proxyMiddleware;
211
+
212
+ if(typeof proxyConfigOrCallback === "function") {
213
+ proxyConfig = proxyConfigOrCallback();
214
+ } else {
215
+ proxyConfig = proxyConfigOrCallback;
216
+ }
217
+
218
+ proxyMiddleware = getProxyMiddleware(proxyConfig);
219
+ if(proxyConfig.ws) {
220
+ websocketProxies.push(proxyMiddleware);
221
+ }
222
+
223
+ app.use((req, res, next) => {
224
+ if(typeof proxyConfigOrCallback === "function") {
225
+ const newProxyConfig = proxyConfigOrCallback();
226
+ if(newProxyConfig !== proxyConfig) {
227
+ proxyConfig = newProxyConfig;
228
+ proxyMiddleware = getProxyMiddleware(proxyConfig);
229
+ }
230
+ }
231
+ const bypass = typeof proxyConfig.bypass === "function";
232
+ const bypassUrl = bypass && proxyConfig.bypass(req, res, proxyConfig) || false;
233
+
234
+ if(bypassUrl) {
235
+ req.url = bypassUrl;
236
+ next();
237
+ } else if(proxyMiddleware) {
238
+ return proxyMiddleware(req, res, next);
239
+ } else {
240
+ next();
241
+ }
242
+ });
243
+ });
244
+ }
245
+ },
246
+
247
+ historyApiFallback() {
248
+ if(options.historyApiFallback) {
249
+ // Fall back to /index.html if nothing else matches.
250
+ app.use(
251
+ historyApiFallback(typeof options.historyApiFallback === "object" ? options.historyApiFallback : null)
252
+ );
253
+ }
254
+ },
255
+
256
+ contentBaseFiles() {
257
+ if(Array.isArray(contentBase)) {
258
+ contentBase.forEach((item) => {
259
+ app.get("*", express.static(item));
260
+ });
261
+ } else if(/^(https?:)?\/\//.test(contentBase)) {
262
+ console.log("Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
263
+ console.log('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
264
+ // Redirect every request to contentBase
265
+ app.get("*", (req, res) => {
266
+ res.writeHead(302, {
267
+ "Location": contentBase + req.path + (req._parsedUrl.search || "")
268
+ });
269
+ res.end();
270
+ });
271
+ } else if(typeof contentBase === "number") {
272
+ console.log("Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.");
273
+ console.log('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
274
+ // Redirect every request to the port contentBase
275
+ app.get("*", (req, res) => {
276
+ res.writeHead(302, {
277
+ "Location": `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ""}`
278
+ });
279
+ res.end();
280
+ });
281
+ } else {
282
+ // route content request
283
+ app.get("*", express.static(contentBase, options.staticOptions));
284
+ }
285
+ },
286
+
287
+ contentBaseIndex() {
288
+ if(Array.isArray(contentBase)) {
289
+ contentBase.forEach((item) => {
290
+ app.get("*", serveIndex(item));
291
+ });
292
+ } else if(!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== "number") {
293
+ app.get("*", serveIndex(contentBase));
294
+ }
295
+ },
296
+
297
+ watchContentBase: () => {
298
+ if(/^(https?:)?\/\//.test(contentBase) || typeof contentBase === "number") {
299
+ throw new Error("Watching remote files is not supported.");
300
+ } else if(Array.isArray(contentBase)) {
301
+ contentBase.forEach((item) => {
302
+ this._watch(item);
303
+ });
304
+ } else {
305
+ this._watch(contentBase);
306
+ }
307
+ },
308
+
309
+ middleware: () => {
310
+ // include our middleware to ensure it is able to handle '/index.html' request after redirect
311
+ app.use(this.middleware);
312
+ },
313
+
314
+ headers: () => {
315
+ app.all("*", this.setContentHeaders.bind(this));
316
+ },
317
+
318
+ magicHtml: () => {
319
+ app.get("*", this.serveMagicHtml.bind(this));
320
+ },
321
+
322
+ setup: () => {
323
+ if(typeof options.setup === "function")
324
+ options.setup(app, this);
325
+ }
326
+ };
327
+
328
+ const defaultFeatures = ["setup", "headers", "middleware"];
329
+ if(options.proxy)
330
+ defaultFeatures.push("proxy", "middleware");
331
+ if(contentBase !== false)
332
+ defaultFeatures.push("contentBaseFiles");
333
+ if(options.watchContentBase)
334
+ defaultFeatures.push("watchContentBase");
335
+ if(options.historyApiFallback) {
336
+ defaultFeatures.push("historyApiFallback", "middleware");
337
+ if(contentBase !== false)
338
+ defaultFeatures.push("contentBaseFiles");
339
+ }
340
+ defaultFeatures.push("magicHtml");
341
+ if(contentBase !== false)
342
+ defaultFeatures.push("contentBaseIndex");
343
+ // compress is placed last and uses unshift so that it will be the first middleware used
344
+ if(options.compress)
345
+ defaultFeatures.unshift("compress");
346
+
347
+ (options.features || defaultFeatures).forEach((feature) => {
348
+ features[feature]();
349
+ });
350
+
351
+ if(options.https) {
352
+ // for keep supporting CLI parameters
353
+ if(typeof options.https === "boolean") {
354
+ options.https = {
355
+ key: options.key,
356
+ cert: options.cert,
357
+ ca: options.ca,
358
+ pfx: options.pfx,
359
+ passphrase: options.pfxPassphrase
360
+ };
361
+ }
362
+
363
+ // Use built-in self-signed certificate if no certificate was configured
364
+ const fakeCert = fs.readFileSync(path.join(__dirname, "../ssl/server.pem"));
365
+ options.https.key = options.https.key || fakeCert;
366
+ options.https.cert = options.https.cert || fakeCert;
367
+
368
+ if(!options.https.spdy) {
369
+ options.https.spdy = {
370
+ protocols: ["h2", "http/1.1"]
371
+ };
372
+ }
373
+
374
+ this.listeningApp = spdy.createServer(options.https, app);
375
+ } else {
376
+ this.listeningApp = http.createServer(app);
377
+ }
378
+
379
+ // Proxy websockets without the initial http request
380
+ // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
381
+ websocketProxies.forEach(function(wsProxy) {
382
+ this.listeningApp.on("upgrade", wsProxy.upgrade);
383
+ }, this);
384
+ }
385
+
386
+ Server.prototype.use = function() {
387
+ this.app.use.apply(this.app, arguments);
388
+ }
389
+
390
+ Server.prototype.setContentHeaders = function(req, res, next) {
391
+ if(this.headers) {
392
+ for(const name in this.headers) {
393
+ res.setHeader(name, this.headers[name]);
394
+ }
395
+ }
396
+
397
+ next();
398
+ }
399
+
400
+ Server.prototype.checkHost = function(headers) {
401
+ // allow user to opt-out this security check, at own risk
402
+ if(this.disableHostCheck) return true;
403
+
404
+ // get the Host header and extract hostname
405
+ // we don't care about port not matching
406
+ const hostHeader = headers.host;
407
+ if(!hostHeader) return false;
408
+ const idx = hostHeader.indexOf(":");
409
+ const hostname = idx >= 0 ? hostHeader.substr(0, idx) : hostHeader;
410
+
411
+ // always allow localhost host, for convience
412
+ if(hostname === "127.0.0.1" || hostname === "localhost") return true;
413
+
414
+ // allow hostname of listening adress
415
+ if(hostname === this.listenHostname) return true;
416
+
417
+ // also allow public hostname if provided
418
+ if(typeof this.publicHost === "string") {
419
+ const idxPublic = this.publicHost.indexOf(":");
420
+ const publicHostname = idxPublic >= 0 ? this.publicHost.substr(0, idxPublic) : this.publicHost;
421
+ if(hostname === publicHostname) return true;
422
+ }
423
+
424
+ // disallow
425
+ return false;
426
+ }
427
+
428
+ // delegate listen call and init sockjs
429
+ Server.prototype.listen = function(port, hostname) {
430
+ this.listenHostname = hostname;
431
+ const returnValue = this.listeningApp.listen.apply(this.listeningApp, arguments);
432
+ const sockServer = sockjs.createServer({
433
+ // Use provided up-to-date sockjs-client
434
+ sockjs_url: "/__webpack_dev_server__/sockjs.bundle.js",
435
+ // Limit useless logs
436
+ log: function(severity, line) {
437
+ if(severity === "error") {
438
+ console.log(line);
439
+ }
440
+ }
441
+ });
442
+ sockServer.on("connection", (conn) => {
443
+ if(!conn) return;
444
+ if(!this.checkHost(conn.headers)) {
445
+ this.sockWrite([conn], "error", "Invalid Host header");
446
+ conn.close();
447
+ return;
448
+ }
449
+ this.sockets.push(conn);
450
+
451
+ conn.on("close", () => {
452
+ const connIndex = this.sockets.indexOf(conn);
453
+ if(connIndex >= 0) {
454
+ this.sockets.splice(connIndex, 1);
455
+ }
456
+ });
457
+
458
+ if(this.clientLogLevel)
459
+ this.sockWrite([conn], "log-level", this.clientLogLevel);
460
+
461
+ if(this.clientOverlay)
462
+ this.sockWrite([conn], "overlay", this.clientOverlay);
463
+
464
+ if(this.hot) this.sockWrite([conn], "hot");
465
+
466
+ if(!this._stats) return;
467
+ this._sendStats([conn], this._stats.toJson(clientStats), true);
468
+ });
469
+
470
+ sockServer.installHandlers(this.listeningApp, {
471
+ prefix: "/sockjs-node"
472
+ });
473
+ return returnValue;
474
+ }
475
+
476
+ Server.prototype.close = function(callback) {
477
+ this.sockets.forEach((sock) => {
478
+ sock.close();
479
+ });
480
+ this.sockets = [];
481
+ this.listeningApp.close(() => {
482
+ this.middleware.close(callback);
483
+ });
484
+
485
+ this.contentBaseWatchers.forEach((watcher) => {
486
+ watcher.close();
487
+ });
488
+ this.contentBaseWatchers = [];
489
+ }
490
+
491
+ Server.prototype.sockWrite = function(sockets, type, data) {
492
+ sockets.forEach((sock) => {
493
+ sock.write(JSON.stringify({
494
+ type: type,
495
+ data: data
496
+ }));
497
+ });
498
+ }
499
+
500
+ Server.prototype.serveMagicHtml = function(req, res, next) {
501
+ const _path = req.path;
502
+ try {
503
+ if(!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(`${_path}.js`)).isFile())
504
+ return next();
505
+ // Serve a page that executes the javascript
506
+ /* eslint-disable quotes */
507
+ res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="');
508
+ res.write(_path);
509
+ res.write('.js');
510
+ res.write(req._parsedUrl.search || "");
511
+ res.end('"></script></body></html>');
512
+ /* eslint-enable quotes */
513
+ } catch(e) {
514
+ return next();
515
+ }
516
+ }
517
+
518
+ // send stats to a socket or multiple sockets
519
+ Server.prototype._sendStats = function(sockets, stats, force) {
520
+ if(!force &&
521
+ stats &&
522
+ (!stats.errors || stats.errors.length === 0) &&
523
+ stats.assets &&
524
+ stats.assets.every((asset) => !asset.emitted)
525
+ )
526
+ return this.sockWrite(sockets, "still-ok");
527
+ this.sockWrite(sockets, "hash", stats.hash);
528
+ if(stats.errors.length > 0)
529
+ this.sockWrite(sockets, "errors", stats.errors);
530
+ else if(stats.warnings.length > 0)
531
+ this.sockWrite(sockets, "warnings", stats.warnings);
532
+ else
533
+ this.sockWrite(sockets, "ok");
534
+ }
535
+
536
+ Server.prototype._watch = function(path) {
537
+ const watcher = chokidar.watch(path).on("change", () => {
538
+ this.sockWrite(this.sockets, "content-changed");
539
+ });
540
+
541
+ this.contentBaseWatchers.push(watcher);
542
+ }
543
+
544
+ Server.prototype.invalidate = function() {
545
+ if(this.middleware) this.middleware.invalidate();
546
+ }
547
+
548
+ // Export this logic, so that other implementations, like task-runners can use it
549
+ Server.addDevServerEntrypoints = require("./util/addDevServerEntrypoints");
550
+
551
+ module.exports = Server;