webpack-dev-server 3.1.6 → 3.1.10

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,40 +1,69 @@
1
1
  'use strict';
2
2
 
3
- /* eslint func-names: off */
4
-
3
+ /* eslint-disable
4
+ import/order,
5
+ no-shadow,
6
+ no-undefined,
7
+ func-names,
8
+ multiline-ternary,
9
+ array-bracket-spacing,
10
+ space-before-function-paren
11
+ */
5
12
  const fs = require('fs');
6
- const http = require('http');
7
13
  const path = require('path');
14
+
15
+ const ip = require('ip');
16
+ const tls = require('tls');
8
17
  const url = require('url');
9
- const chokidar = require('chokidar');
10
- const compress = require('compression');
18
+ const http = require('http');
19
+ const https = require('https');
20
+ const spdy = require('spdy');
21
+ const sockjs = require('sockjs');
22
+
23
+ const killable = require('killable');
24
+
11
25
  const del = require('del');
26
+ const chokidar = require('chokidar');
27
+
12
28
  const express = require('express');
13
- const httpProxyMiddleware = require('http-proxy-middleware');
14
- const ip = require('ip');
15
- const killable = require('killable');
29
+
30
+ const compress = require('compression');
16
31
  const serveIndex = require('serve-index');
32
+ const httpProxyMiddleware = require('http-proxy-middleware');
17
33
  const historyApiFallback = require('connect-history-api-fallback');
18
- const selfsigned = require('selfsigned');
19
- const sockjs = require('sockjs');
20
- const spdy = require('spdy');
34
+
21
35
  const webpack = require('webpack');
22
36
  const webpackDevMiddleware = require('webpack-dev-middleware');
23
- const createLog = require('./createLog');
24
- const OptionsValidationError = require('./OptionsValidationError');
25
- const optionsSchema = require('./optionsSchema.json');
26
37
 
27
- const clientStats = { all: false, assets: true, warnings: true, errors: true, errorDetails: false, hash: true };
38
+ const createLogger = require('./utils/createLogger');
39
+ const createCertificate = require('./utils/createCertificate');
28
40
 
29
- function Server(compiler, options, _log) {
30
- // Default options
31
- if (!options) options = {};
32
- this.log = _log || createLog(options);
41
+ const validateOptions = require('schema-utils');
42
+ const schema = require('./options.json');
33
43
 
34
- const validationErrors = webpack.validateSchema(optionsSchema, options);
35
- if (validationErrors.length) {
36
- throw new OptionsValidationError(validationErrors);
37
- }
44
+ // Workaround for node ^8.6.0, ^9.0.0
45
+ // DEFAULT_ECDH_CURVE is default to prime256v1 in these version
46
+ // breaking connection when certificate is not signed with prime256v1
47
+ // change it to auto allows OpenSSL to select the curve automatically
48
+ // See https://github.com/nodejs/node/issues/16196 for more infomation
49
+ const version = parseFloat(process.version.slice(1));
50
+ if (version >= 8.6 && version < 10) {
51
+ tls.DEFAULT_ECDH_CURVE = 'auto';
52
+ }
53
+
54
+ const STATS = {
55
+ all: false,
56
+ hash: true,
57
+ assets: true,
58
+ warnings: true,
59
+ errors: true,
60
+ errorDetails: false
61
+ };
62
+
63
+ function Server (compiler, options = {}, _log) {
64
+ this.log = _log || createLogger(options);
65
+
66
+ validateOptions(schema, options, 'webpack Dev Server');
38
67
 
39
68
  if (options.lazy && !options.filename) {
40
69
  throw new Error("'filename' option must be set in lazy mode.");
@@ -42,49 +71,71 @@ function Server(compiler, options, _log) {
42
71
 
43
72
  this.hot = options.hot || options.hotOnly;
44
73
  this.headers = options.headers;
45
- this.clientLogLevel = options.clientLogLevel;
46
- this.clientOverlay = options.overlay;
47
74
  this.progress = options.progress;
48
- this.disableHostCheck = !!options.disableHostCheck;
75
+
76
+ this.clientOverlay = options.overlay;
77
+ this.clientLogLevel = options.clientLogLevel;
78
+
49
79
  this.publicHost = options.public;
50
80
  this.allowedHosts = options.allowedHosts;
81
+ this.disableHostCheck = !!options.disableHostCheck;
82
+
51
83
  this.sockets = [];
52
- this.contentBaseWatchers = [];
84
+
53
85
  this.watchOptions = options.watchOptions || {};
86
+ this.contentBaseWatchers = [];
54
87
 
55
88
  // Listening for events
56
89
  const invalidPlugin = () => {
57
90
  this.sockWrite(this.sockets, 'invalid');
58
91
  };
92
+
59
93
  if (this.progress) {
60
- const progressPlugin = new webpack.ProgressPlugin((percent, msg, addInfo) => {
61
- percent = Math.floor(percent * 100);
62
- if (percent === 100) msg = 'Compilation completed';
63
- if (addInfo) msg = `${msg} (${addInfo})`;
64
- this.sockWrite(this.sockets, 'progress-update', { percent, msg });
65
- });
94
+ const progressPlugin = new webpack.ProgressPlugin(
95
+ (percent, msg, addInfo) => {
96
+ percent = Math.floor(percent * 100);
97
+
98
+ if (percent === 100) {
99
+ msg = 'Compilation completed';
100
+ }
101
+
102
+ if (addInfo) {
103
+ msg = `${msg} (${addInfo})`;
104
+ }
105
+
106
+ this.sockWrite(this.sockets, 'progress-update', { percent, msg });
107
+ }
108
+ );
109
+
66
110
  progressPlugin.apply(compiler);
67
111
  }
68
112
 
69
- const addCompilerHooks = (comp) => {
70
- comp.hooks.compile.tap('webpack-dev-server', invalidPlugin);
71
- comp.hooks.invalid.tap('webpack-dev-server', invalidPlugin);
72
- comp.hooks.done.tap('webpack-dev-server', (stats) => {
73
- this._sendStats(this.sockets, stats.toJson(clientStats));
113
+ const addHooks = (compiler) => {
114
+ const { compile, invalid, done } = compiler.hooks;
115
+
116
+ compile.tap('webpack-dev-server', invalidPlugin);
117
+ invalid.tap('webpack-dev-server', invalidPlugin);
118
+ done.tap('webpack-dev-server', (stats) => {
119
+ this._sendStats(this.sockets, stats.toJson(STATS));
74
120
  this._stats = stats;
75
121
  });
76
122
  };
123
+
77
124
  if (compiler.compilers) {
78
- compiler.compilers.forEach(addCompilerHooks);
125
+ compiler.compilers.forEach(addHooks);
79
126
  } else {
80
- addCompilerHooks(compiler);
127
+ addHooks(compiler);
81
128
  }
82
129
 
83
130
  // Init express server
84
- const app = this.app = new express(); // eslint-disable-line
131
+ // eslint-disable-next-line
132
+ const app = this.app = new express();
133
+
134
+ app.all('*', (req, res, next) => {
135
+ if (this.checkHost(req.headers)) {
136
+ return next();
137
+ }
85
138
 
86
- app.all('*', (req, res, next) => { // eslint-disable-line
87
- if (this.checkHost(req.headers)) { return next(); }
88
139
  res.send('Invalid Host header');
89
140
  });
90
141
 
@@ -95,70 +146,106 @@ function Server(compiler, options, _log) {
95
146
 
96
147
  app.get('/__webpack_dev_server__/live.bundle.js', (req, res) => {
97
148
  res.setHeader('Content-Type', 'application/javascript');
98
- fs.createReadStream(path.join(__dirname, '..', 'client', 'live.bundle.js')).pipe(res);
149
+
150
+ fs.createReadStream(
151
+ path.join(__dirname, '..', 'client', 'live.bundle.js')
152
+ ).pipe(res);
99
153
  });
100
154
 
101
155
  app.get('/__webpack_dev_server__/sockjs.bundle.js', (req, res) => {
102
156
  res.setHeader('Content-Type', 'application/javascript');
103
- fs.createReadStream(path.join(__dirname, '..', 'client', 'sockjs.bundle.js')).pipe(res);
157
+
158
+ fs.createReadStream(
159
+ path.join(__dirname, '..', 'client', 'sockjs.bundle.js')
160
+ ).pipe(res);
104
161
  });
105
162
 
106
163
  app.get('/webpack-dev-server.js', (req, res) => {
107
164
  res.setHeader('Content-Type', 'application/javascript');
108
- fs.createReadStream(path.join(__dirname, '..', 'client', 'index.bundle.js')).pipe(res);
165
+
166
+ fs.createReadStream(
167
+ path.join(__dirname, '..', 'client', 'index.bundle.js')
168
+ ).pipe(res);
109
169
  });
110
170
 
111
171
  app.get('/webpack-dev-server/*', (req, res) => {
112
172
  res.setHeader('Content-Type', 'text/html');
113
- fs.createReadStream(path.join(__dirname, '..', 'client', 'live.html')).pipe(res);
173
+
174
+ fs.createReadStream(
175
+ path.join(__dirname, '..', 'client', 'live.html')
176
+ ).pipe(res);
114
177
  });
115
178
 
116
179
  app.get('/webpack-dev-server', (req, res) => {
117
180
  res.setHeader('Content-Type', 'text/html');
118
- res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
119
- const outputPath = this.middleware.getFilenameFromUrl(options.publicPath || '/');
181
+
182
+ res.write(
183
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
184
+ );
185
+
186
+ const outputPath = this.middleware.getFilenameFromUrl(
187
+ options.publicPath || '/'
188
+ );
189
+
120
190
  const filesystem = this.middleware.fileSystem;
121
191
 
122
192
  function writeDirectory(baseUrl, basePath) {
123
193
  const content = filesystem.readdirSync(basePath);
194
+
124
195
  res.write('<ul>');
196
+
125
197
  content.forEach((item) => {
126
198
  const p = `${basePath}/${item}`;
199
+
127
200
  if (filesystem.statSync(p).isFile()) {
128
201
  res.write('<li><a href="');
129
202
  res.write(baseUrl + item);
130
203
  res.write('">');
131
204
  res.write(item);
132
205
  res.write('</a></li>');
206
+
133
207
  if (/\.js$/.test(item)) {
134
- const htmlItem = item.substr(0, item.length - 3);
208
+ const html = item.substr(0, item.length - 3);
209
+
135
210
  res.write('<li><a href="');
136
- res.write(baseUrl + htmlItem);
211
+ res.write(baseUrl + html);
137
212
  res.write('">');
138
- res.write(htmlItem);
213
+ res.write(html);
139
214
  res.write('</a> (magic html for ');
140
215
  res.write(item);
141
216
  res.write(') (<a href="');
142
- res.write(baseUrl.replace(/(^(https?:\/\/[^\/]+)?\/)/, "$1webpack-dev-server/") + htmlItem); // eslint-disable-line
217
+ res.write(
218
+ baseUrl.replace(
219
+ // eslint-disable-next-line
220
+ /(^(https?:\/\/[^\/]+)?\/)/,
221
+ '$1webpack-dev-server/'
222
+ ) + html
223
+ );
143
224
  res.write('">webpack-dev-server</a>)</li>');
144
225
  }
145
226
  } else {
146
227
  res.write('<li>');
147
228
  res.write(item);
148
229
  res.write('<br>');
230
+
149
231
  writeDirectory(`${baseUrl + item}/`, p);
232
+
150
233
  res.write('</li>');
151
234
  }
152
235
  });
236
+
153
237
  res.write('</ul>');
154
238
  }
239
+
155
240
  writeDirectory(options.publicPath || '/', outputPath);
241
+
156
242
  res.end('</body></html>');
157
243
  });
158
244
 
159
245
  let contentBase;
160
- if (options.contentBase !== undefined) { // eslint-disable-line
161
- contentBase = options.contentBase; // eslint-disable-line
246
+
247
+ if (options.contentBase !== undefined) {
248
+ contentBase = options.contentBase;
162
249
  } else {
163
250
  contentBase = process.cwd();
164
251
  }
@@ -167,14 +254,13 @@ function Server(compiler, options, _log) {
167
254
  const websocketProxies = [];
168
255
 
169
256
  const features = {
170
- compress() {
257
+ compress: () => {
171
258
  if (options.compress) {
172
259
  // Enable gzip compression.
173
260
  app.use(compress());
174
261
  }
175
262
  },
176
-
177
- proxy() {
263
+ proxy: () => {
178
264
  if (options.proxy) {
179
265
  /**
180
266
  * Assume a proxy configuration specified as:
@@ -190,7 +276,9 @@ function Server(compiler, options, _log) {
190
276
  options.proxy = Object.keys(options.proxy).map((context) => {
191
277
  let proxyOptions;
192
278
  // For backwards compatibility reasons.
193
- const correctedContext = context.replace(/^\*$/, '**').replace(/\/\*$/, '');
279
+ const correctedContext = context
280
+ .replace(/^\*$/, '**')
281
+ .replace(/\/\*$/, '');
194
282
 
195
283
  if (typeof options.proxy[context] === 'string') {
196
284
  proxyOptions = {
@@ -201,6 +289,7 @@ function Server(compiler, options, _log) {
201
289
  proxyOptions = Object.assign({}, options.proxy[context]);
202
290
  proxyOptions.context = correctedContext;
203
291
  }
292
+
204
293
  proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
205
294
 
206
295
  return proxyOptions;
@@ -209,14 +298,12 @@ function Server(compiler, options, _log) {
209
298
 
210
299
  const getProxyMiddleware = (proxyConfig) => {
211
300
  const context = proxyConfig.context || proxyConfig.path;
212
-
213
301
  // It is possible to use the `bypass` method without a `target`.
214
302
  // However, the proxy middleware has no use in this case, and will fail to instantiate.
215
303
  if (proxyConfig.target) {
216
304
  return httpProxyMiddleware(context, proxyConfig);
217
305
  }
218
306
  };
219
-
220
307
  /**
221
308
  * Assume a proxy configuration specified as:
222
309
  * proxy: [
@@ -244,6 +331,7 @@ function Server(compiler, options, _log) {
244
331
  }
245
332
 
246
333
  proxyMiddleware = getProxyMiddleware(proxyConfig);
334
+
247
335
  if (proxyConfig.ws) {
248
336
  websocketProxies.push(proxyMiddleware);
249
337
  }
@@ -251,17 +339,20 @@ function Server(compiler, options, _log) {
251
339
  app.use((req, res, next) => {
252
340
  if (typeof proxyConfigOrCallback === 'function') {
253
341
  const newProxyConfig = proxyConfigOrCallback();
342
+
254
343
  if (newProxyConfig !== proxyConfig) {
255
344
  proxyConfig = newProxyConfig;
256
345
  proxyMiddleware = getProxyMiddleware(proxyConfig);
257
346
  }
258
347
  }
348
+
259
349
  const bypass = typeof proxyConfig.bypass === 'function';
260
- // eslint-disable-next-line
261
- const bypassUrl = bypass && proxyConfig.bypass(req, res, proxyConfig) || false;
350
+
351
+ const bypassUrl = (bypass && proxyConfig.bypass(req, res, proxyConfig)) || false;
262
352
 
263
353
  if (bypassUrl) {
264
354
  req.url = bypassUrl;
355
+
265
356
  next();
266
357
  } else if (proxyMiddleware) {
267
358
  return proxyMiddleware(req, res, next);
@@ -272,37 +363,50 @@ function Server(compiler, options, _log) {
272
363
  });
273
364
  }
274
365
  },
275
-
276
- historyApiFallback() {
366
+ historyApiFallback: () => {
277
367
  if (options.historyApiFallback) {
368
+ const fallback = typeof options.historyApiFallback === 'object'
369
+ ? options.historyApiFallback
370
+ : null;
278
371
  // Fall back to /index.html if nothing else matches.
279
- app.use(historyApiFallback(typeof options.historyApiFallback === 'object' ? options.historyApiFallback : null));
372
+ app.use(historyApiFallback(fallback));
280
373
  }
281
374
  },
282
-
283
375
  contentBaseFiles: () => {
284
376
  if (Array.isArray(contentBase)) {
285
377
  contentBase.forEach((item) => {
286
378
  app.get('*', express.static(item));
287
379
  });
288
380
  } else if (/^(https?:)?\/\//.test(contentBase)) {
289
- this.log.warn('Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.');
290
- this.log.warn('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
381
+ this.log.warn(
382
+ 'Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
383
+ );
384
+
385
+ this.log.warn(
386
+ 'proxy: {\n\t"*": "<your current contentBase configuration>"\n}'
387
+ );
291
388
  // Redirect every request to contentBase
292
389
  app.get('*', (req, res) => {
293
390
  res.writeHead(302, {
294
391
  Location: contentBase + req.path + (req._parsedUrl.search || '')
295
392
  });
393
+
296
394
  res.end();
297
395
  });
298
396
  } else if (typeof contentBase === 'number') {
299
- this.log.warn('Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.');
300
- this.log.warn('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
397
+ this.log.warn(
398
+ 'Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
399
+ );
400
+
401
+ this.log.warn(
402
+ 'proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'
403
+ );
301
404
  // Redirect every request to the port contentBase
302
405
  app.get('*', (req, res) => {
303
406
  res.writeHead(302, {
304
407
  Location: `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ''}`
305
408
  });
409
+
306
410
  res.end();
307
411
  });
308
412
  } else {
@@ -310,19 +414,23 @@ function Server(compiler, options, _log) {
310
414
  app.get('*', express.static(contentBase, options.staticOptions));
311
415
  }
312
416
  },
313
-
314
- contentBaseIndex() {
417
+ contentBaseIndex: () => {
315
418
  if (Array.isArray(contentBase)) {
316
419
  contentBase.forEach((item) => {
317
420
  app.get('*', serveIndex(item));
318
421
  });
319
- } else if (!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== 'number') {
422
+ } else if (
423
+ !/^(https?:)?\/\//.test(contentBase) &&
424
+ typeof contentBase !== 'number'
425
+ ) {
320
426
  app.get('*', serveIndex(contentBase));
321
427
  }
322
428
  },
323
-
324
429
  watchContentBase: () => {
325
- if (/^(https?:)?\/\//.test(contentBase) || typeof contentBase === 'number') {
430
+ if (
431
+ /^(https?:)?\/\//.test(contentBase) ||
432
+ typeof contentBase === 'number'
433
+ ) {
326
434
  throw new Error('Watching remote files is not supported.');
327
435
  } else if (Array.isArray(contentBase)) {
328
436
  contentBase.forEach((item) => {
@@ -332,51 +440,78 @@ function Server(compiler, options, _log) {
332
440
  this._watch(contentBase);
333
441
  }
334
442
  },
335
-
336
443
  before: () => {
337
444
  if (typeof options.before === 'function') {
338
445
  options.before(app, this);
339
446
  }
340
447
  },
341
-
342
448
  middleware: () => {
343
- // include our middleware to ensure it is able to handle '/index.html' request after redirect
449
+ // include our middleware to ensure
450
+ // it is able to handle '/index.html' request after redirect
344
451
  app.use(this.middleware);
345
452
  },
346
-
347
453
  after: () => {
348
- if (typeof options.after === 'function') { options.after(app, this); }
454
+ if (typeof options.after === 'function') {
455
+ options.after(app, this);
456
+ }
349
457
  },
350
-
351
458
  headers: () => {
352
459
  app.all('*', this.setContentHeaders.bind(this));
353
460
  },
354
-
355
461
  magicHtml: () => {
356
462
  app.get('*', this.serveMagicHtml.bind(this));
357
463
  },
358
-
359
464
  setup: () => {
360
465
  if (typeof options.setup === 'function') {
361
- this.log.warn('The `setup` option is deprecated and will be removed in v3. Please update your config to use `before`');
466
+ this.log.warn(
467
+ 'The `setup` option is deprecated and will be removed in v3. Please update your config to use `before`'
468
+ );
469
+
362
470
  options.setup(app, this);
363
471
  }
364
472
  }
365
473
  };
366
474
 
367
- const defaultFeatures = ['before', 'setup', 'headers', 'middleware'];
368
- if (options.proxy) { defaultFeatures.push('proxy', 'middleware'); }
369
- if (contentBase !== false) { defaultFeatures.push('contentBaseFiles'); }
370
- if (options.watchContentBase) { defaultFeatures.push('watchContentBase'); }
475
+ const defaultFeatures = [
476
+ 'setup',
477
+ 'before',
478
+ 'headers',
479
+ 'middleware'
480
+ ];
481
+
482
+ if (options.proxy) {
483
+ defaultFeatures.push('proxy', 'middleware');
484
+ }
485
+
486
+ if (contentBase !== false) {
487
+ defaultFeatures.push('contentBaseFiles');
488
+ }
489
+
490
+ if (options.watchContentBase) {
491
+ defaultFeatures.push('watchContentBase');
492
+ }
493
+
371
494
  if (options.historyApiFallback) {
372
495
  defaultFeatures.push('historyApiFallback', 'middleware');
373
- if (contentBase !== false) { defaultFeatures.push('contentBaseFiles'); }
496
+
497
+ if (contentBase !== false) {
498
+ defaultFeatures.push('contentBaseFiles');
499
+ }
374
500
  }
501
+
375
502
  defaultFeatures.push('magicHtml');
376
- if (contentBase !== false) { defaultFeatures.push('contentBaseIndex'); }
503
+
504
+ if (contentBase !== false) {
505
+ defaultFeatures.push('contentBaseIndex');
506
+ }
377
507
  // compress is placed last and uses unshift so that it will be the first middleware used
378
- if (options.compress) { defaultFeatures.unshift('compress'); }
379
- if (options.after) { defaultFeatures.push('after'); }
508
+ if (options.compress) {
509
+ defaultFeatures.unshift('compress');
510
+ }
511
+
512
+ if (options.after) {
513
+ defaultFeatures.push('after');
514
+ }
380
515
 
381
516
  (options.features || defaultFeatures).forEach((feature) => {
382
517
  features[feature]();
@@ -386,91 +521,56 @@ function Server(compiler, options, _log) {
386
521
  // for keep supporting CLI parameters
387
522
  if (typeof options.https === 'boolean') {
388
523
  options.https = {
389
- key: options.key,
390
- cert: options.cert,
391
524
  ca: options.ca,
392
525
  pfx: options.pfx,
526
+ key: options.key,
527
+ cert: options.cert,
393
528
  passphrase: options.pfxPassphrase,
394
529
  requestCert: options.requestCert || false
395
530
  };
396
531
  }
397
532
 
398
533
  let fakeCert;
534
+
399
535
  if (!options.https.key || !options.https.cert) {
400
536
  // Use a self-signed certificate if no certificate was configured.
401
537
  // Cycle certs every 24 hours
402
538
  const certPath = path.join(__dirname, '../ssl/server.pem');
539
+
403
540
  let certExists = fs.existsSync(certPath);
404
541
 
405
542
  if (certExists) {
406
- const certStat = fs.statSync(certPath);
407
543
  const certTtl = 1000 * 60 * 60 * 24;
544
+ const certStat = fs.statSync(certPath);
545
+
408
546
  const now = new Date();
409
547
 
410
548
  // cert is more than 30 days old, kill it with fire
411
549
  if ((now - certStat.ctime) / certTtl > 30) {
412
550
  this.log.info('SSL Certificate is more than 30 days old. Removing.');
551
+
413
552
  del.sync([certPath], { force: true });
553
+
414
554
  certExists = false;
415
555
  }
416
556
  }
417
557
 
418
558
  if (!certExists) {
419
559
  this.log.info('Generating SSL Certificate');
420
- const attrs = [{ name: 'commonName', value: 'localhost' }];
421
- const pems = selfsigned.generate(attrs, {
422
- algorithm: 'sha256',
423
- days: 30,
424
- keySize: 2048,
425
- extensions: [{
426
- name: 'basicConstraints',
427
- cA: true
428
- }, {
429
- name: 'keyUsage',
430
- keyCertSign: true,
431
- digitalSignature: true,
432
- nonRepudiation: true,
433
- keyEncipherment: true,
434
- dataEncipherment: true
435
- }, {
436
- name: 'subjectAltName',
437
- altNames: [
438
- {
439
- // type 2 is DNS
440
- type: 2,
441
- value: 'localhost'
442
- },
443
- {
444
- type: 2,
445
- value: 'localhost.localdomain'
446
- },
447
- {
448
- type: 2,
449
- value: 'lvh.me'
450
- },
451
- {
452
- type: 2,
453
- value: '*.lvh.me'
454
- },
455
- {
456
- type: 2,
457
- value: '[::1]'
458
- },
459
- {
460
- // type 7 is IP
461
- type: 7,
462
- ip: '127.0.0.1'
463
- },
464
- {
465
- type: 7,
466
- ip: 'fe80::1'
467
- }
468
- ]
469
- }]
470
- });
471
560
 
472
- fs.writeFileSync(certPath, pems.private + pems.cert, { encoding: 'utf-8' });
561
+ const attrs = [
562
+ { name: 'commonName', value: 'localhost' }
563
+ ];
564
+
565
+ const pems = createCertificate(attrs);
566
+
567
+ fs.writeFileSync(
568
+ certPath,
569
+ pems.private + pems.cert,
570
+ { encoding: 'utf-8' }
571
+ );
473
572
  }
573
+
474
574
  fakeCert = fs.readFileSync(certPath);
475
575
  }
476
576
 
@@ -483,7 +583,20 @@ function Server(compiler, options, _log) {
483
583
  };
484
584
  }
485
585
 
486
- this.listeningApp = spdy.createServer(options.https, app);
586
+ // `spdy` is effectively unmaintained, and as a consequence of an
587
+ // implementation that extensively relies on Node’s non-public APIs, broken
588
+ // on Node 10 and above. In those cases, only https will be used for now.
589
+ // Once express supports Node's built-in HTTP/2 support, migrating over to
590
+ // that should be the best way to go.
591
+ // The relevant issues are:
592
+ // - https://github.com/nodejs/node/issues/21665
593
+ // - https://github.com/webpack/webpack-dev-server/issues/1449
594
+ // - https://github.com/expressjs/express/issues/3388
595
+ if (version >= 10) {
596
+ this.listeningApp = https.createServer(options.https, app);
597
+ } else {
598
+ this.listeningApp = spdy.createServer(options.https, app);
599
+ }
487
600
  } else {
488
601
  this.listeningApp = http.createServer(app);
489
602
  }
@@ -514,50 +627,70 @@ Server.prototype.setContentHeaders = function (req, res, next) {
514
627
 
515
628
  Server.prototype.checkHost = function (headers) {
516
629
  // allow user to opt-out this security check, at own risk
517
- if (this.disableHostCheck) return true;
518
-
630
+ if (this.disableHostCheck) {
631
+ return true;
632
+ }
519
633
  // get the Host header and extract hostname
520
634
  // we don't care about port not matching
521
635
  const hostHeader = headers.host;
522
- if (!hostHeader) return false;
636
+
637
+ if (!hostHeader) {
638
+ return false;
639
+ }
523
640
 
524
641
  // use the node url-parser to retrieve the hostname from the host-header.
525
642
  const hostname = url.parse(`//${hostHeader}`, false, true).hostname;
526
-
527
643
  // always allow requests with explicit IPv4 or IPv6-address.
528
- // A note on IPv6 addresses: hostHeader will always contain the brackets denoting
529
- // an IPv6-address in URLs, these are removed from the hostname in url.parse(),
644
+ // A note on IPv6 addresses:
645
+ // hostHeader will always contain the brackets denoting
646
+ // an IPv6-address in URLs,
647
+ // these are removed from the hostname in url.parse(),
530
648
  // so we have the pure IPv6-address in hostname.
531
- if (ip.isV4Format(hostname) || ip.isV6Format(hostname)) return true;
532
-
649
+ if (ip.isV4Format(hostname) || ip.isV6Format(hostname)) {
650
+ return true;
651
+ }
533
652
  // always allow localhost host, for convience
534
- if (hostname === 'localhost') return true;
535
-
653
+ if (hostname === 'localhost') {
654
+ return true;
655
+ }
536
656
  // allow if hostname is in allowedHosts
537
657
  if (this.allowedHosts && this.allowedHosts.length) {
538
658
  for (let hostIdx = 0; hostIdx < this.allowedHosts.length; hostIdx++) {
539
659
  const allowedHost = this.allowedHosts[hostIdx];
660
+
540
661
  if (allowedHost === hostname) return true;
541
662
 
542
663
  // support "." as a subdomain wildcard
543
664
  // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
544
665
  if (allowedHost[0] === '.') {
545
666
  // "example.com"
546
- if (hostname === allowedHost.substring(1)) return true;
667
+ if (hostname === allowedHost.substring(1)) {
668
+ return true;
669
+ }
547
670
  // "*.example.com"
548
- if (hostname.endsWith(allowedHost)) return true;
671
+ if (hostname.endsWith(allowedHost)) {
672
+ return true;
673
+ }
549
674
  }
550
675
  }
551
676
  }
552
677
 
553
678
  // allow hostname of listening adress
554
- if (hostname === this.listenHostname) return true;
679
+ if (hostname === this.hostname) {
680
+ return true;
681
+ }
555
682
 
556
683
  // also allow public hostname if provided
557
684
  if (typeof this.publicHost === 'string') {
558
685
  const idxPublic = this.publicHost.indexOf(':');
559
- const publicHostname = idxPublic >= 0 ? this.publicHost.substr(0, idxPublic) : this.publicHost;
560
- if (hostname === publicHostname) return true;
686
+
687
+ const publicHostname = idxPublic >= 0
688
+ ? this.publicHost.substr(0, idxPublic)
689
+ : this.publicHost;
690
+
691
+ if (hostname === publicHostname) {
692
+ return true;
693
+ }
561
694
  }
562
695
 
563
696
  // disallow
@@ -566,9 +699,10 @@ Server.prototype.checkHost = function (headers) {
566
699
 
567
700
  // delegate listen call and init sockjs
568
701
  Server.prototype.listen = function (port, hostname, fn) {
569
- this.listenHostname = hostname;
702
+ this.hostname = hostname;
703
+
570
704
  const returnValue = this.listeningApp.listen(port, hostname, (err) => {
571
- const sockServer = sockjs.createServer({
705
+ const socket = sockjs.createServer({
572
706
  // Use provided up-to-date sockjs-client
573
707
  sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js',
574
708
  // Limit useless logs
@@ -581,35 +715,53 @@ Server.prototype.listen = function (port, hostname, fn) {
581
715
  }
582
716
  });
583
717
 
584
- sockServer.on('connection', (conn) => {
585
- if (!conn) return;
586
- if (!this.checkHost(conn.headers)) {
587
- this.sockWrite([conn], 'error', 'Invalid Host header');
588
- conn.close();
718
+ socket.on('connection', (connection) => {
719
+ if (!connection) {
589
720
  return;
590
721
  }
591
- this.sockets.push(conn);
592
722
 
593
- conn.on('close', () => {
594
- const connIndex = this.sockets.indexOf(conn);
595
- if (connIndex >= 0) {
596
- this.sockets.splice(connIndex, 1);
723
+ if (!this.checkHost(connection.headers)) {
724
+ this.sockWrite([ connection ], 'error', 'Invalid Host header');
725
+
726
+ connection.close();
727
+
728
+ return;
729
+ }
730
+
731
+ this.sockets.push(connection);
732
+
733
+ connection.on('close', () => {
734
+ const idx = this.sockets.indexOf(connection);
735
+
736
+ if (idx >= 0) {
737
+ this.sockets.splice(idx, 1);
597
738
  }
598
739
  });
599
740
 
600
- if (this.clientLogLevel) { this.sockWrite([conn], 'log-level', this.clientLogLevel); }
741
+ if (this.hot) {
742
+ this.sockWrite([ connection ], 'hot');
743
+ }
601
744
 
602
- if (this.progress) { this.sockWrite([conn], 'progress', this.progress); }
745
+ if (this.progress) {
746
+ this.sockWrite([ connection ], 'progress', this.progress);
747
+ }
603
748
 
604
- if (this.clientOverlay) { this.sockWrite([conn], 'overlay', this.clientOverlay); }
749
+ if (this.clientOverlay) {
750
+ this.sockWrite([ connection ], 'overlay', this.clientOverlay);
751
+ }
605
752
 
606
- if (this.hot) this.sockWrite([conn], 'hot');
753
+ if (this.clientLogLevel) {
754
+ this.sockWrite([ connection ], 'log-level', this.clientLogLevel);
755
+ }
607
756
 
608
- if (!this._stats) return;
609
- this._sendStats([conn], this._stats.toJson(clientStats), true);
757
+ if (!this._stats) {
758
+ return;
759
+ }
760
+
761
+ this._sendStats([ connection ], this._stats.toJson(STATS), true);
610
762
  });
611
763
 
612
- sockServer.installHandlers(this.listeningApp, {
764
+ socket.installHandlers(this.listeningApp, {
613
765
  prefix: '/sockjs-node'
614
766
  });
615
767
 
@@ -621,64 +773,89 @@ Server.prototype.listen = function (port, hostname, fn) {
621
773
  return returnValue;
622
774
  };
623
775
 
624
- Server.prototype.close = function (callback) {
625
- this.sockets.forEach((sock) => {
626
- sock.close();
776
+ Server.prototype.close = function (cb) {
777
+ this.sockets.forEach((socket) => {
778
+ socket.close();
627
779
  });
780
+
628
781
  this.sockets = [];
629
782
 
630
783
  this.contentBaseWatchers.forEach((watcher) => {
631
784
  watcher.close();
632
785
  });
786
+
633
787
  this.contentBaseWatchers = [];
634
788
 
635
789
  this.listeningApp.kill(() => {
636
- this.middleware.close(callback);
790
+ this.middleware.close(cb);
637
791
  });
638
792
  };
639
793
 
640
794
  Server.prototype.sockWrite = function (sockets, type, data) {
641
- sockets.forEach((sock) => {
642
- sock.write(JSON.stringify({
643
- type,
644
- data
645
- }));
795
+ sockets.forEach((socket) => {
796
+ socket.write(
797
+ JSON.stringify({ type, data })
798
+ );
646
799
  });
647
800
  };
648
801
 
649
802
  Server.prototype.serveMagicHtml = function (req, res, next) {
650
803
  const _path = req.path;
804
+
651
805
  try {
652
- if (!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(`${_path}.js`)).isFile()) { return next(); }
806
+ const isFile = this.middleware.fileSystem.statSync(
807
+ this.middleware.getFilenameFromUrl(`${_path}.js`)
808
+ ).isFile();
809
+
810
+ if (!isFile) {
811
+ return next();
812
+ }
653
813
  // Serve a page that executes the javascript
654
- res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="');
814
+ res.write(
815
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="'
816
+ );
655
817
  res.write(_path);
656
818
  res.write('.js');
657
819
  res.write(req._parsedUrl.search || '');
820
+
658
821
  res.end('"></script></body></html>');
659
- } catch (e) {
822
+ } catch (err) {
660
823
  return next();
661
824
  }
662
825
  };
663
826
 
664
827
  // send stats to a socket or multiple sockets
665
828
  Server.prototype._sendStats = function (sockets, stats, force) {
666
- if (!force &&
667
- stats &&
668
- (!stats.errors || stats.errors.length === 0) &&
669
- stats.assets &&
670
- stats.assets.every(asset => !asset.emitted)
671
- ) { return this.sockWrite(sockets, 'still-ok'); }
829
+ if (
830
+ !force &&
831
+ stats &&
832
+ (!stats.errors || stats.errors.length === 0) &&
833
+ stats.assets &&
834
+ stats.assets.every(asset => !asset.emitted)
835
+ ) {
836
+ return this.sockWrite(sockets, 'still-ok');
837
+ }
838
+
672
839
  this.sockWrite(sockets, 'hash', stats.hash);
673
- if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); } else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); } else { this.sockWrite(sockets, 'ok'); }
840
+
841
+ if (stats.errors.length > 0) {
842
+ this.sockWrite(sockets, 'errors', stats.errors);
843
+ } else if (stats.warnings.length > 0) {
844
+ this.sockWrite(sockets, 'warnings', stats.warnings);
845
+ } else {
846
+ this.sockWrite(sockets, 'ok');
847
+ }
674
848
  };
675
849
 
676
850
  Server.prototype._watch = function (watchPath) {
677
851
  // duplicate the same massaging of options that watchpack performs
678
852
  // https://github.com/webpack/watchpack/blob/master/lib/DirectoryWatcher.js#L49
679
853
  // this isn't an elegant solution, but we'll improve it in the future
680
- const usePolling = this.watchOptions.poll ? true : undefined; // eslint-disable-line no-undefined
681
- const interval = typeof this.watchOptions.poll === 'number' ? this.watchOptions.poll : undefined; // eslint-disable-line no-undefined
854
+ const usePolling = this.watchOptions.poll ? true : undefined;
855
+ const interval = typeof this.watchOptions.poll === 'number'
856
+ ? this.watchOptions.poll
857
+ : undefined;
858
+
682
859
  const options = {
683
860
  ignoreInitial: true,
684
861
  persistent: true,
@@ -691,7 +868,10 @@ Server.prototype._watch = function (watchPath) {
691
868
  usePolling,
692
869
  interval
693
870
  };
694
- const watcher = chokidar.watch(watchPath, options).on('change', () => {
871
+
872
+ const watcher = chokidar.watch(watchPath, options);
873
+
874
+ watcher.on('change', () => {
695
875
  this.sockWrite(this.sockets, 'content-changed');
696
876
  });
697
877
 
@@ -699,10 +879,14 @@ Server.prototype._watch = function (watchPath) {
699
879
  };
700
880
 
701
881
  Server.prototype.invalidate = function () {
702
- if (this.middleware) this.middleware.invalidate();
882
+ if (this.middleware) {
883
+ this.middleware.invalidate();
884
+ }
703
885
  };
704
886
 
705
- // Export this logic, so that other implementations, like task-runners can use it
706
- Server.addDevServerEntrypoints = require('./util/addDevServerEntrypoints');
887
+ // Export this logic,
888
+ // so that other implementations,
889
+ // like task-runners can use it
890
+ Server.addDevServerEntrypoints = require('./utils/addEntries');
707
891
 
708
892
  module.exports = Server;