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/CHANGELOG.md +23 -0
- package/README.md +0 -11
- package/bin/options.js +164 -0
- package/bin/utils.js +114 -0
- package/bin/webpack-dev-server.js +254 -334
- package/client/index.bundle.js +1 -1
- package/client/index.js +1 -0
- package/client/live.bundle.js +39 -1
- package/client/sockjs.bundle.js +1 -1
- package/lib/Server.js +381 -209
- package/lib/options.json +354 -0
- package/lib/utils/addEntries.js +55 -0
- package/lib/utils/createCertificate.js +65 -0
- package/lib/utils/createDomain.js +35 -0
- package/lib/utils/createLogger.js +26 -0
- package/package.json +43 -53
- package/ssl/server.pem +46 -0
- package/lib/OptionsValidationError.js +0 -152
- package/lib/createLog.js +0 -19
- package/lib/optionsSchema.json +0 -347
- package/lib/polyfills.js +0 -8
- package/lib/util/addDevServerEntrypoints.js +0 -40
- package/lib/util/createDomain.js +0 -23
- package/ssl/.DS_Store +0 -0
package/lib/Server.js
CHANGED
|
@@ -1,41 +1,58 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* eslint
|
|
4
|
-
|
|
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
|
|
11
|
-
const
|
|
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
|
-
|
|
15
|
-
const
|
|
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
|
-
|
|
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
|
|
37
|
+
const createLogger = require('./utils/createLogger');
|
|
38
|
+
const createCertificate = require('./utils/createCertificate');
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
62
|
-
percent
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
114
|
+
compiler.compilers.forEach(addHooks);
|
|
80
115
|
} else {
|
|
81
|
-
|
|
116
|
+
addHooks(compiler);
|
|
82
117
|
}
|
|
83
118
|
|
|
84
119
|
// Init express server
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
|
197
|
+
const html = item.substr(0, item.length - 3);
|
|
198
|
+
|
|
136
199
|
res.write('<li><a href="');
|
|
137
|
-
res.write(baseUrl +
|
|
200
|
+
res.write(baseUrl + html);
|
|
138
201
|
res.write('">');
|
|
139
|
-
res.write(
|
|
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(
|
|
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
|
-
|
|
162
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
291
|
-
|
|
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(
|
|
301
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
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') {
|
|
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(
|
|
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 = [
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
485
|
+
|
|
486
|
+
if (contentBase !== false) {
|
|
487
|
+
defaultFeatures.push('contentBaseFiles');
|
|
488
|
+
}
|
|
375
489
|
}
|
|
490
|
+
|
|
376
491
|
defaultFeatures.push('magicHtml');
|
|
377
|
-
|
|
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) {
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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:
|
|
530
|
-
//
|
|
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))
|
|
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')
|
|
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))
|
|
656
|
+
if (hostname === allowedHost.substring(1)) {
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
548
659
|
// "*.example.com"
|
|
549
|
-
if (hostname.endsWith(allowedHost))
|
|
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.
|
|
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
|
-
|
|
561
|
-
|
|
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.
|
|
691
|
+
this.hostname = hostname;
|
|
692
|
+
|
|
571
693
|
const returnValue = this.listeningApp.listen(port, hostname, (err) => {
|
|
572
|
-
const
|
|
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
|
-
|
|
586
|
-
if (!
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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.
|
|
730
|
+
if (this.hot) {
|
|
731
|
+
this.sockWrite([ connection ], 'hot');
|
|
732
|
+
}
|
|
602
733
|
|
|
603
|
-
if (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.
|
|
742
|
+
if (this.clientLogLevel) {
|
|
743
|
+
this.sockWrite([ connection ], 'log-level', this.clientLogLevel);
|
|
744
|
+
}
|
|
606
745
|
|
|
607
|
-
if (this.
|
|
746
|
+
if (!this._stats) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
608
749
|
|
|
609
|
-
|
|
610
|
-
this._sendStats([conn], this._stats.toJson(clientStats), true);
|
|
750
|
+
this._sendStats([ connection ], this._stats.toJson(STATS), true);
|
|
611
751
|
});
|
|
612
752
|
|
|
613
|
-
|
|
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 (
|
|
626
|
-
this.sockets.forEach((
|
|
627
|
-
|
|
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(
|
|
779
|
+
this.middleware.close(cb);
|
|
638
780
|
});
|
|
639
781
|
};
|
|
640
782
|
|
|
641
783
|
Server.prototype.sockWrite = function (sockets, type, data) {
|
|
642
|
-
sockets.forEach((
|
|
643
|
-
|
|
644
|
-
type,
|
|
645
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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 (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
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;
|
|
682
|
-
const interval = typeof this.watchOptions.poll === 'number'
|
|
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
|
-
|
|
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)
|
|
871
|
+
if (this.middleware) {
|
|
872
|
+
this.middleware.invalidate();
|
|
873
|
+
}
|
|
704
874
|
};
|
|
705
875
|
|
|
706
|
-
// Export this logic,
|
|
707
|
-
|
|
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;
|