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