webpack-dev-server 3.3.1 → 3.5.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/CHANGELOG.md +60 -1
- package/README.md +17 -10
- package/bin/options.js +7 -1
- package/bin/webpack-dev-server.js +22 -99
- package/client/index.bundle.js +1 -1
- package/client/index.js +89 -116
- package/client/live.bundle.js +10 -10
- package/client/sockjs.bundle.js +1 -1
- package/client/utils/getCurrentScriptSource.js +22 -0
- package/client/utils/reloadApp.js +54 -0
- package/client/utils/sendMessage.js +14 -0
- package/lib/Server.js +559 -599
- package/lib/options.json +246 -177
- package/lib/servers/BaseServer.js +9 -0
- package/lib/servers/SockJSServer.js +64 -0
- package/lib/servers/WebsocketServer.js +8 -0
- package/lib/utils/addEntries.js +45 -14
- package/lib/utils/createCertificate.js +2 -2
- package/lib/utils/createConfig.js +16 -0
- package/lib/utils/defaultPort.js +3 -0
- package/lib/utils/findPort.js +22 -29
- package/lib/utils/getCertificate.js +45 -0
- package/lib/utils/processOptions.js +29 -0
- package/lib/utils/routes.js +103 -0
- package/lib/utils/runOpen.js +21 -0
- package/lib/utils/setupExitSignals.js +21 -0
- package/lib/utils/status.js +3 -14
- package/package.json +58 -61
- package/client/webpack.config.js +0 -14
package/lib/Server.js
CHANGED
|
@@ -1,153 +1,169 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/* eslint-disable
|
|
4
|
-
import/order,
|
|
5
4
|
no-shadow,
|
|
6
5
|
no-undefined,
|
|
7
6
|
func-names
|
|
8
7
|
*/
|
|
9
8
|
const fs = require('fs');
|
|
10
9
|
const path = require('path');
|
|
11
|
-
|
|
12
|
-
const ip = require('ip');
|
|
13
10
|
const tls = require('tls');
|
|
14
11
|
const url = require('url');
|
|
15
12
|
const http = require('http');
|
|
16
13
|
const https = require('https');
|
|
17
|
-
const
|
|
18
|
-
|
|
14
|
+
const ip = require('ip');
|
|
19
15
|
const semver = require('semver');
|
|
20
|
-
|
|
21
16
|
const killable = require('killable');
|
|
22
|
-
|
|
23
|
-
const del = require('del');
|
|
24
17
|
const chokidar = require('chokidar');
|
|
25
|
-
|
|
26
18
|
const express = require('express');
|
|
27
|
-
|
|
28
|
-
const compress = require('compression');
|
|
29
|
-
const serveIndex = require('serve-index');
|
|
30
19
|
const httpProxyMiddleware = require('http-proxy-middleware');
|
|
31
20
|
const historyApiFallback = require('connect-history-api-fallback');
|
|
32
|
-
|
|
21
|
+
const compress = require('compression');
|
|
22
|
+
const serveIndex = require('serve-index');
|
|
33
23
|
const webpack = require('webpack');
|
|
34
24
|
const webpackDevMiddleware = require('webpack-dev-middleware');
|
|
35
|
-
|
|
25
|
+
const validateOptions = require('schema-utils');
|
|
36
26
|
const updateCompiler = require('./utils/updateCompiler');
|
|
37
27
|
const createLogger = require('./utils/createLogger');
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
28
|
+
const getCertificate = require('./utils/getCertificate');
|
|
29
|
+
const status = require('./utils/status');
|
|
30
|
+
const createDomain = require('./utils/createDomain');
|
|
31
|
+
const runBonjour = require('./utils/runBonjour');
|
|
32
|
+
const routes = require('./utils/routes');
|
|
41
33
|
const schema = require('./options.json');
|
|
42
|
-
|
|
43
|
-
// Workaround for sockjs@~0.3.19
|
|
44
|
-
// sockjs will remove Origin header, however Origin header is required for checking host.
|
|
45
|
-
// See https://github.com/webpack/webpack-dev-server/issues/1604 for more information
|
|
46
|
-
{
|
|
47
|
-
// eslint-disable-next-line global-require
|
|
48
|
-
const SockjsSession = require('sockjs/lib/transport').Session;
|
|
49
|
-
const decorateConnection = SockjsSession.prototype.decorateConnection;
|
|
50
|
-
SockjsSession.prototype.decorateConnection = function(req) {
|
|
51
|
-
decorateConnection.call(this, req);
|
|
52
|
-
const connection = this.connection;
|
|
53
|
-
if (
|
|
54
|
-
connection.headers &&
|
|
55
|
-
!('origin' in connection.headers) &&
|
|
56
|
-
'origin' in req.headers
|
|
57
|
-
) {
|
|
58
|
-
connection.headers.origin = req.headers.origin;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
34
|
+
const SockJSServer = require('./servers/SockJSServer');
|
|
62
35
|
|
|
63
36
|
// Workaround for node ^8.6.0, ^9.0.0
|
|
64
37
|
// DEFAULT_ECDH_CURVE is default to prime256v1 in these version
|
|
65
38
|
// breaking connection when certificate is not signed with prime256v1
|
|
66
39
|
// change it to auto allows OpenSSL to select the curve automatically
|
|
67
|
-
// See https://github.com/nodejs/node/issues/16196 for more
|
|
40
|
+
// See https://github.com/nodejs/node/issues/16196 for more information
|
|
68
41
|
if (semver.satisfies(process.version, '8.6.0 - 9')) {
|
|
69
42
|
tls.DEFAULT_ECDH_CURVE = 'auto';
|
|
70
43
|
}
|
|
71
44
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
all: false,
|
|
76
|
-
hash: true,
|
|
77
|
-
assets: true,
|
|
78
|
-
warnings: true,
|
|
79
|
-
errors: true,
|
|
80
|
-
errorDetails: false,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
45
|
+
if (!process.env.WEBPACK_DEV_SERVER) {
|
|
46
|
+
process.env.WEBPACK_DEV_SERVER = true;
|
|
47
|
+
}
|
|
83
48
|
|
|
49
|
+
class Server {
|
|
84
50
|
constructor(compiler, options = {}, _log) {
|
|
85
|
-
this.log = _log || createLogger(options);
|
|
86
|
-
|
|
87
|
-
validateOptions(schema, options, 'webpack Dev Server');
|
|
88
|
-
|
|
89
51
|
if (options.lazy && !options.filename) {
|
|
90
52
|
throw new Error("'filename' option must be set in lazy mode.");
|
|
91
53
|
}
|
|
92
54
|
|
|
93
|
-
|
|
94
|
-
if (options.http2 && !options.https) {
|
|
95
|
-
options.https = true;
|
|
96
|
-
}
|
|
55
|
+
validateOptions(schema, options, 'webpack Dev Server');
|
|
97
56
|
|
|
98
57
|
updateCompiler(compiler, options);
|
|
99
58
|
|
|
59
|
+
this.compiler = compiler;
|
|
60
|
+
this.options = options;
|
|
61
|
+
|
|
62
|
+
// Setup default value
|
|
63
|
+
this.options.contentBase =
|
|
64
|
+
this.options.contentBase !== undefined
|
|
65
|
+
? this.options.contentBase
|
|
66
|
+
: process.cwd();
|
|
67
|
+
|
|
68
|
+
this.log = _log || createLogger(options);
|
|
69
|
+
|
|
100
70
|
this.originalStats =
|
|
101
|
-
options.stats && Object.keys(options.stats).length
|
|
71
|
+
this.options.stats && Object.keys(this.options.stats).length
|
|
72
|
+
? this.options.stats
|
|
73
|
+
: {};
|
|
102
74
|
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
105
|
-
this.progress = options.progress;
|
|
75
|
+
this.sockets = [];
|
|
76
|
+
this.contentBaseWatchers = [];
|
|
106
77
|
|
|
107
|
-
this.
|
|
78
|
+
// TODO this.<property> is deprecated (remove them in next major release.) in favor this.options.<property>
|
|
79
|
+
this.hot = this.options.hot || this.options.hotOnly;
|
|
80
|
+
this.headers = this.options.headers;
|
|
81
|
+
this.progress = this.options.progress;
|
|
108
82
|
|
|
109
|
-
this.
|
|
110
|
-
this.clientLogLevel = options.clientLogLevel;
|
|
83
|
+
this.serveIndex = this.options.serveIndex;
|
|
111
84
|
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
114
|
-
this.disableHostCheck = !!options.disableHostCheck;
|
|
85
|
+
this.clientOverlay = this.options.overlay;
|
|
86
|
+
this.clientLogLevel = this.options.clientLogLevel;
|
|
115
87
|
|
|
116
|
-
this.
|
|
88
|
+
this.publicHost = this.options.public;
|
|
89
|
+
this.allowedHosts = this.options.allowedHosts;
|
|
90
|
+
this.disableHostCheck = !!this.options.disableHostCheck;
|
|
91
|
+
|
|
92
|
+
if (!this.options.watchOptions) {
|
|
93
|
+
this.options.watchOptions = {};
|
|
94
|
+
}
|
|
95
|
+
// Ignoring node_modules folder by default
|
|
96
|
+
this.options.watchOptions.ignored = this.options.watchOptions.ignored || [
|
|
97
|
+
/node_modules/,
|
|
98
|
+
];
|
|
99
|
+
this.watchOptions = this.options.watchOptions;
|
|
117
100
|
|
|
118
|
-
this.watchOptions = options.watchOptions || {};
|
|
119
|
-
this.contentBaseWatchers = [];
|
|
120
101
|
// Replace leading and trailing slashes to normalize path
|
|
121
102
|
this.sockPath = `/${
|
|
122
|
-
options.sockPath
|
|
123
|
-
? options.sockPath.replace(/^\/|\/$/g, '')
|
|
103
|
+
this.options.sockPath
|
|
104
|
+
? this.options.sockPath.replace(/^\/|\/$/g, '')
|
|
124
105
|
: 'sockjs-node'
|
|
125
106
|
}`;
|
|
126
107
|
|
|
127
|
-
// Listening for events
|
|
128
|
-
const invalidPlugin = () => {
|
|
129
|
-
this.sockWrite(this.sockets, 'invalid');
|
|
130
|
-
};
|
|
131
|
-
|
|
132
108
|
if (this.progress) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
percent = Math.floor(percent * 100);
|
|
109
|
+
this.setupProgressPlugin();
|
|
110
|
+
}
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
112
|
+
this.setupHooks();
|
|
113
|
+
this.setupApp();
|
|
114
|
+
this.setupCheckHostRoute();
|
|
115
|
+
this.setupDevMiddleware();
|
|
140
116
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
117
|
+
// set express routes
|
|
118
|
+
routes(this.app, this.middleware, this.options);
|
|
119
|
+
|
|
120
|
+
// Keep track of websocket proxies for external websocket upgrade.
|
|
121
|
+
this.websocketProxies = [];
|
|
122
|
+
|
|
123
|
+
this.setupFeatures();
|
|
124
|
+
this.setupHttps();
|
|
125
|
+
this.createServer();
|
|
144
126
|
|
|
145
|
-
|
|
127
|
+
killable(this.listeningApp);
|
|
128
|
+
|
|
129
|
+
// Proxy websockets without the initial http request
|
|
130
|
+
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
131
|
+
this.websocketProxies.forEach(function(wsProxy) {
|
|
132
|
+
this.listeningApp.on('upgrade', wsProxy.upgrade);
|
|
133
|
+
}, this);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setupProgressPlugin() {
|
|
137
|
+
const progressPlugin = new webpack.ProgressPlugin(
|
|
138
|
+
(percent, msg, addInfo) => {
|
|
139
|
+
percent = Math.floor(percent * 100);
|
|
140
|
+
|
|
141
|
+
if (percent === 100) {
|
|
142
|
+
msg = 'Compilation completed';
|
|
146
143
|
}
|
|
147
|
-
);
|
|
148
144
|
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
if (addInfo) {
|
|
146
|
+
msg = `${msg} (${addInfo})`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.sockWrite(this.sockets, 'progress-update', { percent, msg });
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
progressPlugin.apply(this.compiler);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
setupApp() {
|
|
157
|
+
// Init express server
|
|
158
|
+
// eslint-disable-next-line new-cap
|
|
159
|
+
this.app = new express();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setupHooks() {
|
|
163
|
+
// Listening for events
|
|
164
|
+
const invalidPlugin = () => {
|
|
165
|
+
this.sockWrite(this.sockets, 'invalid');
|
|
166
|
+
};
|
|
151
167
|
|
|
152
168
|
const addHooks = (compiler) => {
|
|
153
169
|
const { compile, invalid, done } = compiler.hooks;
|
|
@@ -160,527 +176,450 @@ class Server {
|
|
|
160
176
|
});
|
|
161
177
|
};
|
|
162
178
|
|
|
163
|
-
if (compiler.compilers) {
|
|
164
|
-
compiler.compilers.forEach(addHooks);
|
|
179
|
+
if (this.compiler.compilers) {
|
|
180
|
+
this.compiler.compilers.forEach(addHooks);
|
|
165
181
|
} else {
|
|
166
|
-
addHooks(compiler);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Init express server
|
|
170
|
-
// eslint-disable-next-line
|
|
171
|
-
const app = (this.app = new express());
|
|
172
|
-
|
|
173
|
-
// ref: https://github.com/webpack/webpack-dev-server/issues/1575
|
|
174
|
-
// ref: https://github.com/webpack/webpack-dev-server/issues/1724
|
|
175
|
-
// remove this when send@^0.16.3
|
|
176
|
-
if (express.static && express.static.mime && express.static.mime.types) {
|
|
177
|
-
express.static.mime.types.wasm = 'application/wasm';
|
|
182
|
+
addHooks(this.compiler);
|
|
178
183
|
}
|
|
184
|
+
}
|
|
179
185
|
|
|
180
|
-
|
|
186
|
+
setupCheckHostRoute() {
|
|
187
|
+
this.app.all('*', (req, res, next) => {
|
|
181
188
|
if (this.checkHost(req.headers)) {
|
|
182
189
|
return next();
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
res.send('Invalid Host header');
|
|
186
193
|
});
|
|
194
|
+
}
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
setupDevMiddleware() {
|
|
190
197
|
// middleware for serving webpack bundle
|
|
191
198
|
this.middleware = webpackDevMiddleware(
|
|
192
|
-
compiler,
|
|
193
|
-
Object.assign({}, options,
|
|
199
|
+
this.compiler,
|
|
200
|
+
Object.assign({}, this.options, { logLevel: this.log.options.level })
|
|
194
201
|
);
|
|
202
|
+
}
|
|
195
203
|
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
setupCompressFeature() {
|
|
205
|
+
this.app.use(compress());
|
|
206
|
+
}
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
setupProxyFeature() {
|
|
209
|
+
/**
|
|
210
|
+
* Assume a proxy configuration specified as:
|
|
211
|
+
* proxy: {
|
|
212
|
+
* 'context': { options }
|
|
213
|
+
* }
|
|
214
|
+
* OR
|
|
215
|
+
* proxy: {
|
|
216
|
+
* 'context': 'target'
|
|
217
|
+
* }
|
|
218
|
+
*/
|
|
219
|
+
if (!Array.isArray(this.options.proxy)) {
|
|
220
|
+
if (Object.prototype.hasOwnProperty.call(this.options.proxy, 'target')) {
|
|
221
|
+
this.options.proxy = [this.options.proxy];
|
|
222
|
+
} else {
|
|
223
|
+
this.options.proxy = Object.keys(this.options.proxy).map((context) => {
|
|
224
|
+
let proxyOptions;
|
|
225
|
+
// For backwards compatibility reasons.
|
|
226
|
+
const correctedContext = context
|
|
227
|
+
.replace(/^\*$/, '**')
|
|
228
|
+
.replace(/\/\*$/, '');
|
|
229
|
+
|
|
230
|
+
if (typeof this.options.proxy[context] === 'string') {
|
|
231
|
+
proxyOptions = {
|
|
232
|
+
context: correctedContext,
|
|
233
|
+
target: this.options.proxy[context],
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
proxyOptions = Object.assign({}, this.options.proxy[context]);
|
|
237
|
+
proxyOptions.context = correctedContext;
|
|
238
|
+
}
|
|
203
239
|
|
|
204
|
-
|
|
205
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
240
|
+
proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
|
|
206
241
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
242
|
+
return proxyOptions;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
211
246
|
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
const getProxyMiddleware = (proxyConfig) => {
|
|
248
|
+
const context = proxyConfig.context || proxyConfig.path;
|
|
214
249
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
250
|
+
// It is possible to use the `bypass` method without a `target`.
|
|
251
|
+
// However, the proxy middleware has no use in this case, and will fail to instantiate.
|
|
252
|
+
if (proxyConfig.target) {
|
|
253
|
+
return httpProxyMiddleware(context, proxyConfig);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Assume a proxy configuration specified as:
|
|
258
|
+
* proxy: [
|
|
259
|
+
* {
|
|
260
|
+
* context: ...,
|
|
261
|
+
* ...options...
|
|
262
|
+
* },
|
|
263
|
+
* // or:
|
|
264
|
+
* function() {
|
|
265
|
+
* return {
|
|
266
|
+
* context: ...,
|
|
267
|
+
* ...options...
|
|
268
|
+
* };
|
|
269
|
+
* }
|
|
270
|
+
* ]
|
|
271
|
+
*/
|
|
272
|
+
this.options.proxy.forEach((proxyConfigOrCallback) => {
|
|
273
|
+
let proxyConfig;
|
|
274
|
+
let proxyMiddleware;
|
|
275
|
+
|
|
276
|
+
if (typeof proxyConfigOrCallback === 'function') {
|
|
277
|
+
proxyConfig = proxyConfigOrCallback();
|
|
278
|
+
} else {
|
|
279
|
+
proxyConfig = proxyConfigOrCallback;
|
|
280
|
+
}
|
|
219
281
|
|
|
220
|
-
|
|
221
|
-
|
|
282
|
+
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
283
|
+
|
|
284
|
+
if (proxyConfig.ws) {
|
|
285
|
+
this.websocketProxies.push(proxyMiddleware);
|
|
286
|
+
}
|
|
222
287
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
288
|
+
this.app.use((req, res, next) => {
|
|
289
|
+
if (typeof proxyConfigOrCallback === 'function') {
|
|
290
|
+
const newProxyConfig = proxyConfigOrCallback();
|
|
291
|
+
|
|
292
|
+
if (newProxyConfig !== proxyConfig) {
|
|
293
|
+
proxyConfig = newProxyConfig;
|
|
294
|
+
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// - Check if we have a bypass function defined
|
|
299
|
+
// - In case the bypass function is defined we'll retrieve the
|
|
300
|
+
// bypassUrl from it otherwise byPassUrl would be null
|
|
301
|
+
const isByPassFuncDefined = typeof proxyConfig.bypass === 'function';
|
|
302
|
+
const bypassUrl = isByPassFuncDefined
|
|
303
|
+
? proxyConfig.bypass(req, res, proxyConfig)
|
|
304
|
+
: null;
|
|
305
|
+
|
|
306
|
+
if (typeof bypassUrl === 'boolean') {
|
|
307
|
+
// skip the proxy
|
|
308
|
+
req.url = null;
|
|
309
|
+
next();
|
|
310
|
+
} else if (typeof bypassUrl === 'string') {
|
|
311
|
+
// byPass to that url
|
|
312
|
+
req.url = bypassUrl;
|
|
313
|
+
next();
|
|
314
|
+
} else if (proxyMiddleware) {
|
|
315
|
+
return proxyMiddleware(req, res, next);
|
|
316
|
+
} else {
|
|
317
|
+
next();
|
|
318
|
+
}
|
|
319
|
+
});
|
|
226
320
|
});
|
|
321
|
+
}
|
|
227
322
|
|
|
228
|
-
|
|
229
|
-
|
|
323
|
+
setupHistoryApiFallbackFeature() {
|
|
324
|
+
const fallback =
|
|
325
|
+
typeof this.options.historyApiFallback === 'object'
|
|
326
|
+
? this.options.historyApiFallback
|
|
327
|
+
: null;
|
|
230
328
|
|
|
231
|
-
|
|
232
|
-
|
|
329
|
+
// Fall back to /index.html if nothing else matches.
|
|
330
|
+
this.app.use(historyApiFallback(fallback));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
setupStaticFeature() {
|
|
334
|
+
const contentBase = this.options.contentBase;
|
|
335
|
+
|
|
336
|
+
if (Array.isArray(contentBase)) {
|
|
337
|
+
contentBase.forEach((item) => {
|
|
338
|
+
this.app.get('*', express.static(item));
|
|
339
|
+
});
|
|
340
|
+
} else if (/^(https?:)?\/\//.test(contentBase)) {
|
|
341
|
+
this.log.warn(
|
|
342
|
+
'Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
|
|
233
343
|
);
|
|
234
344
|
|
|
235
|
-
|
|
236
|
-
|
|
345
|
+
this.log.warn(
|
|
346
|
+
'proxy: {\n\t"*": "<your current contentBase configuration>"\n}'
|
|
237
347
|
);
|
|
238
348
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
res.write('<ul>');
|
|
245
|
-
|
|
246
|
-
content.forEach((item) => {
|
|
247
|
-
const p = `${basePath}/${item}`;
|
|
248
|
-
|
|
249
|
-
if (filesystem.statSync(p).isFile()) {
|
|
250
|
-
res.write('<li><a href="');
|
|
251
|
-
res.write(baseUrl + item);
|
|
252
|
-
res.write('">');
|
|
253
|
-
res.write(item);
|
|
254
|
-
res.write('</a></li>');
|
|
255
|
-
|
|
256
|
-
if (/\.js$/.test(item)) {
|
|
257
|
-
const html = item.substr(0, item.length - 3);
|
|
258
|
-
|
|
259
|
-
res.write('<li><a href="');
|
|
260
|
-
res.write(baseUrl + html);
|
|
261
|
-
res.write('">');
|
|
262
|
-
res.write(html);
|
|
263
|
-
res.write('</a> (magic html for ');
|
|
264
|
-
res.write(item);
|
|
265
|
-
res.write(') (<a href="');
|
|
266
|
-
res.write(
|
|
267
|
-
baseUrl.replace(
|
|
268
|
-
// eslint-disable-next-line
|
|
269
|
-
/(^(https?:\/\/[^\/]+)?\/)/,
|
|
270
|
-
'$1webpack-dev-server/'
|
|
271
|
-
) + html
|
|
272
|
-
);
|
|
273
|
-
res.write('">webpack-dev-server</a>)</li>');
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
res.write('<li>');
|
|
277
|
-
res.write(item);
|
|
278
|
-
res.write('<br>');
|
|
349
|
+
// Redirect every request to contentBase
|
|
350
|
+
this.app.get('*', (req, res) => {
|
|
351
|
+
res.writeHead(302, {
|
|
352
|
+
Location: contentBase + req.path + (req._parsedUrl.search || ''),
|
|
353
|
+
});
|
|
279
354
|
|
|
280
|
-
|
|
355
|
+
res.end();
|
|
356
|
+
});
|
|
357
|
+
} else if (typeof contentBase === 'number') {
|
|
358
|
+
this.log.warn(
|
|
359
|
+
'Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
|
|
360
|
+
);
|
|
281
361
|
|
|
282
|
-
|
|
283
|
-
|
|
362
|
+
this.log.warn(
|
|
363
|
+
'proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Redirect every request to the port contentBase
|
|
367
|
+
this.app.get('*', (req, res) => {
|
|
368
|
+
res.writeHead(302, {
|
|
369
|
+
Location: `//localhost:${contentBase}${req.path}${req._parsedUrl
|
|
370
|
+
.search || ''}`,
|
|
284
371
|
});
|
|
285
372
|
|
|
286
|
-
res.
|
|
287
|
-
}
|
|
373
|
+
res.end();
|
|
374
|
+
});
|
|
375
|
+
} else {
|
|
376
|
+
// route content request
|
|
377
|
+
this.app.get(
|
|
378
|
+
'*',
|
|
379
|
+
express.static(contentBase, this.options.staticOptions)
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
288
383
|
|
|
289
|
-
|
|
384
|
+
setupServeIndexFeature() {
|
|
385
|
+
const contentBase = this.options.contentBase;
|
|
290
386
|
|
|
291
|
-
|
|
292
|
-
|
|
387
|
+
if (Array.isArray(contentBase)) {
|
|
388
|
+
contentBase.forEach((item) => {
|
|
389
|
+
this.app.get('*', serveIndex(item));
|
|
390
|
+
});
|
|
391
|
+
} else if (
|
|
392
|
+
!/^(https?:)?\/\//.test(contentBase) &&
|
|
393
|
+
typeof contentBase !== 'number'
|
|
394
|
+
) {
|
|
395
|
+
this.app.get('*', serveIndex(contentBase));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
293
398
|
|
|
294
|
-
|
|
399
|
+
setupWatchStaticFeature() {
|
|
400
|
+
const contentBase = this.options.contentBase;
|
|
295
401
|
|
|
296
|
-
if (
|
|
297
|
-
contentBase
|
|
402
|
+
if (
|
|
403
|
+
/^(https?:)?\/\//.test(contentBase) ||
|
|
404
|
+
typeof contentBase === 'number'
|
|
405
|
+
) {
|
|
406
|
+
throw new Error('Watching remote files is not supported.');
|
|
407
|
+
} else if (Array.isArray(contentBase)) {
|
|
408
|
+
contentBase.forEach((item) => {
|
|
409
|
+
this._watch(item);
|
|
410
|
+
});
|
|
298
411
|
} else {
|
|
299
|
-
|
|
412
|
+
this._watch(contentBase);
|
|
300
413
|
}
|
|
414
|
+
}
|
|
301
415
|
|
|
302
|
-
|
|
303
|
-
|
|
416
|
+
setupBeforeFeature() {
|
|
417
|
+
// Todo rename onBeforeSetupMiddleware in next major release
|
|
418
|
+
// Todo pass only `this` argument
|
|
419
|
+
this.options.before(this.app, this, this.compiler);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setupMiddleware() {
|
|
423
|
+
this.app.use(this.middleware);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
setupAfterFeature() {
|
|
427
|
+
// Todo rename onAfterSetupMiddleware in next major release
|
|
428
|
+
// Todo pass only `this` argument
|
|
429
|
+
this.options.after(this.app, this, this.compiler);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
setupHeadersFeature() {
|
|
433
|
+
this.app.all('*', this.setContentHeaders.bind(this));
|
|
434
|
+
}
|
|
304
435
|
|
|
436
|
+
setupMagicHtmlFeature() {
|
|
437
|
+
this.app.get('*', this.serveMagicHtml.bind(this));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
setupSetupFeature() {
|
|
441
|
+
this.log.warn(
|
|
442
|
+
'The `setup` option is deprecated and will be removed in v4. Please update your config to use `before`'
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
this.options.setup(this.app, this);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
setupFeatures() {
|
|
305
449
|
const features = {
|
|
306
450
|
compress: () => {
|
|
307
|
-
if (options.compress) {
|
|
308
|
-
|
|
309
|
-
app.use(compress());
|
|
451
|
+
if (this.options.compress) {
|
|
452
|
+
this.setupCompressFeature();
|
|
310
453
|
}
|
|
311
454
|
},
|
|
312
455
|
proxy: () => {
|
|
313
|
-
if (options.proxy) {
|
|
314
|
-
|
|
315
|
-
* Assume a proxy configuration specified as:
|
|
316
|
-
* proxy: {
|
|
317
|
-
* 'context': { options }
|
|
318
|
-
* }
|
|
319
|
-
* OR
|
|
320
|
-
* proxy: {
|
|
321
|
-
* 'context': 'target'
|
|
322
|
-
* }
|
|
323
|
-
*/
|
|
324
|
-
if (!Array.isArray(options.proxy)) {
|
|
325
|
-
if (Object.prototype.hasOwnProperty.call(options.proxy, 'target')) {
|
|
326
|
-
options.proxy = [options.proxy];
|
|
327
|
-
} else {
|
|
328
|
-
options.proxy = Object.keys(options.proxy).map((context) => {
|
|
329
|
-
let proxyOptions;
|
|
330
|
-
// For backwards compatibility reasons.
|
|
331
|
-
const correctedContext = context
|
|
332
|
-
.replace(/^\*$/, '**')
|
|
333
|
-
.replace(/\/\*$/, '');
|
|
334
|
-
|
|
335
|
-
if (typeof options.proxy[context] === 'string') {
|
|
336
|
-
proxyOptions = {
|
|
337
|
-
context: correctedContext,
|
|
338
|
-
target: options.proxy[context],
|
|
339
|
-
};
|
|
340
|
-
} else {
|
|
341
|
-
proxyOptions = Object.assign({}, options.proxy[context]);
|
|
342
|
-
proxyOptions.context = correctedContext;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
|
|
346
|
-
|
|
347
|
-
return proxyOptions;
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const getProxyMiddleware = (proxyConfig) => {
|
|
353
|
-
const context = proxyConfig.context || proxyConfig.path;
|
|
354
|
-
// It is possible to use the `bypass` method without a `target`.
|
|
355
|
-
// However, the proxy middleware has no use in this case, and will fail to instantiate.
|
|
356
|
-
if (proxyConfig.target) {
|
|
357
|
-
return httpProxyMiddleware(context, proxyConfig);
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
/**
|
|
361
|
-
* Assume a proxy configuration specified as:
|
|
362
|
-
* proxy: [
|
|
363
|
-
* {
|
|
364
|
-
* context: ...,
|
|
365
|
-
* ...options...
|
|
366
|
-
* },
|
|
367
|
-
* // or:
|
|
368
|
-
* function() {
|
|
369
|
-
* return {
|
|
370
|
-
* context: ...,
|
|
371
|
-
* ...options...
|
|
372
|
-
* };
|
|
373
|
-
* }
|
|
374
|
-
* ]
|
|
375
|
-
*/
|
|
376
|
-
options.proxy.forEach((proxyConfigOrCallback) => {
|
|
377
|
-
let proxyConfig;
|
|
378
|
-
let proxyMiddleware;
|
|
379
|
-
|
|
380
|
-
if (typeof proxyConfigOrCallback === 'function') {
|
|
381
|
-
proxyConfig = proxyConfigOrCallback();
|
|
382
|
-
} else {
|
|
383
|
-
proxyConfig = proxyConfigOrCallback;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
387
|
-
|
|
388
|
-
if (proxyConfig.ws) {
|
|
389
|
-
websocketProxies.push(proxyMiddleware);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
app.use((req, res, next) => {
|
|
393
|
-
if (typeof proxyConfigOrCallback === 'function') {
|
|
394
|
-
const newProxyConfig = proxyConfigOrCallback();
|
|
395
|
-
|
|
396
|
-
if (newProxyConfig !== proxyConfig) {
|
|
397
|
-
proxyConfig = newProxyConfig;
|
|
398
|
-
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// - Check if we have a bypass function defined
|
|
403
|
-
// - In case the bypass function is defined we'll retrieve the
|
|
404
|
-
// bypassUrl from it otherwise byPassUrl would be null
|
|
405
|
-
const isByPassFuncDefined =
|
|
406
|
-
typeof proxyConfig.bypass === 'function';
|
|
407
|
-
const bypassUrl = isByPassFuncDefined
|
|
408
|
-
? proxyConfig.bypass(req, res, proxyConfig)
|
|
409
|
-
: null;
|
|
410
|
-
|
|
411
|
-
if (typeof bypassUrl === 'boolean') {
|
|
412
|
-
// skip the proxy
|
|
413
|
-
req.url = null;
|
|
414
|
-
next();
|
|
415
|
-
} else if (typeof bypassUrl === 'string') {
|
|
416
|
-
// byPass to that url
|
|
417
|
-
req.url = bypassUrl;
|
|
418
|
-
next();
|
|
419
|
-
} else if (proxyMiddleware) {
|
|
420
|
-
return proxyMiddleware(req, res, next);
|
|
421
|
-
} else {
|
|
422
|
-
next();
|
|
423
|
-
}
|
|
424
|
-
});
|
|
425
|
-
});
|
|
456
|
+
if (this.options.proxy) {
|
|
457
|
+
this.setupProxyFeature();
|
|
426
458
|
}
|
|
427
459
|
},
|
|
428
460
|
historyApiFallback: () => {
|
|
429
|
-
if (options.historyApiFallback) {
|
|
430
|
-
|
|
431
|
-
typeof options.historyApiFallback === 'object'
|
|
432
|
-
? options.historyApiFallback
|
|
433
|
-
: null;
|
|
434
|
-
// Fall back to /index.html if nothing else matches.
|
|
435
|
-
app.use(historyApiFallback(fallback));
|
|
461
|
+
if (this.options.historyApiFallback) {
|
|
462
|
+
this.setupHistoryApiFallbackFeature();
|
|
436
463
|
}
|
|
437
464
|
},
|
|
465
|
+
// Todo rename to `static` in future major release
|
|
438
466
|
contentBaseFiles: () => {
|
|
439
|
-
|
|
440
|
-
contentBase.forEach((item) => {
|
|
441
|
-
app.get('*', express.static(item));
|
|
442
|
-
});
|
|
443
|
-
} else if (/^(https?:)?\/\//.test(contentBase)) {
|
|
444
|
-
this.log.warn(
|
|
445
|
-
'Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
this.log.warn(
|
|
449
|
-
'proxy: {\n\t"*": "<your current contentBase configuration>"\n}'
|
|
450
|
-
);
|
|
451
|
-
// Redirect every request to contentBase
|
|
452
|
-
app.get('*', (req, res) => {
|
|
453
|
-
res.writeHead(302, {
|
|
454
|
-
Location: contentBase + req.path + (req._parsedUrl.search || ''),
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
res.end();
|
|
458
|
-
});
|
|
459
|
-
} else if (typeof contentBase === 'number') {
|
|
460
|
-
this.log.warn(
|
|
461
|
-
'Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.'
|
|
462
|
-
);
|
|
463
|
-
|
|
464
|
-
this.log.warn(
|
|
465
|
-
'proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'
|
|
466
|
-
);
|
|
467
|
-
// Redirect every request to the port contentBase
|
|
468
|
-
app.get('*', (req, res) => {
|
|
469
|
-
res.writeHead(302, {
|
|
470
|
-
Location: `//localhost:${contentBase}${req.path}${req._parsedUrl
|
|
471
|
-
.search || ''}`,
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
res.end();
|
|
475
|
-
});
|
|
476
|
-
} else {
|
|
477
|
-
// route content request
|
|
478
|
-
app.get('*', express.static(contentBase, options.staticOptions));
|
|
479
|
-
}
|
|
467
|
+
this.setupStaticFeature();
|
|
480
468
|
},
|
|
469
|
+
// Todo rename to `serveIndex` in future major release
|
|
481
470
|
contentBaseIndex: () => {
|
|
482
|
-
|
|
483
|
-
contentBase.forEach((item) => {
|
|
484
|
-
app.get('*', serveIndex(item));
|
|
485
|
-
});
|
|
486
|
-
} else if (
|
|
487
|
-
!/^(https?:)?\/\//.test(contentBase) &&
|
|
488
|
-
typeof contentBase !== 'number'
|
|
489
|
-
) {
|
|
490
|
-
app.get('*', serveIndex(contentBase));
|
|
491
|
-
}
|
|
471
|
+
this.setupServeIndexFeature();
|
|
492
472
|
},
|
|
473
|
+
// Todo rename to `watchStatic` in future major release
|
|
493
474
|
watchContentBase: () => {
|
|
494
|
-
|
|
495
|
-
/^(https?:)?\/\//.test(contentBase) ||
|
|
496
|
-
typeof contentBase === 'number'
|
|
497
|
-
) {
|
|
498
|
-
throw new Error('Watching remote files is not supported.');
|
|
499
|
-
} else if (Array.isArray(contentBase)) {
|
|
500
|
-
contentBase.forEach((item) => {
|
|
501
|
-
this._watch(item);
|
|
502
|
-
});
|
|
503
|
-
} else {
|
|
504
|
-
this._watch(contentBase);
|
|
505
|
-
}
|
|
475
|
+
this.setupWatchStaticFeature();
|
|
506
476
|
},
|
|
507
477
|
before: () => {
|
|
508
|
-
if (typeof options.before === 'function') {
|
|
509
|
-
|
|
478
|
+
if (typeof this.options.before === 'function') {
|
|
479
|
+
this.setupBeforeFeature();
|
|
510
480
|
}
|
|
511
481
|
},
|
|
512
482
|
middleware: () => {
|
|
513
483
|
// include our middleware to ensure
|
|
514
484
|
// it is able to handle '/index.html' request after redirect
|
|
515
|
-
|
|
485
|
+
this.setupMiddleware();
|
|
516
486
|
},
|
|
517
487
|
after: () => {
|
|
518
|
-
if (typeof options.after === 'function') {
|
|
519
|
-
|
|
488
|
+
if (typeof this.options.after === 'function') {
|
|
489
|
+
this.setupAfterFeature();
|
|
520
490
|
}
|
|
521
491
|
},
|
|
522
492
|
headers: () => {
|
|
523
|
-
|
|
493
|
+
this.setupHeadersFeature();
|
|
524
494
|
},
|
|
525
495
|
magicHtml: () => {
|
|
526
|
-
|
|
496
|
+
this.setupMagicHtmlFeature();
|
|
527
497
|
},
|
|
528
498
|
setup: () => {
|
|
529
|
-
if (typeof options.setup === 'function') {
|
|
530
|
-
this.
|
|
531
|
-
'The `setup` option is deprecated and will be removed in v4. Please update your config to use `before`'
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
options.setup(app, this);
|
|
499
|
+
if (typeof this.options.setup === 'function') {
|
|
500
|
+
this.setupSetupFeature();
|
|
535
501
|
}
|
|
536
502
|
},
|
|
537
503
|
};
|
|
538
504
|
|
|
539
|
-
const
|
|
505
|
+
const runnableFeatures = [];
|
|
540
506
|
|
|
541
|
-
|
|
542
|
-
|
|
507
|
+
// compress is placed last and uses unshift so that it will be the first middleware used
|
|
508
|
+
if (this.options.compress) {
|
|
509
|
+
runnableFeatures.push('compress');
|
|
543
510
|
}
|
|
544
511
|
|
|
545
|
-
|
|
546
|
-
|
|
512
|
+
runnableFeatures.push('setup', 'before', 'headers', 'middleware');
|
|
513
|
+
|
|
514
|
+
if (this.options.proxy) {
|
|
515
|
+
runnableFeatures.push('proxy', 'middleware');
|
|
547
516
|
}
|
|
548
517
|
|
|
549
|
-
if (options.
|
|
550
|
-
|
|
518
|
+
if (this.options.contentBase !== false) {
|
|
519
|
+
runnableFeatures.push('contentBaseFiles');
|
|
551
520
|
}
|
|
552
521
|
|
|
553
|
-
if (options.historyApiFallback) {
|
|
554
|
-
|
|
522
|
+
if (this.options.historyApiFallback) {
|
|
523
|
+
runnableFeatures.push('historyApiFallback', 'middleware');
|
|
555
524
|
|
|
556
|
-
if (contentBase !== false) {
|
|
557
|
-
|
|
525
|
+
if (this.options.contentBase !== false) {
|
|
526
|
+
runnableFeatures.push('contentBaseFiles');
|
|
558
527
|
}
|
|
559
528
|
}
|
|
560
529
|
|
|
561
|
-
defaultFeatures.push('magicHtml');
|
|
562
|
-
|
|
563
530
|
// checking if it's set to true or not set (Default : undefined => true)
|
|
564
531
|
this.serveIndex = this.serveIndex || this.serveIndex === undefined;
|
|
565
532
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (shouldHandleServeIndex) {
|
|
569
|
-
defaultFeatures.push('contentBaseIndex');
|
|
533
|
+
if (this.options.contentBase && this.serveIndex) {
|
|
534
|
+
runnableFeatures.push('contentBaseIndex');
|
|
570
535
|
}
|
|
571
536
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
defaultFeatures.unshift('compress');
|
|
537
|
+
if (this.options.watchContentBase) {
|
|
538
|
+
runnableFeatures.push('watchContentBase');
|
|
575
539
|
}
|
|
576
540
|
|
|
577
|
-
|
|
578
|
-
|
|
541
|
+
runnableFeatures.push('magicHtml');
|
|
542
|
+
|
|
543
|
+
if (this.options.after) {
|
|
544
|
+
runnableFeatures.push('after');
|
|
579
545
|
}
|
|
580
546
|
|
|
581
|
-
(options.features ||
|
|
547
|
+
(this.options.features || runnableFeatures).forEach((feature) => {
|
|
582
548
|
features[feature]();
|
|
583
549
|
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
setupHttps() {
|
|
553
|
+
// if the user enables http2, we can safely enable https
|
|
554
|
+
if (this.options.http2 && !this.options.https) {
|
|
555
|
+
this.options.https = true;
|
|
556
|
+
}
|
|
584
557
|
|
|
585
|
-
if (options.https) {
|
|
558
|
+
if (this.options.https) {
|
|
586
559
|
// for keep supporting CLI parameters
|
|
587
|
-
if (typeof options.https === 'boolean') {
|
|
588
|
-
options.https = {
|
|
589
|
-
ca: options.ca,
|
|
590
|
-
pfx: options.pfx,
|
|
591
|
-
key: options.key,
|
|
592
|
-
cert: options.cert,
|
|
593
|
-
passphrase: options.pfxPassphrase,
|
|
594
|
-
requestCert: options.requestCert || false,
|
|
560
|
+
if (typeof this.options.https === 'boolean') {
|
|
561
|
+
this.options.https = {
|
|
562
|
+
ca: this.options.ca,
|
|
563
|
+
pfx: this.options.pfx,
|
|
564
|
+
key: this.options.key,
|
|
565
|
+
cert: this.options.cert,
|
|
566
|
+
passphrase: this.options.pfxPassphrase,
|
|
567
|
+
requestCert: this.options.requestCert || false,
|
|
595
568
|
};
|
|
596
569
|
}
|
|
597
570
|
|
|
598
571
|
for (const property of ['ca', 'pfx', 'key', 'cert']) {
|
|
599
|
-
const value = options.https[property];
|
|
572
|
+
const value = this.options.https[property];
|
|
600
573
|
const isBuffer = value instanceof Buffer;
|
|
601
574
|
|
|
602
575
|
if (value && !isBuffer) {
|
|
603
576
|
let stats = null;
|
|
604
577
|
|
|
605
578
|
try {
|
|
606
|
-
stats = fs.lstatSync(value).isFile();
|
|
579
|
+
stats = fs.lstatSync(fs.realpathSync(value)).isFile();
|
|
607
580
|
} catch (error) {
|
|
608
581
|
// ignore error
|
|
609
582
|
}
|
|
610
583
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
options.https[property] = value;
|
|
616
|
-
}
|
|
584
|
+
// It is file
|
|
585
|
+
this.options.https[property] = stats
|
|
586
|
+
? fs.readFileSync(path.resolve(value))
|
|
587
|
+
: value;
|
|
617
588
|
}
|
|
618
589
|
}
|
|
619
590
|
|
|
620
591
|
let fakeCert;
|
|
621
592
|
|
|
622
|
-
if (!options.https.key || !options.https.cert) {
|
|
623
|
-
|
|
624
|
-
// Cycle certs every 24 hours
|
|
625
|
-
const certPath = path.join(__dirname, '../ssl/server.pem');
|
|
626
|
-
|
|
627
|
-
let certExists = fs.existsSync(certPath);
|
|
628
|
-
|
|
629
|
-
if (certExists) {
|
|
630
|
-
const certTtl = 1000 * 60 * 60 * 24;
|
|
631
|
-
const certStat = fs.statSync(certPath);
|
|
632
|
-
|
|
633
|
-
const now = new Date();
|
|
634
|
-
|
|
635
|
-
// cert is more than 30 days old, kill it with fire
|
|
636
|
-
if ((now - certStat.ctime) / certTtl > 30) {
|
|
637
|
-
this.log.info(
|
|
638
|
-
'SSL Certificate is more than 30 days old. Removing.'
|
|
639
|
-
);
|
|
640
|
-
|
|
641
|
-
del.sync([certPath], { force: true });
|
|
642
|
-
|
|
643
|
-
certExists = false;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
if (!certExists) {
|
|
648
|
-
this.log.info('Generating SSL Certificate');
|
|
649
|
-
|
|
650
|
-
const attrs = [{ name: 'commonName', value: 'localhost' }];
|
|
651
|
-
|
|
652
|
-
const pems = createCertificate(attrs);
|
|
653
|
-
|
|
654
|
-
fs.writeFileSync(certPath, pems.private + pems.cert, {
|
|
655
|
-
encoding: 'utf8',
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
fakeCert = fs.readFileSync(certPath);
|
|
593
|
+
if (!this.options.https.key || !this.options.https.cert) {
|
|
594
|
+
fakeCert = getCertificate(this.log);
|
|
660
595
|
}
|
|
661
596
|
|
|
662
|
-
options.https.key = options.https.key || fakeCert;
|
|
663
|
-
options.https.cert = options.https.cert || fakeCert;
|
|
664
|
-
|
|
665
|
-
// Only prevent HTTP/2 if http2 is explicitly set to false
|
|
666
|
-
const isHttp2 = options.http2 !== false;
|
|
597
|
+
this.options.https.key = this.options.https.key || fakeCert;
|
|
598
|
+
this.options.https.cert = this.options.https.cert || fakeCert;
|
|
667
599
|
|
|
668
600
|
// note that options.spdy never existed. The user was able
|
|
669
601
|
// to set options.https.spdy before, though it was not in the
|
|
670
602
|
// docs. Keep options.https.spdy if the user sets it for
|
|
671
|
-
// backwards
|
|
672
|
-
if (options.https.spdy) {
|
|
673
|
-
// for backwards
|
|
603
|
+
// backwards compatibility, but log a deprecation warning.
|
|
604
|
+
if (this.options.https.spdy) {
|
|
605
|
+
// for backwards compatibility: if options.https.spdy was passed in before,
|
|
674
606
|
// it was not altered in any way
|
|
675
607
|
this.log.warn(
|
|
676
608
|
'Providing custom spdy server options is deprecated and will be removed in the next major version.'
|
|
677
609
|
);
|
|
678
610
|
} else {
|
|
679
611
|
// if the normal https server gets this option, it will not affect it.
|
|
680
|
-
options.https.spdy = {
|
|
612
|
+
this.options.https.spdy = {
|
|
681
613
|
protocols: ['h2', 'http/1.1'],
|
|
682
614
|
};
|
|
683
615
|
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
createServer() {
|
|
620
|
+
if (this.options.https) {
|
|
621
|
+
// Only prevent HTTP/2 if http2 is explicitly set to false
|
|
622
|
+
const isHttp2 = this.options.http2 !== false;
|
|
684
623
|
|
|
685
624
|
// `spdy` is effectively unmaintained, and as a consequence of an
|
|
686
625
|
// implementation that extensively relies on Node’s non-public APIs, broken
|
|
@@ -692,33 +631,150 @@ class Server {
|
|
|
692
631
|
// - https://github.com/webpack/webpack-dev-server/issues/1449
|
|
693
632
|
// - https://github.com/expressjs/express/issues/3388
|
|
694
633
|
if (semver.gte(process.version, '10.0.0') || !isHttp2) {
|
|
695
|
-
if (options.http2) {
|
|
634
|
+
if (this.options.http2) {
|
|
696
635
|
// the user explicitly requested http2 but is not getting it because
|
|
697
636
|
// of the node version.
|
|
698
637
|
this.log.warn(
|
|
699
638
|
'HTTP/2 is currently unsupported for Node 10.0.0 and above, but will be supported once Express supports it'
|
|
700
639
|
);
|
|
701
640
|
}
|
|
702
|
-
this.listeningApp = https.createServer(options.https, app);
|
|
641
|
+
this.listeningApp = https.createServer(this.options.https, this.app);
|
|
703
642
|
} else {
|
|
704
|
-
/* eslint-disable global-require */
|
|
705
643
|
// The relevant issues are:
|
|
706
644
|
// https://github.com/spdy-http2/node-spdy/issues/350
|
|
707
645
|
// https://github.com/webpack/webpack-dev-server/issues/1592
|
|
708
|
-
|
|
709
|
-
|
|
646
|
+
// eslint-disable-next-line global-require
|
|
647
|
+
this.listeningApp = require('spdy').createServer(
|
|
648
|
+
this.options.https,
|
|
649
|
+
this.app
|
|
650
|
+
);
|
|
710
651
|
}
|
|
711
652
|
} else {
|
|
712
|
-
this.listeningApp = http.createServer(app);
|
|
653
|
+
this.listeningApp = http.createServer(this.app);
|
|
713
654
|
}
|
|
655
|
+
}
|
|
714
656
|
|
|
715
|
-
|
|
657
|
+
createSocketServer() {
|
|
658
|
+
this.socketServer = new SockJSServer(this);
|
|
716
659
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
660
|
+
this.socketServer.onConnection((connection) => {
|
|
661
|
+
if (!connection) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (
|
|
666
|
+
!this.checkHost(connection.headers) ||
|
|
667
|
+
!this.checkOrigin(connection.headers)
|
|
668
|
+
) {
|
|
669
|
+
this.sockWrite([connection], 'error', 'Invalid Host/Origin header');
|
|
670
|
+
|
|
671
|
+
connection.close();
|
|
672
|
+
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this.sockets.push(connection);
|
|
677
|
+
|
|
678
|
+
connection.on('close', () => {
|
|
679
|
+
const idx = this.sockets.indexOf(connection);
|
|
680
|
+
|
|
681
|
+
if (idx >= 0) {
|
|
682
|
+
this.sockets.splice(idx, 1);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
if (this.hot) {
|
|
687
|
+
this.sockWrite([connection], 'hot');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (this.options.liveReload !== false) {
|
|
691
|
+
this.sockWrite([connection], 'liveReload', this.options.liveReload);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (this.progress) {
|
|
695
|
+
this.sockWrite([connection], 'progress', this.progress);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (this.clientOverlay) {
|
|
699
|
+
this.sockWrite([connection], 'overlay', this.clientOverlay);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (this.clientLogLevel) {
|
|
703
|
+
this.sockWrite([connection], 'log-level', this.clientLogLevel);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (!this._stats) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
this._sendStats([connection], this.getStats(this._stats), true);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
showStatus() {
|
|
715
|
+
const suffix =
|
|
716
|
+
this.options.inline !== false || this.options.lazy === true
|
|
717
|
+
? '/'
|
|
718
|
+
: '/webpack-dev-server/';
|
|
719
|
+
const uri = `${createDomain(this.options, this.listeningApp)}${suffix}`;
|
|
720
|
+
|
|
721
|
+
status(
|
|
722
|
+
uri,
|
|
723
|
+
this.options,
|
|
724
|
+
this.log,
|
|
725
|
+
this.options.stats && this.options.stats.colors
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
listen(port, hostname, fn) {
|
|
730
|
+
this.hostname = hostname;
|
|
731
|
+
|
|
732
|
+
return this.listeningApp.listen(port, hostname, (err) => {
|
|
733
|
+
this.createSocketServer();
|
|
734
|
+
|
|
735
|
+
if (this.options.bonjour) {
|
|
736
|
+
runBonjour(this.options);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
this.showStatus();
|
|
740
|
+
|
|
741
|
+
if (fn) {
|
|
742
|
+
fn.call(this.listeningApp, err);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (typeof this.options.onListening === 'function') {
|
|
746
|
+
this.options.onListening(this);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
close(cb) {
|
|
752
|
+
this.sockets.forEach((socket) => {
|
|
753
|
+
this.socketServer.close(socket);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
this.sockets = [];
|
|
757
|
+
|
|
758
|
+
this.contentBaseWatchers.forEach((watcher) => {
|
|
759
|
+
watcher.close();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
this.contentBaseWatchers = [];
|
|
763
|
+
|
|
764
|
+
this.listeningApp.kill(() => {
|
|
765
|
+
this.middleware.close(cb);
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
static get DEFAULT_STATS() {
|
|
770
|
+
return {
|
|
771
|
+
all: false,
|
|
772
|
+
hash: true,
|
|
773
|
+
assets: true,
|
|
774
|
+
warnings: true,
|
|
775
|
+
errors: true,
|
|
776
|
+
errorDetails: false,
|
|
777
|
+
};
|
|
722
778
|
}
|
|
723
779
|
|
|
724
780
|
getStats(statsObj) {
|
|
@@ -740,7 +796,6 @@ class Server {
|
|
|
740
796
|
if (this.headers) {
|
|
741
797
|
// eslint-disable-next-line
|
|
742
798
|
for (const name in this.headers) {
|
|
743
|
-
// eslint-disable-line
|
|
744
799
|
res.setHeader(name, this.headers[name]);
|
|
745
800
|
}
|
|
746
801
|
}
|
|
@@ -790,7 +845,7 @@ class Server {
|
|
|
790
845
|
if (ip.isV4Format(hostname) || ip.isV6Format(hostname)) {
|
|
791
846
|
return true;
|
|
792
847
|
}
|
|
793
|
-
// always allow localhost host, for
|
|
848
|
+
// always allow localhost host, for convenience
|
|
794
849
|
if (hostname === 'localhost') {
|
|
795
850
|
return true;
|
|
796
851
|
}
|
|
@@ -818,7 +873,7 @@ class Server {
|
|
|
818
873
|
}
|
|
819
874
|
}
|
|
820
875
|
|
|
821
|
-
// allow hostname of listening
|
|
876
|
+
// allow hostname of listening address
|
|
822
877
|
if (hostname === this.hostname) {
|
|
823
878
|
return true;
|
|
824
879
|
}
|
|
@@ -826,7 +881,6 @@ class Server {
|
|
|
826
881
|
// also allow public hostname if provided
|
|
827
882
|
if (typeof this.publicHost === 'string') {
|
|
828
883
|
const idxPublic = this.publicHost.indexOf(':');
|
|
829
|
-
|
|
830
884
|
const publicHostname =
|
|
831
885
|
idxPublic >= 0 ? this.publicHost.substr(0, idxPublic) : this.publicHost;
|
|
832
886
|
|
|
@@ -839,105 +893,10 @@ class Server {
|
|
|
839
893
|
return false;
|
|
840
894
|
}
|
|
841
895
|
|
|
842
|
-
// delegate listen call and init sockjs
|
|
843
|
-
listen(port, hostname, fn) {
|
|
844
|
-
this.hostname = hostname;
|
|
845
|
-
|
|
846
|
-
return this.listeningApp.listen(port, hostname, (err) => {
|
|
847
|
-
const socket = sockjs.createServer({
|
|
848
|
-
// Use provided up-to-date sockjs-client
|
|
849
|
-
sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js',
|
|
850
|
-
// Limit useless logs
|
|
851
|
-
log: (severity, line) => {
|
|
852
|
-
if (severity === 'error') {
|
|
853
|
-
this.log.error(line);
|
|
854
|
-
} else {
|
|
855
|
-
this.log.debug(line);
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
socket.on('connection', (connection) => {
|
|
861
|
-
if (!connection) {
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (
|
|
866
|
-
!this.checkHost(connection.headers) ||
|
|
867
|
-
!this.checkOrigin(connection.headers)
|
|
868
|
-
) {
|
|
869
|
-
this.sockWrite([connection], 'error', 'Invalid Host/Origin header');
|
|
870
|
-
|
|
871
|
-
connection.close();
|
|
872
|
-
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
this.sockets.push(connection);
|
|
877
|
-
|
|
878
|
-
connection.on('close', () => {
|
|
879
|
-
const idx = this.sockets.indexOf(connection);
|
|
880
|
-
|
|
881
|
-
if (idx >= 0) {
|
|
882
|
-
this.sockets.splice(idx, 1);
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
if (this.hot) {
|
|
887
|
-
this.sockWrite([connection], 'hot');
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
if (this.progress) {
|
|
891
|
-
this.sockWrite([connection], 'progress', this.progress);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
if (this.clientOverlay) {
|
|
895
|
-
this.sockWrite([connection], 'overlay', this.clientOverlay);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (this.clientLogLevel) {
|
|
899
|
-
this.sockWrite([connection], 'log-level', this.clientLogLevel);
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
if (!this._stats) {
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
this._sendStats([connection], this.getStats(this._stats), true);
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
socket.installHandlers(this.listeningApp, {
|
|
910
|
-
prefix: this.sockPath,
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
if (fn) {
|
|
914
|
-
fn.call(this.listeningApp, err);
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
close(cb) {
|
|
920
|
-
this.sockets.forEach((socket) => {
|
|
921
|
-
socket.close();
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
this.sockets = [];
|
|
925
|
-
|
|
926
|
-
this.contentBaseWatchers.forEach((watcher) => {
|
|
927
|
-
watcher.close();
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
this.contentBaseWatchers = [];
|
|
931
|
-
|
|
932
|
-
this.listeningApp.kill(() => {
|
|
933
|
-
this.middleware.close(cb);
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
|
|
937
896
|
// eslint-disable-next-line
|
|
938
897
|
sockWrite(sockets, type, data) {
|
|
939
898
|
sockets.forEach((socket) => {
|
|
940
|
-
|
|
899
|
+
this.socketServer.send(socket, JSON.stringify({ type, data }));
|
|
941
900
|
});
|
|
942
901
|
}
|
|
943
902
|
|
|
@@ -999,7 +958,7 @@ class Server {
|
|
|
999
958
|
? this.watchOptions.poll
|
|
1000
959
|
: undefined;
|
|
1001
960
|
|
|
1002
|
-
const
|
|
961
|
+
const watchOptions = {
|
|
1003
962
|
ignoreInitial: true,
|
|
1004
963
|
persistent: true,
|
|
1005
964
|
followSymlinks: false,
|
|
@@ -1011,18 +970,19 @@ class Server {
|
|
|
1011
970
|
interval,
|
|
1012
971
|
};
|
|
1013
972
|
|
|
1014
|
-
const watcher = chokidar.watch(watchPath,
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
973
|
+
const watcher = chokidar.watch(watchPath, watchOptions);
|
|
974
|
+
// disabling refreshing on changing the content
|
|
975
|
+
if (this.options.liveReload !== false) {
|
|
976
|
+
watcher.on('change', () => {
|
|
977
|
+
this.sockWrite(this.sockets, 'content-changed');
|
|
978
|
+
});
|
|
979
|
+
}
|
|
1020
980
|
this.contentBaseWatchers.push(watcher);
|
|
1021
981
|
}
|
|
1022
982
|
|
|
1023
|
-
invalidate() {
|
|
983
|
+
invalidate(callback) {
|
|
1024
984
|
if (this.middleware) {
|
|
1025
|
-
this.middleware.invalidate();
|
|
985
|
+
this.middleware.invalidate(callback);
|
|
1026
986
|
}
|
|
1027
987
|
}
|
|
1028
988
|
}
|