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