webpack-dev-server 3.1.12 → 3.2.1

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