webpack-dev-server 3.1.3 → 3.1.7

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