webpack-dev-server 4.0.0-beta.2 → 4.0.0
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/README.md +109 -58
- package/bin/cli-flags.js +827 -269
- package/bin/process-arguments.js +332 -0
- package/bin/webpack-dev-server.js +46 -30
- package/client/clients/SockJSClient.js +11 -44
- package/client/clients/WebSocketClient.js +43 -0
- package/client/index.js +90 -98
- package/client/modules/logger/index.js +90 -2736
- package/client/modules/sockjs-client/index.js +127 -41
- package/client/modules/strip-ansi/index.js +69 -37
- package/client/overlay.js +111 -95
- package/client/socket.js +11 -12
- package/client/utils/createSocketURL.js +70 -0
- package/client/utils/getCurrentScriptSource.js +10 -9
- package/client/utils/log.js +5 -10
- package/client/utils/parseURL.js +43 -0
- package/client/utils/reloadApp.js +49 -36
- package/client/utils/sendMessage.js +3 -5
- package/lib/Server.js +1456 -539
- package/lib/options.json +562 -314
- package/lib/servers/BaseServer.js +2 -1
- package/lib/servers/SockJSServer.js +32 -31
- package/lib/servers/WebsocketServer.js +42 -41
- package/lib/utils/DevServerPlugin.js +275 -128
- package/package.json +51 -52
- package/CHANGELOG.md +0 -569
- package/client/clients/BaseClient.js +0 -23
- package/client/clients/WebsocketClient.js +0 -76
- package/client/modules/logger/SyncBailHookFake.js +0 -10
- package/client/utils/createSocketUrl.js +0 -94
- package/client/webpack.config.js +0 -57
- package/lib/utils/colors.js +0 -22
- package/lib/utils/createCertificate.js +0 -69
- package/lib/utils/createDomain.js +0 -31
- package/lib/utils/defaultPort.js +0 -3
- package/lib/utils/defaultTo.js +0 -7
- package/lib/utils/findPort.js +0 -39
- package/lib/utils/getCertificate.js +0 -50
- package/lib/utils/getColorsOption.js +0 -15
- package/lib/utils/getCompilerConfigArray.js +0 -8
- package/lib/utils/getSocketClientPath.d.ts +0 -3
- package/lib/utils/getSocketClientPath.js +0 -38
- package/lib/utils/getSocketServerImplementation.js +0 -42
- package/lib/utils/getStatsOption.js +0 -16
- package/lib/utils/getVersions.js +0 -10
- package/lib/utils/normalizeOptions.js +0 -122
- package/lib/utils/routes.js +0 -70
- package/lib/utils/runBonjour.js +0 -21
- package/lib/utils/runOpen.js +0 -83
- package/lib/utils/setupExitSignals.js +0 -25
- package/lib/utils/tryParseInt.js +0 -13
- package/lib/utils/updateCompiler.js +0 -14
package/lib/Server.js
CHANGED
|
@@ -1,108 +1,745 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const path = require(
|
|
5
|
-
const url = require(
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const ipaddr = require(
|
|
9
|
-
const internalIp = require(
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
14
|
-
const historyApiFallback = require('connect-history-api-fallback');
|
|
15
|
-
const compress = require('compression');
|
|
16
|
-
const serveIndex = require('serve-index');
|
|
17
|
-
const webpack = require('webpack');
|
|
18
|
-
const webpackDevMiddleware = require('webpack-dev-middleware');
|
|
19
|
-
const getFilenameFromUrl = require('webpack-dev-middleware/dist/utils/getFilenameFromUrl')
|
|
20
|
-
.default;
|
|
21
|
-
const { validate } = require('schema-utils');
|
|
22
|
-
const normalizeOptions = require('./utils/normalizeOptions');
|
|
23
|
-
const updateCompiler = require('./utils/updateCompiler');
|
|
24
|
-
const getCertificate = require('./utils/getCertificate');
|
|
25
|
-
const colors = require('./utils/colors');
|
|
26
|
-
const runOpen = require('./utils/runOpen');
|
|
27
|
-
const runBonjour = require('./utils/runBonjour');
|
|
28
|
-
const routes = require('./utils/routes');
|
|
29
|
-
const getSocketServerImplementation = require('./utils/getSocketServerImplementation');
|
|
30
|
-
const getCompilerConfigArray = require('./utils/getCompilerConfigArray');
|
|
31
|
-
const getStatsOption = require('./utils/getStatsOption');
|
|
32
|
-
const getColorsOption = require('./utils/getColorsOption');
|
|
33
|
-
const setupExitSignals = require('./utils/setupExitSignals');
|
|
34
|
-
const findPort = require('./utils/findPort');
|
|
35
|
-
const schema = require('./options.json');
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const url = require("url");
|
|
6
|
+
const util = require("util");
|
|
7
|
+
const fs = require("graceful-fs");
|
|
8
|
+
const ipaddr = require("ipaddr.js");
|
|
9
|
+
const internalIp = require("internal-ip");
|
|
10
|
+
const express = require("express");
|
|
11
|
+
const { validate } = require("schema-utils");
|
|
12
|
+
const schema = require("./options.json");
|
|
36
13
|
|
|
37
14
|
if (!process.env.WEBPACK_SERVE) {
|
|
38
15
|
process.env.WEBPACK_SERVE = true;
|
|
39
16
|
}
|
|
40
17
|
|
|
41
18
|
class Server {
|
|
42
|
-
constructor(
|
|
43
|
-
|
|
19
|
+
constructor(options = {}, compiler) {
|
|
20
|
+
// TODO: remove this after plugin support is published
|
|
21
|
+
if (options.hooks) {
|
|
22
|
+
util.deprecate(
|
|
23
|
+
() => {},
|
|
24
|
+
"Using 'compiler' as the first argument is deprecated. Please use 'options' as the first argument and 'compiler' as the second argument.",
|
|
25
|
+
"DEP_WEBPACK_DEV_SERVER_CONSTRUCTOR"
|
|
26
|
+
)();
|
|
27
|
+
|
|
28
|
+
[options = {}, compiler] = [compiler, options];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
validate(schema, options, "webpack Dev Server");
|
|
44
32
|
|
|
45
|
-
this.compiler = compiler;
|
|
46
33
|
this.options = options;
|
|
47
|
-
this.logger = this.compiler.getInfrastructureLogger('webpack-dev-server');
|
|
48
|
-
this.sockets = [];
|
|
49
34
|
this.staticWatchers = [];
|
|
50
35
|
// Keep track of websocket proxies for external websocket upgrade.
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
this.
|
|
36
|
+
this.webSocketProxies = [];
|
|
37
|
+
this.sockets = [];
|
|
38
|
+
this.compiler = compiler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static get DEFAULT_STATS() {
|
|
42
|
+
return {
|
|
43
|
+
all: false,
|
|
44
|
+
hash: true,
|
|
45
|
+
assets: true,
|
|
46
|
+
warnings: true,
|
|
47
|
+
errors: true,
|
|
48
|
+
errorDetails: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
// eslint-disable-next-line class-methods-use-this
|
|
53
|
+
static isAbsoluteURL(URL) {
|
|
54
|
+
// Don't match Windows paths `c:\`
|
|
55
|
+
if (/^[a-zA-Z]:\\/.test(URL)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
59
|
+
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
|
|
60
|
+
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
|
|
61
|
+
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async getHostname(hostname) {
|
|
65
|
+
if (hostname === "local-ip") {
|
|
66
|
+
return (await internalIp.v4()) || (await internalIp.v6()) || "0.0.0.0";
|
|
67
|
+
} else if (hostname === "local-ipv4") {
|
|
68
|
+
return (await internalIp.v4()) || "0.0.0.0";
|
|
69
|
+
} else if (hostname === "local-ipv6") {
|
|
70
|
+
return (await internalIp.v6()) || "::";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return hostname;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static async getFreePort(port) {
|
|
77
|
+
if (port && port !== "auto") {
|
|
78
|
+
return port;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pRetry = require("p-retry");
|
|
82
|
+
const portfinder = require("portfinder");
|
|
83
|
+
|
|
84
|
+
portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080;
|
|
85
|
+
|
|
86
|
+
// Try to find unused port and listen on it for 3 times,
|
|
87
|
+
// if port is not specified in options.
|
|
88
|
+
const defaultPortRetry =
|
|
89
|
+
parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3;
|
|
90
|
+
|
|
91
|
+
return pRetry(() => portfinder.getPortPromise(), {
|
|
92
|
+
retries: defaultPortRetry,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static findCacheDir() {
|
|
97
|
+
const cwd = process.cwd();
|
|
98
|
+
|
|
99
|
+
let dir = cwd;
|
|
100
|
+
|
|
101
|
+
for (;;) {
|
|
102
|
+
try {
|
|
103
|
+
if (fs.statSync(path.join(dir, "package.json")).isFile()) break;
|
|
104
|
+
// eslint-disable-next-line no-empty
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
|
|
107
|
+
const parent = path.dirname(dir);
|
|
108
|
+
|
|
109
|
+
if (dir === parent) {
|
|
110
|
+
// eslint-disable-next-line no-undefined
|
|
111
|
+
dir = undefined;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
dir = parent;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!dir) {
|
|
119
|
+
return path.resolve(cwd, ".cache/webpack-dev-server");
|
|
120
|
+
} else if (process.versions.pnp === "1") {
|
|
121
|
+
return path.resolve(dir, ".pnp/.cache/webpack-dev-server");
|
|
122
|
+
} else if (process.versions.pnp === "3") {
|
|
123
|
+
return path.resolve(dir, ".yarn/.cache/webpack-dev-server");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return path.resolve(dir, "node_modules/.cache/webpack-dev-server");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getCompilerOptions() {
|
|
130
|
+
if (typeof this.compiler.compilers !== "undefined") {
|
|
131
|
+
if (this.compiler.compilers.length === 1) {
|
|
132
|
+
return this.compiler.compilers[0].options;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Configuration with the `devServer` options
|
|
136
|
+
const compilerWithDevServer = this.compiler.compilers.find(
|
|
137
|
+
(config) => config.options.devServer
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (compilerWithDevServer) {
|
|
141
|
+
return compilerWithDevServer.options;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Configuration with `web` preset
|
|
145
|
+
const compilerWithWebPreset = this.compiler.compilers.find(
|
|
146
|
+
(config) =>
|
|
147
|
+
(config.options.externalsPresets &&
|
|
148
|
+
config.options.externalsPresets.web) ||
|
|
149
|
+
[
|
|
150
|
+
"web",
|
|
151
|
+
"webworker",
|
|
152
|
+
"electron-preload",
|
|
153
|
+
"electron-renderer",
|
|
154
|
+
"node-webkit",
|
|
155
|
+
// eslint-disable-next-line no-undefined
|
|
156
|
+
undefined,
|
|
157
|
+
null,
|
|
158
|
+
].includes(config.options.target)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (compilerWithWebPreset) {
|
|
162
|
+
return compilerWithWebPreset.options;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fallback
|
|
166
|
+
return this.compiler.compilers[0].options;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this.compiler.options;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// eslint-disable-next-line class-methods-use-this
|
|
173
|
+
async normalizeOptions() {
|
|
174
|
+
const { options } = this;
|
|
175
|
+
|
|
176
|
+
if (!this.logger) {
|
|
177
|
+
this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const compilerOptions = this.getCompilerOptions();
|
|
181
|
+
// TODO remove `{}` after drop webpack v4 support
|
|
182
|
+
const watchOptions = compilerOptions.watchOptions || {};
|
|
183
|
+
const defaultOptionsForStatic = {
|
|
184
|
+
directory: path.join(process.cwd(), "public"),
|
|
185
|
+
staticOptions: {},
|
|
186
|
+
publicPath: ["/"],
|
|
187
|
+
serveIndex: { icons: true },
|
|
188
|
+
// Respect options from compiler watchOptions
|
|
189
|
+
watch: watchOptions,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (typeof options.allowedHosts === "undefined") {
|
|
193
|
+
// allowedHosts allows some default hosts picked from
|
|
194
|
+
// `options.host` or `webSocketURL.hostname` and `localhost`
|
|
195
|
+
options.allowedHosts = "auto";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
typeof options.allowedHosts === "string" &&
|
|
200
|
+
options.allowedHosts !== "auto" &&
|
|
201
|
+
options.allowedHosts !== "all"
|
|
202
|
+
) {
|
|
203
|
+
// we store allowedHosts as array when supplied as string
|
|
204
|
+
options.allowedHosts = [options.allowedHosts];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof options.bonjour === "undefined") {
|
|
208
|
+
options.bonjour = false;
|
|
209
|
+
} else if (typeof options.bonjour === "boolean") {
|
|
210
|
+
options.bonjour = options.bonjour ? {} : false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
typeof options.client === "undefined" ||
|
|
215
|
+
(typeof options.client === "object" && options.client !== null)
|
|
216
|
+
) {
|
|
217
|
+
if (!options.client) {
|
|
218
|
+
options.client = {};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof options.client.webSocketURL === "undefined") {
|
|
222
|
+
options.client.webSocketURL = {};
|
|
223
|
+
} else if (typeof options.client.webSocketURL === "string") {
|
|
224
|
+
const parsedURL = new URL(options.client.webSocketURL);
|
|
225
|
+
|
|
226
|
+
options.client.webSocketURL = {
|
|
227
|
+
protocol: parsedURL.protocol,
|
|
228
|
+
hostname: parsedURL.hostname,
|
|
229
|
+
port: parsedURL.port.length > 0 ? Number(parsedURL.port) : "",
|
|
230
|
+
pathname: parsedURL.pathname,
|
|
231
|
+
username: parsedURL.username,
|
|
232
|
+
password: parsedURL.password,
|
|
233
|
+
};
|
|
234
|
+
} else if (typeof options.client.webSocketURL.port === "string") {
|
|
235
|
+
options.client.webSocketURL.port = Number(
|
|
236
|
+
options.client.webSocketURL.port
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Enable client overlay by default
|
|
241
|
+
if (typeof options.client.overlay === "undefined") {
|
|
242
|
+
options.client.overlay = true;
|
|
243
|
+
} else if (typeof options.client.overlay !== "boolean") {
|
|
244
|
+
options.client.overlay = {
|
|
245
|
+
errors: true,
|
|
246
|
+
warnings: true,
|
|
247
|
+
...options.client.overlay,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Respect infrastructureLogging.level
|
|
252
|
+
if (typeof options.client.logging === "undefined") {
|
|
253
|
+
options.client.logging = compilerOptions.infrastructureLogging
|
|
254
|
+
? compilerOptions.infrastructureLogging.level
|
|
255
|
+
: "info";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (typeof options.compress === "undefined") {
|
|
260
|
+
options.compress = true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (typeof options.devMiddleware === "undefined") {
|
|
264
|
+
options.devMiddleware = {};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// No need to normalize `headers`
|
|
268
|
+
|
|
269
|
+
if (typeof options.historyApiFallback === "undefined") {
|
|
270
|
+
options.historyApiFallback = false;
|
|
271
|
+
} else if (
|
|
272
|
+
typeof options.historyApiFallback === "boolean" &&
|
|
273
|
+
options.historyApiFallback
|
|
274
|
+
) {
|
|
275
|
+
options.historyApiFallback = {};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// No need to normalize `host`
|
|
279
|
+
|
|
280
|
+
options.hot =
|
|
281
|
+
typeof options.hot === "boolean" || options.hot === "only"
|
|
282
|
+
? options.hot
|
|
283
|
+
: true;
|
|
284
|
+
|
|
285
|
+
// if the user enables http2, we can safely enable https
|
|
286
|
+
if ((options.http2 && !options.https) || options.https === true) {
|
|
287
|
+
options.https = {
|
|
288
|
+
requestCert: false,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// https option
|
|
293
|
+
if (options.https) {
|
|
294
|
+
for (const property of ["cacert", "pfx", "key", "cert"]) {
|
|
295
|
+
const value = options.https[property];
|
|
296
|
+
const isBuffer = value instanceof Buffer;
|
|
297
|
+
|
|
298
|
+
if (value && !isBuffer) {
|
|
299
|
+
let stats = null;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
stats = fs.lstatSync(fs.realpathSync(value)).isFile();
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// ignore error
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// It is file
|
|
308
|
+
options.https[property] = stats
|
|
309
|
+
? fs.readFileSync(path.resolve(value))
|
|
310
|
+
: value;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let fakeCert;
|
|
315
|
+
|
|
316
|
+
if (!options.https.key || !options.https.cert) {
|
|
317
|
+
const certificateDir = Server.findCacheDir();
|
|
318
|
+
const certificatePath = path.join(certificateDir, "server.pem");
|
|
319
|
+
let certificateExists = fs.existsSync(certificatePath);
|
|
320
|
+
|
|
321
|
+
if (certificateExists) {
|
|
322
|
+
const certificateTtl = 1000 * 60 * 60 * 24;
|
|
323
|
+
const certificateStat = fs.statSync(certificatePath);
|
|
324
|
+
|
|
325
|
+
const now = new Date();
|
|
326
|
+
|
|
327
|
+
// cert is more than 30 days old, kill it with fire
|
|
328
|
+
if ((now - certificateStat.ctime) / certificateTtl > 30) {
|
|
329
|
+
const del = require("del");
|
|
330
|
+
|
|
331
|
+
this.logger.info(
|
|
332
|
+
"SSL Certificate is more than 30 days old. Removing..."
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
del.sync([certificatePath], { force: true });
|
|
336
|
+
|
|
337
|
+
certificateExists = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!certificateExists) {
|
|
342
|
+
this.logger.info("Generating SSL Certificate...");
|
|
343
|
+
|
|
344
|
+
const selfsigned = require("selfsigned");
|
|
345
|
+
const attributes = [{ name: "commonName", value: "localhost" }];
|
|
346
|
+
const pems = selfsigned.generate(attributes, {
|
|
347
|
+
algorithm: "sha256",
|
|
348
|
+
days: 30,
|
|
349
|
+
keySize: 2048,
|
|
350
|
+
extensions: [
|
|
351
|
+
{
|
|
352
|
+
name: "basicConstraints",
|
|
353
|
+
cA: true,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "keyUsage",
|
|
357
|
+
keyCertSign: true,
|
|
358
|
+
digitalSignature: true,
|
|
359
|
+
nonRepudiation: true,
|
|
360
|
+
keyEncipherment: true,
|
|
361
|
+
dataEncipherment: true,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "extKeyUsage",
|
|
365
|
+
serverAuth: true,
|
|
366
|
+
clientAuth: true,
|
|
367
|
+
codeSigning: true,
|
|
368
|
+
timeStamping: true,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "subjectAltName",
|
|
372
|
+
altNames: [
|
|
373
|
+
{
|
|
374
|
+
// type 2 is DNS
|
|
375
|
+
type: 2,
|
|
376
|
+
value: "localhost",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: 2,
|
|
380
|
+
value: "localhost.localdomain",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
type: 2,
|
|
384
|
+
value: "lvh.me",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
type: 2,
|
|
388
|
+
value: "*.lvh.me",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
type: 2,
|
|
392
|
+
value: "[::1]",
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
// type 7 is IP
|
|
396
|
+
type: 7,
|
|
397
|
+
ip: "127.0.0.1",
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
type: 7,
|
|
401
|
+
ip: "fe80::1",
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
fs.mkdirSync(certificateDir, { recursive: true });
|
|
409
|
+
fs.writeFileSync(certificatePath, pems.private + pems.cert, {
|
|
410
|
+
encoding: "utf8",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
fakeCert = fs.readFileSync(certificatePath);
|
|
415
|
+
|
|
416
|
+
this.logger.info(`SSL certificate: ${certificatePath}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
options.https.key = options.https.key || fakeCert;
|
|
420
|
+
options.https.cert = options.https.cert || fakeCert;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (typeof options.ipc === "boolean") {
|
|
424
|
+
const isWindows = process.platform === "win32";
|
|
425
|
+
const pipePrefix = isWindows ? "\\\\.\\pipe\\" : os.tmpdir();
|
|
426
|
+
const pipeName = "webpack-dev-server.sock";
|
|
427
|
+
|
|
428
|
+
options.ipc = path.join(pipePrefix, pipeName);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
options.liveReload =
|
|
432
|
+
typeof options.liveReload !== "undefined" ? options.liveReload : true;
|
|
433
|
+
|
|
434
|
+
// https://github.com/webpack/webpack-dev-server/issues/1990
|
|
435
|
+
const defaultOpenOptions = { wait: false };
|
|
436
|
+
const getOpenItemsFromObject = ({ target, ...rest }) => {
|
|
437
|
+
const normalizedOptions = { ...defaultOpenOptions, ...rest };
|
|
438
|
+
|
|
439
|
+
if (typeof normalizedOptions.app === "string") {
|
|
440
|
+
normalizedOptions.app = {
|
|
441
|
+
name: normalizedOptions.app,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const normalizedTarget = typeof target === "undefined" ? "<url>" : target;
|
|
446
|
+
|
|
447
|
+
if (Array.isArray(normalizedTarget)) {
|
|
448
|
+
return normalizedTarget.map((singleTarget) => {
|
|
449
|
+
return { target: singleTarget, options: normalizedOptions };
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return [{ target: normalizedTarget, options: normalizedOptions }];
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
if (typeof options.open === "undefined") {
|
|
457
|
+
options.open = [];
|
|
458
|
+
} else if (typeof options.open === "boolean") {
|
|
459
|
+
options.open = options.open
|
|
460
|
+
? [{ target: "<url>", options: defaultOpenOptions }]
|
|
461
|
+
: [];
|
|
462
|
+
} else if (typeof options.open === "string") {
|
|
463
|
+
options.open = [{ target: options.open, options: defaultOpenOptions }];
|
|
464
|
+
} else if (Array.isArray(options.open)) {
|
|
465
|
+
const result = [];
|
|
466
|
+
|
|
467
|
+
options.open.forEach((item) => {
|
|
468
|
+
if (typeof item === "string") {
|
|
469
|
+
result.push({ target: item, options: defaultOpenOptions });
|
|
470
|
+
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
result.push(...getOpenItemsFromObject(item));
|
|
475
|
+
});
|
|
61
476
|
|
|
62
|
-
|
|
477
|
+
options.open = result;
|
|
478
|
+
} else {
|
|
479
|
+
options.open = [...getOpenItemsFromObject(options.open)];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (typeof options.port === "string" && options.port !== "auto") {
|
|
483
|
+
options.port = Number(options.port);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Assume a proxy configuration specified as:
|
|
488
|
+
* proxy: {
|
|
489
|
+
* 'context': { options }
|
|
490
|
+
* }
|
|
491
|
+
* OR
|
|
492
|
+
* proxy: {
|
|
493
|
+
* 'context': 'target'
|
|
494
|
+
* }
|
|
495
|
+
*/
|
|
496
|
+
if (typeof options.proxy !== "undefined") {
|
|
497
|
+
// TODO remove in the next major release, only accept `Array`
|
|
498
|
+
if (!Array.isArray(options.proxy)) {
|
|
499
|
+
if (
|
|
500
|
+
Object.prototype.hasOwnProperty.call(options.proxy, "target") ||
|
|
501
|
+
Object.prototype.hasOwnProperty.call(options.proxy, "router")
|
|
502
|
+
) {
|
|
503
|
+
options.proxy = [options.proxy];
|
|
504
|
+
} else {
|
|
505
|
+
options.proxy = Object.keys(options.proxy).map((context) => {
|
|
506
|
+
let proxyOptions;
|
|
507
|
+
// For backwards compatibility reasons.
|
|
508
|
+
const correctedContext = context
|
|
509
|
+
.replace(/^\*$/, "**")
|
|
510
|
+
.replace(/\/\*$/, "");
|
|
511
|
+
|
|
512
|
+
if (typeof options.proxy[context] === "string") {
|
|
513
|
+
proxyOptions = {
|
|
514
|
+
context: correctedContext,
|
|
515
|
+
target: options.proxy[context],
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
proxyOptions = { ...options.proxy[context] };
|
|
519
|
+
proxyOptions.context = correctedContext;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return proxyOptions;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
options.proxy = options.proxy.map((item) => {
|
|
528
|
+
const getLogLevelForProxy = (level) => {
|
|
529
|
+
if (level === "none") {
|
|
530
|
+
return "silent";
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (level === "log") {
|
|
534
|
+
return "info";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (level === "verbose") {
|
|
538
|
+
return "debug";
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return level;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
if (typeof item.logLevel === "undefined") {
|
|
545
|
+
item.logLevel = getLogLevelForProxy(
|
|
546
|
+
compilerOptions.infrastructureLogging
|
|
547
|
+
? compilerOptions.infrastructureLogging.level
|
|
548
|
+
: "info"
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (typeof item.logProvider === "undefined") {
|
|
553
|
+
item.logProvider = () => this.logger;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return item;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (typeof options.setupExitSignals === "undefined") {
|
|
561
|
+
options.setupExitSignals = true;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (typeof options.static === "undefined") {
|
|
565
|
+
options.static = [defaultOptionsForStatic];
|
|
566
|
+
} else if (typeof options.static === "boolean") {
|
|
567
|
+
options.static = options.static ? [defaultOptionsForStatic] : false;
|
|
568
|
+
} else if (typeof options.static === "string") {
|
|
569
|
+
options.static = [
|
|
570
|
+
{ ...defaultOptionsForStatic, directory: options.static },
|
|
571
|
+
];
|
|
572
|
+
} else if (Array.isArray(options.static)) {
|
|
573
|
+
options.static = options.static.map((item) => {
|
|
574
|
+
if (typeof item === "string") {
|
|
575
|
+
return { ...defaultOptionsForStatic, directory: item };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return { ...defaultOptionsForStatic, ...item };
|
|
579
|
+
});
|
|
580
|
+
} else {
|
|
581
|
+
options.static = [{ ...defaultOptionsForStatic, ...options.static }];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (options.static) {
|
|
585
|
+
options.static.forEach((staticOption) => {
|
|
586
|
+
if (Server.isAbsoluteURL(staticOption.directory)) {
|
|
587
|
+
throw new Error("Using a URL as static.directory is not supported");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ensure that publicPath is an array
|
|
591
|
+
if (typeof staticOption.publicPath === "string") {
|
|
592
|
+
staticOption.publicPath = [staticOption.publicPath];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ensure that watch is an object if true
|
|
596
|
+
if (staticOption.watch === true) {
|
|
597
|
+
staticOption.watch = defaultOptionsForStatic.watch;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ensure that serveIndex is an object if true
|
|
601
|
+
if (staticOption.serveIndex === true) {
|
|
602
|
+
staticOption.serveIndex = defaultOptionsForStatic.serveIndex;
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (typeof options.watchFiles === "string") {
|
|
608
|
+
options.watchFiles = [{ paths: options.watchFiles, options: {} }];
|
|
609
|
+
} else if (
|
|
610
|
+
typeof options.watchFiles === "object" &&
|
|
611
|
+
options.watchFiles !== null &&
|
|
612
|
+
!Array.isArray(options.watchFiles)
|
|
613
|
+
) {
|
|
614
|
+
options.watchFiles = [
|
|
615
|
+
{
|
|
616
|
+
paths: options.watchFiles.paths,
|
|
617
|
+
options: options.watchFiles.options || {},
|
|
618
|
+
},
|
|
619
|
+
];
|
|
620
|
+
} else if (Array.isArray(options.watchFiles)) {
|
|
621
|
+
options.watchFiles = options.watchFiles.map((item) => {
|
|
622
|
+
if (typeof item === "string") {
|
|
623
|
+
return { paths: item, options: {} };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return { paths: item.paths, options: item.options || {} };
|
|
627
|
+
});
|
|
628
|
+
} else {
|
|
629
|
+
options.watchFiles = [];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const defaultWebSocketServerType = "ws";
|
|
633
|
+
const defaultWebSocketServerOptions = { path: "/ws" };
|
|
634
|
+
|
|
635
|
+
if (typeof options.webSocketServer === "undefined") {
|
|
636
|
+
options.webSocketServer = {
|
|
637
|
+
type: defaultWebSocketServerType,
|
|
638
|
+
options: defaultWebSocketServerOptions,
|
|
639
|
+
};
|
|
640
|
+
} else if (
|
|
641
|
+
typeof options.webSocketServer === "boolean" &&
|
|
642
|
+
!options.webSocketServer
|
|
643
|
+
) {
|
|
644
|
+
options.webSocketServer = false;
|
|
645
|
+
} else if (
|
|
646
|
+
typeof options.webSocketServer === "string" ||
|
|
647
|
+
typeof options.webSocketServer === "function"
|
|
648
|
+
) {
|
|
649
|
+
options.webSocketServer = {
|
|
650
|
+
type: options.webSocketServer,
|
|
651
|
+
options: defaultWebSocketServerOptions,
|
|
652
|
+
};
|
|
653
|
+
} else {
|
|
654
|
+
options.webSocketServer = {
|
|
655
|
+
type: options.webSocketServer.type || defaultWebSocketServerType,
|
|
656
|
+
options: {
|
|
657
|
+
...defaultWebSocketServerOptions,
|
|
658
|
+
...options.webSocketServer.options,
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
if (typeof options.webSocketServer.options.port === "string") {
|
|
663
|
+
options.webSocketServer.options.port = Number(
|
|
664
|
+
options.webSocketServer.options.port
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async initialize() {
|
|
671
|
+
this.applyDevServerPlugin();
|
|
672
|
+
|
|
673
|
+
if (this.options.client && this.options.client.progress) {
|
|
63
674
|
this.setupProgressPlugin();
|
|
64
675
|
}
|
|
65
676
|
|
|
66
677
|
this.setupHooks();
|
|
67
678
|
this.setupApp();
|
|
68
|
-
this.
|
|
679
|
+
this.setupHostHeaderCheck();
|
|
69
680
|
this.setupDevMiddleware();
|
|
70
|
-
|
|
71
681
|
// Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
|
|
72
|
-
|
|
73
|
-
|
|
682
|
+
this.setupBuiltInRoutes();
|
|
74
683
|
this.setupWatchFiles();
|
|
75
684
|
this.setupFeatures();
|
|
76
|
-
this.setupHttps();
|
|
77
685
|
this.createServer();
|
|
78
686
|
|
|
79
|
-
|
|
80
|
-
|
|
687
|
+
if (this.options.setupExitSignals) {
|
|
688
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
689
|
+
|
|
690
|
+
signals.forEach((signal) => {
|
|
691
|
+
process.on(signal, () => {
|
|
692
|
+
this.stopCallback(() => {
|
|
693
|
+
// eslint-disable-next-line no-process-exit
|
|
694
|
+
process.exit();
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
}
|
|
81
699
|
|
|
82
700
|
// Proxy WebSocket without the initial http request
|
|
83
701
|
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
84
702
|
// eslint-disable-next-line func-names
|
|
85
|
-
this.
|
|
86
|
-
this.server.on(
|
|
703
|
+
this.webSocketProxies.forEach(function (webSocketProxy) {
|
|
704
|
+
this.server.on("upgrade", webSocketProxy.upgrade);
|
|
87
705
|
}, this);
|
|
88
706
|
}
|
|
89
707
|
|
|
708
|
+
applyDevServerPlugin() {
|
|
709
|
+
const DevServerPlugin = require("./utils/DevServerPlugin");
|
|
710
|
+
|
|
711
|
+
const compilers = this.compiler.compilers || [this.compiler];
|
|
712
|
+
|
|
713
|
+
// eslint-disable-next-line no-shadow
|
|
714
|
+
compilers.forEach((compiler) => {
|
|
715
|
+
new DevServerPlugin(this.options).apply(compiler);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
90
719
|
setupProgressPlugin() {
|
|
91
|
-
|
|
720
|
+
const { ProgressPlugin } = this.compiler.webpack || require("webpack");
|
|
721
|
+
|
|
722
|
+
new ProgressPlugin((percent, msg, addInfo, pluginName) => {
|
|
92
723
|
percent = Math.floor(percent * 100);
|
|
93
724
|
|
|
94
725
|
if (percent === 100) {
|
|
95
|
-
msg =
|
|
726
|
+
msg = "Compilation completed";
|
|
96
727
|
}
|
|
97
728
|
|
|
98
729
|
if (addInfo) {
|
|
99
730
|
msg = `${msg} (${addInfo})`;
|
|
100
731
|
}
|
|
101
732
|
|
|
102
|
-
|
|
733
|
+
if (this.webSocketServer) {
|
|
734
|
+
this.sendMessage(this.webSocketServer.clients, "progress-update", {
|
|
735
|
+
percent,
|
|
736
|
+
msg,
|
|
737
|
+
pluginName,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
103
740
|
|
|
104
741
|
if (this.server) {
|
|
105
|
-
this.server.emit(
|
|
742
|
+
this.server.emit("progress-update", { percent, msg, pluginName });
|
|
106
743
|
}
|
|
107
744
|
}).apply(this.compiler);
|
|
108
745
|
}
|
|
@@ -113,19 +750,29 @@ class Server {
|
|
|
113
750
|
this.app = new express();
|
|
114
751
|
}
|
|
115
752
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
this.sockWrite(this.sockets, 'invalid');
|
|
120
|
-
};
|
|
753
|
+
getStats(statsObj) {
|
|
754
|
+
const stats = Server.DEFAULT_STATS;
|
|
755
|
+
const compilerOptions = this.getCompilerOptions();
|
|
121
756
|
|
|
757
|
+
if (compilerOptions.stats && compilerOptions.stats.warningsFilter) {
|
|
758
|
+
stats.warningsFilter = compilerOptions.stats.warningsFilter;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return statsObj.toJson(stats);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
setupHooks() {
|
|
122
765
|
const addHooks = (compiler) => {
|
|
123
|
-
|
|
766
|
+
compiler.hooks.invalid.tap("webpack-dev-server", () => {
|
|
767
|
+
if (this.webSocketServer) {
|
|
768
|
+
this.sendMessage(this.webSocketServer.clients, "invalid");
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
compiler.hooks.done.tap("webpack-dev-server", (stats) => {
|
|
772
|
+
if (this.webSocketServer) {
|
|
773
|
+
this.sendStats(this.webSocketServer.clients, this.getStats(stats));
|
|
774
|
+
}
|
|
124
775
|
|
|
125
|
-
compile.tap('webpack-dev-server', invalidPlugin);
|
|
126
|
-
invalid.tap('webpack-dev-server', invalidPlugin);
|
|
127
|
-
done.tap('webpack-dev-server', (stats) => {
|
|
128
|
-
this.sendStats(this.sockets, this.getStats(stats));
|
|
129
776
|
this.stats = stats;
|
|
130
777
|
});
|
|
131
778
|
};
|
|
@@ -137,108 +784,123 @@ class Server {
|
|
|
137
784
|
}
|
|
138
785
|
}
|
|
139
786
|
|
|
140
|
-
|
|
141
|
-
this.app.all(
|
|
142
|
-
if (this.
|
|
787
|
+
setupHostHeaderCheck() {
|
|
788
|
+
this.app.all("*", (req, res, next) => {
|
|
789
|
+
if (this.checkHeader(req.headers, "host")) {
|
|
143
790
|
return next();
|
|
144
791
|
}
|
|
145
792
|
|
|
146
|
-
res.send(
|
|
793
|
+
res.send("Invalid Host header");
|
|
147
794
|
});
|
|
148
795
|
}
|
|
149
796
|
|
|
150
797
|
setupDevMiddleware() {
|
|
798
|
+
const webpackDevMiddleware = require("webpack-dev-middleware");
|
|
799
|
+
|
|
151
800
|
// middleware for serving webpack bundle
|
|
152
|
-
this.middleware = webpackDevMiddleware(
|
|
801
|
+
this.middleware = webpackDevMiddleware(
|
|
802
|
+
this.compiler,
|
|
803
|
+
this.options.devMiddleware
|
|
804
|
+
);
|
|
153
805
|
}
|
|
154
806
|
|
|
155
|
-
|
|
156
|
-
this
|
|
157
|
-
}
|
|
807
|
+
setupBuiltInRoutes() {
|
|
808
|
+
const { app, middleware } = this;
|
|
158
809
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
* Assume a proxy configuration specified as:
|
|
162
|
-
* proxy: {
|
|
163
|
-
* 'context': { options }
|
|
164
|
-
* }
|
|
165
|
-
* OR
|
|
166
|
-
* proxy: {
|
|
167
|
-
* 'context': 'target'
|
|
168
|
-
* }
|
|
169
|
-
*/
|
|
170
|
-
if (!Array.isArray(this.options.proxy)) {
|
|
171
|
-
if (Object.prototype.hasOwnProperty.call(this.options.proxy, 'target')) {
|
|
172
|
-
this.options.proxy = [this.options.proxy];
|
|
173
|
-
} else {
|
|
174
|
-
this.options.proxy = Object.keys(this.options.proxy).map((context) => {
|
|
175
|
-
let proxyOptions;
|
|
176
|
-
// For backwards compatibility reasons.
|
|
177
|
-
const correctedContext = context
|
|
178
|
-
.replace(/^\*$/, '**')
|
|
179
|
-
.replace(/\/\*$/, '');
|
|
180
|
-
|
|
181
|
-
if (typeof this.options.proxy[context] === 'string') {
|
|
182
|
-
proxyOptions = {
|
|
183
|
-
context: correctedContext,
|
|
184
|
-
target: this.options.proxy[context],
|
|
185
|
-
};
|
|
186
|
-
} else {
|
|
187
|
-
proxyOptions = Object.assign({}, this.options.proxy[context]);
|
|
188
|
-
proxyOptions.context = correctedContext;
|
|
189
|
-
}
|
|
810
|
+
app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
|
|
811
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
190
812
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return 'silent';
|
|
194
|
-
}
|
|
813
|
+
const { createReadStream } = require("graceful-fs");
|
|
814
|
+
const clientPath = path.join(__dirname, "..", "client");
|
|
195
815
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
816
|
+
createReadStream(
|
|
817
|
+
path.join(clientPath, "modules/sockjs-client/index.js")
|
|
818
|
+
).pipe(res);
|
|
819
|
+
});
|
|
199
820
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
821
|
+
app.get("/webpack-dev-server/invalidate", (_req, res) => {
|
|
822
|
+
this.invalidate();
|
|
823
|
+
|
|
824
|
+
res.end();
|
|
825
|
+
});
|
|
203
826
|
|
|
204
|
-
|
|
205
|
-
|
|
827
|
+
app.get("/webpack-dev-server", (req, res) => {
|
|
828
|
+
middleware.waitUntilValid((stats) => {
|
|
829
|
+
res.setHeader("Content-Type", "text/html");
|
|
830
|
+
res.write(
|
|
831
|
+
'<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
|
|
832
|
+
);
|
|
206
833
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
834
|
+
const statsForPrint =
|
|
835
|
+
typeof stats.stats !== "undefined"
|
|
836
|
+
? stats.toJson().children
|
|
837
|
+
: [stats.toJson()];
|
|
210
838
|
|
|
211
|
-
|
|
212
|
-
configWithDevServer.infrastructureLogging.level
|
|
213
|
-
);
|
|
214
|
-
proxyOptions.logProvider = () => this.logger;
|
|
839
|
+
res.write(`<h1>Assets Report:</h1>`);
|
|
215
840
|
|
|
216
|
-
|
|
841
|
+
statsForPrint.forEach((item, index) => {
|
|
842
|
+
res.write("<div>");
|
|
843
|
+
|
|
844
|
+
const name =
|
|
845
|
+
item.name || (stats.stats ? `unnamed[${index}]` : "unnamed");
|
|
846
|
+
|
|
847
|
+
res.write(`<h2>Compilation: ${name}</h2>`);
|
|
848
|
+
res.write("<ul>");
|
|
849
|
+
|
|
850
|
+
const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
|
|
851
|
+
|
|
852
|
+
for (const asset of item.assets) {
|
|
853
|
+
const assetName = asset.name;
|
|
854
|
+
const assetURL = `${publicPath}${assetName}`;
|
|
855
|
+
|
|
856
|
+
res.write(
|
|
857
|
+
`<li>
|
|
858
|
+
<strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
|
|
859
|
+
</li>`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
res.write("</ul>");
|
|
864
|
+
res.write("</div>");
|
|
217
865
|
});
|
|
218
|
-
|
|
219
|
-
|
|
866
|
+
|
|
867
|
+
res.end("</body></html>");
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
setupCompressFeature() {
|
|
873
|
+
const compress = require("compression");
|
|
874
|
+
|
|
875
|
+
this.app.use(compress());
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
setupProxyFeature() {
|
|
879
|
+
const { createProxyMiddleware } = require("http-proxy-middleware");
|
|
220
880
|
|
|
221
881
|
const getProxyMiddleware = (proxyConfig) => {
|
|
222
882
|
const context = proxyConfig.context || proxyConfig.path;
|
|
223
883
|
|
|
224
884
|
// It is possible to use the `bypass` method without a `target`.
|
|
225
885
|
// However, the proxy middleware has no use in this case, and will fail to instantiate.
|
|
226
|
-
if (
|
|
886
|
+
if (context) {
|
|
227
887
|
return createProxyMiddleware(context, proxyConfig);
|
|
228
888
|
}
|
|
889
|
+
|
|
890
|
+
return createProxyMiddleware(proxyConfig);
|
|
229
891
|
};
|
|
230
892
|
/**
|
|
231
893
|
* Assume a proxy configuration specified as:
|
|
232
894
|
* proxy: [
|
|
233
895
|
* {
|
|
234
|
-
* context:
|
|
235
|
-
* ...options
|
|
896
|
+
* context: "value",
|
|
897
|
+
* ...options,
|
|
236
898
|
* },
|
|
237
899
|
* // or:
|
|
238
900
|
* function() {
|
|
239
901
|
* return {
|
|
240
|
-
* context:
|
|
241
|
-
* ...options
|
|
902
|
+
* context: "context",
|
|
903
|
+
* ...options,
|
|
242
904
|
* };
|
|
243
905
|
* }
|
|
244
906
|
* ]
|
|
@@ -247,18 +909,20 @@ class Server {
|
|
|
247
909
|
let proxyMiddleware;
|
|
248
910
|
|
|
249
911
|
let proxyConfig =
|
|
250
|
-
typeof proxyConfigOrCallback ===
|
|
912
|
+
typeof proxyConfigOrCallback === "function"
|
|
251
913
|
? proxyConfigOrCallback()
|
|
252
914
|
: proxyConfigOrCallback;
|
|
253
915
|
|
|
254
|
-
|
|
916
|
+
if (!proxyConfig.bypass) {
|
|
917
|
+
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
918
|
+
}
|
|
255
919
|
|
|
256
920
|
if (proxyConfig.ws) {
|
|
257
|
-
this.
|
|
921
|
+
this.webSocketProxies.push(proxyMiddleware);
|
|
258
922
|
}
|
|
259
923
|
|
|
260
924
|
const handle = async (req, res, next) => {
|
|
261
|
-
if (typeof proxyConfigOrCallback ===
|
|
925
|
+
if (typeof proxyConfigOrCallback === "function") {
|
|
262
926
|
const newProxyConfig = proxyConfigOrCallback(req, res, next);
|
|
263
927
|
|
|
264
928
|
if (newProxyConfig !== proxyConfig) {
|
|
@@ -270,16 +934,17 @@ class Server {
|
|
|
270
934
|
// - Check if we have a bypass function defined
|
|
271
935
|
// - In case the bypass function is defined we'll retrieve the
|
|
272
936
|
// bypassUrl from it otherwise bypassUrl would be null
|
|
273
|
-
|
|
937
|
+
// TODO remove in the next major in favor `context` and `router` options
|
|
938
|
+
const isByPassFuncDefined = typeof proxyConfig.bypass === "function";
|
|
274
939
|
const bypassUrl = isByPassFuncDefined
|
|
275
940
|
? await proxyConfig.bypass(req, res, proxyConfig)
|
|
276
941
|
: null;
|
|
277
942
|
|
|
278
|
-
if (typeof bypassUrl ===
|
|
943
|
+
if (typeof bypassUrl === "boolean") {
|
|
279
944
|
// skip the proxy
|
|
280
945
|
req.url = null;
|
|
281
946
|
next();
|
|
282
|
-
} else if (typeof bypassUrl ===
|
|
947
|
+
} else if (typeof bypassUrl === "string") {
|
|
283
948
|
// byPass to that url
|
|
284
949
|
req.url = bypassUrl;
|
|
285
950
|
next();
|
|
@@ -297,13 +962,20 @@ class Server {
|
|
|
297
962
|
}
|
|
298
963
|
|
|
299
964
|
setupHistoryApiFallbackFeature() {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
965
|
+
const { historyApiFallback } = this.options;
|
|
966
|
+
|
|
967
|
+
if (
|
|
968
|
+
typeof historyApiFallback.logger === "undefined" &&
|
|
969
|
+
!historyApiFallback.verbose
|
|
970
|
+
) {
|
|
971
|
+
historyApiFallback.logger = this.logger.log.bind(
|
|
972
|
+
this.logger,
|
|
973
|
+
"[connect-history-api-fallback]"
|
|
974
|
+
);
|
|
975
|
+
}
|
|
304
976
|
|
|
305
977
|
// Fall back to /index.html if nothing else matches.
|
|
306
|
-
this.app.use(
|
|
978
|
+
this.app.use(require("connect-history-api-fallback")(historyApiFallback));
|
|
307
979
|
}
|
|
308
980
|
|
|
309
981
|
setupStaticFeature() {
|
|
@@ -318,12 +990,14 @@ class Server {
|
|
|
318
990
|
}
|
|
319
991
|
|
|
320
992
|
setupStaticServeIndexFeature() {
|
|
993
|
+
const serveIndex = require("serve-index");
|
|
994
|
+
|
|
321
995
|
this.options.static.forEach((staticOption) => {
|
|
322
996
|
staticOption.publicPath.forEach((publicPath) => {
|
|
323
997
|
if (staticOption.serveIndex) {
|
|
324
998
|
this.app.use(publicPath, (req, res, next) => {
|
|
325
999
|
// serve-index doesn't fallthrough non-get/head request to next middleware
|
|
326
|
-
if (req.method !==
|
|
1000
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
327
1001
|
return next();
|
|
328
1002
|
}
|
|
329
1003
|
|
|
@@ -351,23 +1025,12 @@ class Server {
|
|
|
351
1025
|
}
|
|
352
1026
|
|
|
353
1027
|
setupWatchFiles() {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this.watchFiles(
|
|
359
|
-
}
|
|
360
|
-
watchFiles.forEach((file) => {
|
|
361
|
-
if (typeof file === 'string') {
|
|
362
|
-
this.watchFiles(file, {});
|
|
363
|
-
} else {
|
|
364
|
-
this.watchFiles(file.paths, file.options || {});
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
} else {
|
|
368
|
-
// { paths: [...], options: {} }
|
|
369
|
-
this.watchFiles(watchFiles.paths, watchFiles.options || {});
|
|
370
|
-
}
|
|
1028
|
+
const { watchFiles } = this.options;
|
|
1029
|
+
|
|
1030
|
+
if (watchFiles.length > 0) {
|
|
1031
|
+
watchFiles.forEach((item) => {
|
|
1032
|
+
this.watchFiles(item.paths, item.options);
|
|
1033
|
+
});
|
|
371
1034
|
}
|
|
372
1035
|
}
|
|
373
1036
|
|
|
@@ -380,11 +1043,11 @@ class Server {
|
|
|
380
1043
|
}
|
|
381
1044
|
|
|
382
1045
|
setupHeadersFeature() {
|
|
383
|
-
this.app.all(
|
|
1046
|
+
this.app.all("*", this.setHeaders.bind(this));
|
|
384
1047
|
}
|
|
385
1048
|
|
|
386
1049
|
setupMagicHtmlFeature() {
|
|
387
|
-
this.app.get(
|
|
1050
|
+
this.app.get("*", this.serveMagicHtml.bind(this));
|
|
388
1051
|
}
|
|
389
1052
|
|
|
390
1053
|
setupFeatures() {
|
|
@@ -414,12 +1077,12 @@ class Server {
|
|
|
414
1077
|
this.setupStaticWatchFeature();
|
|
415
1078
|
},
|
|
416
1079
|
onBeforeSetupMiddleware: () => {
|
|
417
|
-
if (typeof this.options.onBeforeSetupMiddleware ===
|
|
1080
|
+
if (typeof this.options.onBeforeSetupMiddleware === "function") {
|
|
418
1081
|
this.setupOnBeforeSetupMiddlewareFeature();
|
|
419
1082
|
}
|
|
420
1083
|
},
|
|
421
1084
|
onAfterSetupMiddleware: () => {
|
|
422
|
-
if (typeof this.options.onAfterSetupMiddleware ===
|
|
1085
|
+
if (typeof this.options.onAfterSetupMiddleware === "function") {
|
|
423
1086
|
this.setupOnAfterSetupMiddlewareFeature();
|
|
424
1087
|
}
|
|
425
1088
|
},
|
|
@@ -440,39 +1103,39 @@ class Server {
|
|
|
440
1103
|
|
|
441
1104
|
// compress is placed last and uses unshift so that it will be the first middleware used
|
|
442
1105
|
if (this.options.compress) {
|
|
443
|
-
runnableFeatures.push(
|
|
1106
|
+
runnableFeatures.push("compress");
|
|
444
1107
|
}
|
|
445
1108
|
|
|
446
1109
|
if (this.options.onBeforeSetupMiddleware) {
|
|
447
|
-
runnableFeatures.push(
|
|
1110
|
+
runnableFeatures.push("onBeforeSetupMiddleware");
|
|
448
1111
|
}
|
|
449
1112
|
|
|
450
|
-
runnableFeatures.push(
|
|
1113
|
+
runnableFeatures.push("headers", "middleware");
|
|
451
1114
|
|
|
452
1115
|
if (this.options.proxy) {
|
|
453
|
-
runnableFeatures.push(
|
|
1116
|
+
runnableFeatures.push("proxy", "middleware");
|
|
454
1117
|
}
|
|
455
1118
|
|
|
456
1119
|
if (this.options.static) {
|
|
457
|
-
runnableFeatures.push(
|
|
1120
|
+
runnableFeatures.push("static");
|
|
458
1121
|
}
|
|
459
1122
|
|
|
460
1123
|
if (this.options.historyApiFallback) {
|
|
461
|
-
runnableFeatures.push(
|
|
1124
|
+
runnableFeatures.push("historyApiFallback", "middleware");
|
|
462
1125
|
|
|
463
1126
|
if (this.options.static) {
|
|
464
|
-
runnableFeatures.push(
|
|
1127
|
+
runnableFeatures.push("static");
|
|
465
1128
|
}
|
|
466
1129
|
}
|
|
467
1130
|
|
|
468
1131
|
if (this.options.static) {
|
|
469
|
-
runnableFeatures.push(
|
|
1132
|
+
runnableFeatures.push("staticServeIndex", "staticWatch");
|
|
470
1133
|
}
|
|
471
1134
|
|
|
472
|
-
runnableFeatures.push(
|
|
1135
|
+
runnableFeatures.push("magicHtml");
|
|
473
1136
|
|
|
474
1137
|
if (this.options.onAfterSetupMiddleware) {
|
|
475
|
-
runnableFeatures.push(
|
|
1138
|
+
runnableFeatures.push("onAfterSetupMiddleware");
|
|
476
1139
|
}
|
|
477
1140
|
|
|
478
1141
|
runnableFeatures.forEach((feature) => {
|
|
@@ -480,239 +1143,324 @@ class Server {
|
|
|
480
1143
|
});
|
|
481
1144
|
}
|
|
482
1145
|
|
|
483
|
-
setupHttps() {
|
|
484
|
-
// if the user enables http2, we can safely enable https
|
|
485
|
-
if (
|
|
486
|
-
(this.options.http2 && !this.options.https) ||
|
|
487
|
-
this.options.https === true
|
|
488
|
-
) {
|
|
489
|
-
this.options.https = {
|
|
490
|
-
requestCert: false,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (this.options.https) {
|
|
495
|
-
for (const property of ['ca', 'pfx', 'key', 'cert']) {
|
|
496
|
-
const value = this.options.https[property];
|
|
497
|
-
const isBuffer = value instanceof Buffer;
|
|
498
|
-
|
|
499
|
-
if (value && !isBuffer) {
|
|
500
|
-
let stats = null;
|
|
501
|
-
|
|
502
|
-
try {
|
|
503
|
-
stats = fs.lstatSync(fs.realpathSync(value)).isFile();
|
|
504
|
-
} catch (error) {
|
|
505
|
-
// ignore error
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// It is file
|
|
509
|
-
this.options.https[property] = stats
|
|
510
|
-
? fs.readFileSync(path.resolve(value))
|
|
511
|
-
: value;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
let fakeCert;
|
|
516
|
-
|
|
517
|
-
if (!this.options.https.key || !this.options.https.cert) {
|
|
518
|
-
fakeCert = getCertificate(this.logger);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
this.options.https.key = this.options.https.key || fakeCert;
|
|
522
|
-
this.options.https.cert = this.options.https.cert || fakeCert;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
1146
|
createServer() {
|
|
527
1147
|
if (this.options.https) {
|
|
528
1148
|
if (this.options.http2) {
|
|
529
1149
|
// TODO: we need to replace spdy with http2 which is an internal module
|
|
530
|
-
this.server = require(
|
|
1150
|
+
this.server = require("spdy").createServer(
|
|
531
1151
|
{
|
|
532
1152
|
...this.options.https,
|
|
533
1153
|
spdy: {
|
|
534
|
-
protocols: [
|
|
1154
|
+
protocols: ["h2", "http/1.1"],
|
|
535
1155
|
},
|
|
536
1156
|
},
|
|
537
1157
|
this.app
|
|
538
1158
|
);
|
|
539
1159
|
} else {
|
|
1160
|
+
const https = require("https");
|
|
1161
|
+
|
|
540
1162
|
this.server = https.createServer(this.options.https, this.app);
|
|
541
1163
|
}
|
|
542
1164
|
} else {
|
|
1165
|
+
const http = require("http");
|
|
1166
|
+
|
|
543
1167
|
this.server = http.createServer(this.app);
|
|
544
1168
|
}
|
|
545
1169
|
|
|
546
|
-
this.server.on(
|
|
1170
|
+
this.server.on("connection", (socket) => {
|
|
1171
|
+
// Add socket to list
|
|
1172
|
+
this.sockets.push(socket);
|
|
1173
|
+
|
|
1174
|
+
socket.once("close", () => {
|
|
1175
|
+
// Remove socket from list
|
|
1176
|
+
this.sockets.splice(this.sockets.indexOf(socket), 1);
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
this.server.on("error", (error) => {
|
|
547
1181
|
throw error;
|
|
548
1182
|
});
|
|
549
1183
|
}
|
|
550
1184
|
|
|
551
|
-
|
|
552
|
-
|
|
1185
|
+
getWebSocketServerImplementation() {
|
|
1186
|
+
let implementation;
|
|
1187
|
+
let implementationFound = true;
|
|
1188
|
+
|
|
1189
|
+
switch (typeof this.options.webSocketServer.type) {
|
|
1190
|
+
case "string":
|
|
1191
|
+
// Could be 'sockjs', in the future 'ws', or a path that should be required
|
|
1192
|
+
if (this.options.webSocketServer.type === "sockjs") {
|
|
1193
|
+
implementation = require("./servers/SockJSServer");
|
|
1194
|
+
} else if (this.options.webSocketServer.type === "ws") {
|
|
1195
|
+
implementation = require("./servers/WebsocketServer");
|
|
1196
|
+
} else {
|
|
1197
|
+
try {
|
|
1198
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
1199
|
+
implementation = require(this.options.webSocketServer.type);
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
implementationFound = false;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
break;
|
|
1205
|
+
case "function":
|
|
1206
|
+
implementation = this.options.webSocketServer.type;
|
|
1207
|
+
break;
|
|
1208
|
+
default:
|
|
1209
|
+
implementationFound = false;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (!implementationFound) {
|
|
1213
|
+
throw new Error(
|
|
1214
|
+
"webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to " +
|
|
1215
|
+
"a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) " +
|
|
1216
|
+
"via require.resolve(...), or the class itself which extends BaseServer"
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return implementation;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
createWebSocketServer() {
|
|
1224
|
+
this.webSocketServer = new (this.getWebSocketServerImplementation())(this);
|
|
1225
|
+
this.webSocketServer.implementation.on("connection", (client, request) => {
|
|
1226
|
+
const headers =
|
|
1227
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1228
|
+
typeof request !== "undefined"
|
|
1229
|
+
? request.headers
|
|
1230
|
+
: typeof client.headers !== "undefined"
|
|
1231
|
+
? client.headers
|
|
1232
|
+
: // eslint-disable-next-line no-undefined
|
|
1233
|
+
undefined;
|
|
1234
|
+
|
|
1235
|
+
if (!headers) {
|
|
1236
|
+
this.logger.warn(
|
|
1237
|
+
'webSocketServer implementation must pass headers for the "connection" event'
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (
|
|
1242
|
+
!headers ||
|
|
1243
|
+
!this.checkHeader(headers, "host") ||
|
|
1244
|
+
!this.checkHeader(headers, "origin")
|
|
1245
|
+
) {
|
|
1246
|
+
this.sendMessage([client], "error", "Invalid Host/Origin header");
|
|
1247
|
+
|
|
1248
|
+
client.terminate();
|
|
1249
|
+
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (this.options.hot === true || this.options.hot === "only") {
|
|
1254
|
+
this.sendMessage([client], "hot");
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (this.options.liveReload) {
|
|
1258
|
+
this.sendMessage([client], "liveReload");
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (this.options.client && this.options.client.progress) {
|
|
1262
|
+
this.sendMessage([client], "progress", this.options.client.progress);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (this.options.client && this.options.client.overlay) {
|
|
1266
|
+
this.sendMessage([client], "overlay", this.options.client.overlay);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (!this.stats) {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
this.sendStats([client], this.getStats(this.stats), true);
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
openBrowser(defaultOpenTarget) {
|
|
1278
|
+
const open = require("open");
|
|
1279
|
+
|
|
1280
|
+
Promise.all(
|
|
1281
|
+
this.options.open.map((item) => {
|
|
1282
|
+
let openTarget;
|
|
1283
|
+
|
|
1284
|
+
if (item.target === "<url>") {
|
|
1285
|
+
openTarget = defaultOpenTarget;
|
|
1286
|
+
} else {
|
|
1287
|
+
openTarget = Server.isAbsoluteURL(item.target)
|
|
1288
|
+
? item.target
|
|
1289
|
+
: new URL(item.target, defaultOpenTarget).toString();
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return open(openTarget, item.options).catch(() => {
|
|
1293
|
+
this.logger.warn(
|
|
1294
|
+
`Unable to open "${openTarget}" page${
|
|
1295
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1296
|
+
item.options.app
|
|
1297
|
+
? ` in "${item.options.app.name}" app${
|
|
1298
|
+
item.options.app.arguments
|
|
1299
|
+
? ` with "${item.options.app.arguments.join(
|
|
1300
|
+
" "
|
|
1301
|
+
)}" arguments`
|
|
1302
|
+
: ""
|
|
1303
|
+
}`
|
|
1304
|
+
: ""
|
|
1305
|
+
}. If you are running in a headless environment, please do not use the "open" option or related flags like "--open", "--open-target", and "--open-app".`
|
|
1306
|
+
);
|
|
1307
|
+
});
|
|
1308
|
+
})
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
runBonjour() {
|
|
1313
|
+
const bonjour = require("bonjour")();
|
|
1314
|
+
|
|
1315
|
+
bonjour.publish({
|
|
1316
|
+
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
|
|
1317
|
+
port: this.options.port,
|
|
1318
|
+
type: this.options.https ? "https" : "http",
|
|
1319
|
+
subtypes: ["webpack"],
|
|
1320
|
+
...this.options.bonjour,
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
process.on("exit", () => {
|
|
1324
|
+
bonjour.unpublishAll(() => {
|
|
1325
|
+
bonjour.destroy();
|
|
1326
|
+
});
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
logStatus() {
|
|
1331
|
+
const colorette = require("colorette");
|
|
553
1332
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (!headers) {
|
|
560
|
-
this.logger.warn(
|
|
561
|
-
'transportMode.server implementation must pass headers to the callback of onConnection(f) ' +
|
|
562
|
-
'via f(connection, headers) in order for clients to pass a headers security check'
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (!headers || !this.checkHost(headers) || !this.checkOrigin(headers)) {
|
|
567
|
-
this.sockWrite([connection], 'error', 'Invalid Host/Origin header');
|
|
1333
|
+
const getColorsOption = (compilerOptions) => {
|
|
1334
|
+
let colorsEnabled;
|
|
568
1335
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1336
|
+
if (
|
|
1337
|
+
compilerOptions.stats &&
|
|
1338
|
+
typeof compilerOptions.stats.colors !== "undefined"
|
|
1339
|
+
) {
|
|
1340
|
+
colorsEnabled = compilerOptions.stats;
|
|
1341
|
+
} else {
|
|
1342
|
+
colorsEnabled = colorette.options.enabled;
|
|
572
1343
|
}
|
|
573
1344
|
|
|
574
|
-
|
|
1345
|
+
return colorsEnabled;
|
|
1346
|
+
};
|
|
575
1347
|
|
|
576
|
-
|
|
577
|
-
|
|
1348
|
+
const colors = {
|
|
1349
|
+
info(useColor, msg) {
|
|
1350
|
+
if (useColor) {
|
|
1351
|
+
return colorette.cyan(msg);
|
|
1352
|
+
}
|
|
578
1353
|
|
|
579
|
-
|
|
580
|
-
|
|
1354
|
+
return msg;
|
|
1355
|
+
},
|
|
1356
|
+
error(useColor, msg) {
|
|
1357
|
+
if (useColor) {
|
|
1358
|
+
return colorette.red(msg);
|
|
581
1359
|
}
|
|
582
|
-
});
|
|
583
1360
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1361
|
+
return msg;
|
|
1362
|
+
},
|
|
1363
|
+
};
|
|
1364
|
+
const useColor = getColorsOption(this.getCompilerOptions());
|
|
587
1365
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1366
|
+
if (this.options.ipc) {
|
|
1367
|
+
this.logger.info(`Project is running at: "${this.server.address()}"`);
|
|
1368
|
+
} else {
|
|
1369
|
+
const protocol = this.options.https ? "https" : "http";
|
|
1370
|
+
const { address, port } = this.server.address();
|
|
1371
|
+
const prettyPrintURL = (newHostname) =>
|
|
1372
|
+
url.format({ protocol, hostname: newHostname, port, pathname: "/" });
|
|
1373
|
+
|
|
1374
|
+
let server;
|
|
1375
|
+
let localhost;
|
|
1376
|
+
let loopbackIPv4;
|
|
1377
|
+
let loopbackIPv6;
|
|
1378
|
+
let networkUrlIPv4;
|
|
1379
|
+
let networkUrlIPv6;
|
|
1380
|
+
|
|
1381
|
+
if (this.options.host) {
|
|
1382
|
+
if (this.options.host === "localhost") {
|
|
1383
|
+
localhost = prettyPrintURL("localhost");
|
|
1384
|
+
} else {
|
|
1385
|
+
let isIP;
|
|
591
1386
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1387
|
+
try {
|
|
1388
|
+
isIP = ipaddr.parse(this.options.host);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
// Ignore
|
|
1391
|
+
}
|
|
595
1392
|
|
|
596
|
-
|
|
597
|
-
|
|
1393
|
+
if (!isIP) {
|
|
1394
|
+
server = prettyPrintURL(this.options.host);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
598
1397
|
}
|
|
599
1398
|
|
|
600
|
-
|
|
601
|
-
this.sockWrite([connection], 'overlay', this.options.client.overlay);
|
|
602
|
-
}
|
|
1399
|
+
const parsedIP = ipaddr.parse(address);
|
|
603
1400
|
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
}
|
|
1401
|
+
if (parsedIP.range() === "unspecified") {
|
|
1402
|
+
localhost = prettyPrintURL("localhost");
|
|
607
1403
|
|
|
608
|
-
|
|
609
|
-
});
|
|
610
|
-
}
|
|
1404
|
+
const networkIPv4 = internalIp.v4.sync();
|
|
611
1405
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const { address, port } = this.server.address();
|
|
616
|
-
const prettyPrintUrl = (newHostname) =>
|
|
617
|
-
url.format({ protocol, hostname: newHostname, port, pathname: '/' });
|
|
618
|
-
|
|
619
|
-
let server;
|
|
620
|
-
let localhost;
|
|
621
|
-
let loopbackIPv4;
|
|
622
|
-
let loopbackIPv6;
|
|
623
|
-
let networkUrlIPv4;
|
|
624
|
-
let networkUrlIPv6;
|
|
625
|
-
|
|
626
|
-
if (this.hostname) {
|
|
627
|
-
if (this.hostname === 'localhost') {
|
|
628
|
-
localhost = prettyPrintUrl('localhost');
|
|
629
|
-
} else {
|
|
630
|
-
let isIP;
|
|
1406
|
+
if (networkIPv4) {
|
|
1407
|
+
networkUrlIPv4 = prettyPrintURL(networkIPv4);
|
|
1408
|
+
}
|
|
631
1409
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
1410
|
+
const networkIPv6 = internalIp.v6.sync();
|
|
1411
|
+
|
|
1412
|
+
if (networkIPv6) {
|
|
1413
|
+
networkUrlIPv6 = prettyPrintURL(networkIPv6);
|
|
1414
|
+
}
|
|
1415
|
+
} else if (parsedIP.range() === "loopback") {
|
|
1416
|
+
if (parsedIP.kind() === "ipv4") {
|
|
1417
|
+
loopbackIPv4 = prettyPrintURL(parsedIP.toString());
|
|
1418
|
+
} else if (parsedIP.kind() === "ipv6") {
|
|
1419
|
+
loopbackIPv6 = prettyPrintURL(parsedIP.toString());
|
|
636
1420
|
}
|
|
1421
|
+
} else {
|
|
1422
|
+
networkUrlIPv4 =
|
|
1423
|
+
parsedIP.kind() === "ipv6" && parsedIP.isIPv4MappedAddress()
|
|
1424
|
+
? prettyPrintURL(parsedIP.toIPv4Address().toString())
|
|
1425
|
+
: prettyPrintURL(address);
|
|
637
1426
|
|
|
638
|
-
if (
|
|
639
|
-
|
|
1427
|
+
if (parsedIP.kind() === "ipv6") {
|
|
1428
|
+
networkUrlIPv6 = prettyPrintURL(address);
|
|
640
1429
|
}
|
|
641
1430
|
}
|
|
642
|
-
}
|
|
643
1431
|
|
|
644
|
-
|
|
1432
|
+
this.logger.info("Project is running at:");
|
|
645
1433
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const networkIPv4 = internalIp.v4.sync();
|
|
650
|
-
|
|
651
|
-
if (networkIPv4) {
|
|
652
|
-
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
|
|
1434
|
+
if (server) {
|
|
1435
|
+
this.logger.info(`Server: ${colors.info(useColor, server)}`);
|
|
653
1436
|
}
|
|
654
1437
|
|
|
655
|
-
|
|
1438
|
+
if (localhost || loopbackIPv4 || loopbackIPv6) {
|
|
1439
|
+
const loopbacks = []
|
|
1440
|
+
.concat(localhost ? [colors.info(useColor, localhost)] : [])
|
|
1441
|
+
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
|
|
1442
|
+
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
|
|
656
1443
|
|
|
657
|
-
|
|
658
|
-
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
|
|
1444
|
+
this.logger.info(`Loopback: ${loopbacks.join(", ")}`);
|
|
659
1445
|
}
|
|
660
|
-
} else if (parsedIP.range() === 'loopback') {
|
|
661
|
-
if (parsedIP.kind() === 'ipv4') {
|
|
662
|
-
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
|
|
663
|
-
} else if (parsedIP.kind() === 'ipv6') {
|
|
664
|
-
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
|
|
665
|
-
}
|
|
666
|
-
} else {
|
|
667
|
-
networkUrlIPv4 =
|
|
668
|
-
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
|
|
669
|
-
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
|
|
670
|
-
: prettyPrintUrl(address);
|
|
671
1446
|
|
|
672
|
-
if (
|
|
673
|
-
|
|
1447
|
+
if (networkUrlIPv4) {
|
|
1448
|
+
this.logger.info(
|
|
1449
|
+
`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`
|
|
1450
|
+
);
|
|
674
1451
|
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
this.logger.info('Project is running at:');
|
|
678
|
-
|
|
679
|
-
if (server) {
|
|
680
|
-
this.logger.info(`Server: ${colors.info(useColor, server)}`);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (localhost || loopbackIPv4 || loopbackIPv6) {
|
|
684
|
-
const loopbacks = []
|
|
685
|
-
.concat(localhost ? [colors.info(useColor, localhost)] : [])
|
|
686
|
-
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
|
|
687
|
-
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
|
|
688
|
-
|
|
689
|
-
this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
|
|
690
|
-
}
|
|
691
1452
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1453
|
+
if (networkUrlIPv6) {
|
|
1454
|
+
this.logger.info(
|
|
1455
|
+
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
697
1458
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
|
|
701
|
-
);
|
|
702
|
-
}
|
|
1459
|
+
if (this.options.open.length > 0) {
|
|
1460
|
+
const openTarget = prettyPrintURL(this.options.host || "localhost");
|
|
703
1461
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
typeof this.options.dev.publicPath !== 'undefined'
|
|
707
|
-
) {
|
|
708
|
-
this.logger.info(
|
|
709
|
-
`webpack output is served from '${colors.info(
|
|
710
|
-
useColor,
|
|
711
|
-
this.options.dev.publicPath === 'auto'
|
|
712
|
-
? '/'
|
|
713
|
-
: this.options.dev.publicPath
|
|
714
|
-
)}' URL`
|
|
715
|
-
);
|
|
1462
|
+
this.openBrowser(openTarget);
|
|
1463
|
+
}
|
|
716
1464
|
}
|
|
717
1465
|
|
|
718
1466
|
if (this.options.static && this.options.static.length > 0) {
|
|
@@ -721,7 +1469,7 @@ class Server {
|
|
|
721
1469
|
useColor,
|
|
722
1470
|
this.options.static
|
|
723
1471
|
.map((staticOption) => staticOption.directory)
|
|
724
|
-
.join(
|
|
1472
|
+
.join(", ")
|
|
725
1473
|
)}' directory`
|
|
726
1474
|
);
|
|
727
1475
|
}
|
|
@@ -730,153 +1478,45 @@ class Server {
|
|
|
730
1478
|
this.logger.info(
|
|
731
1479
|
`404s will fallback to '${colors.info(
|
|
732
1480
|
useColor,
|
|
733
|
-
this.options.historyApiFallback.index ||
|
|
1481
|
+
this.options.historyApiFallback.index || "/index.html"
|
|
734
1482
|
)}'`
|
|
735
1483
|
);
|
|
736
1484
|
}
|
|
737
1485
|
|
|
738
1486
|
if (this.options.bonjour) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (this.options.open) {
|
|
745
|
-
const openTarget = prettyPrintUrl(this.hostname || 'localhost');
|
|
746
|
-
|
|
747
|
-
runOpen(openTarget, this.options.open, this.logger);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
listen(port, hostname, fn) {
|
|
752
|
-
if (hostname === 'local-ip') {
|
|
753
|
-
this.hostname = internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
|
|
754
|
-
} else if (hostname === 'local-ipv4') {
|
|
755
|
-
this.hostname = internalIp.v4.sync() || '0.0.0.0';
|
|
756
|
-
} else if (hostname === 'local-ipv6') {
|
|
757
|
-
this.hostname = internalIp.v6.sync() || '::';
|
|
758
|
-
} else {
|
|
759
|
-
this.hostname = hostname;
|
|
760
|
-
}
|
|
1487
|
+
const bonjourProtocol =
|
|
1488
|
+
this.options.bonjour.type || this.options.https ? "https" : "http";
|
|
761
1489
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
'The port specified in options and the port passed as an argument is different.'
|
|
1490
|
+
this.logger.info(
|
|
1491
|
+
`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`
|
|
765
1492
|
);
|
|
766
1493
|
}
|
|
767
|
-
|
|
768
|
-
return (
|
|
769
|
-
findPort(port || this.options.port)
|
|
770
|
-
// eslint-disable-next-line no-shadow
|
|
771
|
-
.then((port) => {
|
|
772
|
-
this.port = port;
|
|
773
|
-
return this.server.listen(port, this.hostname, (error) => {
|
|
774
|
-
if (this.options.hot || this.options.liveReload) {
|
|
775
|
-
this.createSocketServer();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
if (this.options.bonjour) {
|
|
779
|
-
runBonjour(this.options);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
this.showStatus();
|
|
783
|
-
|
|
784
|
-
if (fn) {
|
|
785
|
-
fn.call(this.server, error);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
if (typeof this.options.onListening === 'function') {
|
|
789
|
-
this.options.onListening(this);
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
})
|
|
793
|
-
.catch((error) => {
|
|
794
|
-
if (fn) {
|
|
795
|
-
fn.call(this.server, error);
|
|
796
|
-
}
|
|
797
|
-
})
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
close(cb) {
|
|
802
|
-
this.sockets.forEach((socket) => {
|
|
803
|
-
this.socketServer.close(socket);
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
this.sockets = [];
|
|
807
|
-
|
|
808
|
-
const prom = Promise.all(
|
|
809
|
-
this.staticWatchers.map((watcher) => watcher.close())
|
|
810
|
-
);
|
|
811
|
-
this.staticWatchers = [];
|
|
812
|
-
|
|
813
|
-
this.server.kill(() => {
|
|
814
|
-
// watchers must be closed before closing middleware
|
|
815
|
-
prom.then(() => {
|
|
816
|
-
this.middleware.close(cb);
|
|
817
|
-
});
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
static get DEFAULT_STATS() {
|
|
822
|
-
return {
|
|
823
|
-
all: false,
|
|
824
|
-
hash: true,
|
|
825
|
-
assets: true,
|
|
826
|
-
warnings: true,
|
|
827
|
-
errors: true,
|
|
828
|
-
errorDetails: false,
|
|
829
|
-
};
|
|
830
1494
|
}
|
|
831
1495
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const configArr = getCompilerConfigArray(this.compiler);
|
|
836
|
-
const statsOption = getStatsOption(configArr);
|
|
837
|
-
|
|
838
|
-
if (typeof statsOption === 'object' && statsOption.warningsFilter) {
|
|
839
|
-
stats.warningsFilter = statsOption.warningsFilter;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return statsObj.toJson(stats);
|
|
843
|
-
}
|
|
1496
|
+
setHeaders(req, res, next) {
|
|
1497
|
+
let { headers } = this.options;
|
|
844
1498
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1499
|
+
if (headers) {
|
|
1500
|
+
if (typeof headers === "function") {
|
|
1501
|
+
headers = headers(req, res, this.middleware.context);
|
|
1502
|
+
}
|
|
849
1503
|
|
|
850
|
-
setContentHeaders(req, res, next) {
|
|
851
|
-
if (this.options.headers) {
|
|
852
1504
|
// eslint-disable-next-line guard-for-in
|
|
853
|
-
for (const name in
|
|
854
|
-
res.setHeader(name,
|
|
1505
|
+
for (const name in headers) {
|
|
1506
|
+
res.setHeader(name, headers[name]);
|
|
855
1507
|
}
|
|
856
1508
|
}
|
|
857
1509
|
|
|
858
1510
|
next();
|
|
859
1511
|
}
|
|
860
1512
|
|
|
861
|
-
|
|
862
|
-
return this.checkHeaders(headers, 'host');
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
checkOrigin(headers) {
|
|
866
|
-
return this.checkHeaders(headers, 'origin');
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
checkHeaders(headers, headerToCheck) {
|
|
1513
|
+
checkHeader(headers, headerToCheck) {
|
|
870
1514
|
// allow user to opt out of this security check, at their own risk
|
|
871
|
-
// by explicitly
|
|
872
|
-
if (
|
|
1515
|
+
// by explicitly enabling allowedHosts
|
|
1516
|
+
if (this.options.allowedHosts === "all") {
|
|
873
1517
|
return true;
|
|
874
1518
|
}
|
|
875
1519
|
|
|
876
|
-
if (!headerToCheck) {
|
|
877
|
-
headerToCheck = 'host';
|
|
878
|
-
}
|
|
879
|
-
|
|
880
1520
|
// get the Host header and extract hostname
|
|
881
1521
|
// we don't care about port not matching
|
|
882
1522
|
const hostHeader = headers[headerToCheck];
|
|
@@ -900,22 +1540,22 @@ class Server {
|
|
|
900
1540
|
// these are removed from the hostname in url.parse(),
|
|
901
1541
|
// so we have the pure IPv6-address in hostname.
|
|
902
1542
|
// always allow localhost host, for convenience (hostname === 'localhost')
|
|
903
|
-
// allow hostname of listening address (hostname === this.
|
|
1543
|
+
// allow hostname of listening address (hostname === this.options.host)
|
|
904
1544
|
const isValidHostname =
|
|
905
1545
|
ipaddr.IPv4.isValid(hostname) ||
|
|
906
1546
|
ipaddr.IPv6.isValid(hostname) ||
|
|
907
|
-
hostname ===
|
|
908
|
-
hostname === this.
|
|
1547
|
+
hostname === "localhost" ||
|
|
1548
|
+
hostname === this.options.host;
|
|
909
1549
|
|
|
910
1550
|
if (isValidHostname) {
|
|
911
1551
|
return true;
|
|
912
1552
|
}
|
|
913
1553
|
|
|
914
|
-
const allowedHosts = this.options
|
|
1554
|
+
const { allowedHosts } = this.options;
|
|
915
1555
|
|
|
916
1556
|
// always allow localhost host, for convenience
|
|
917
1557
|
// allow if hostname is in allowedHosts
|
|
918
|
-
if (Array.isArray(allowedHosts) && allowedHosts.length) {
|
|
1558
|
+
if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
|
|
919
1559
|
for (let hostIdx = 0; hostIdx < allowedHosts.length; hostIdx++) {
|
|
920
1560
|
const allowedHost = allowedHosts[hostIdx];
|
|
921
1561
|
|
|
@@ -925,7 +1565,7 @@ class Server {
|
|
|
925
1565
|
|
|
926
1566
|
// support "." as a subdomain wildcard
|
|
927
1567
|
// e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
|
|
928
|
-
if (allowedHost[0] ===
|
|
1568
|
+
if (allowedHost[0] === ".") {
|
|
929
1569
|
// "example.com" (hostname === allowedHost.substring(1))
|
|
930
1570
|
// "*.example.com" (hostname.endsWith(allowedHost))
|
|
931
1571
|
if (
|
|
@@ -938,76 +1578,82 @@ class Server {
|
|
|
938
1578
|
}
|
|
939
1579
|
}
|
|
940
1580
|
|
|
941
|
-
//
|
|
942
|
-
if (
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
: this.options.public;
|
|
948
|
-
|
|
949
|
-
if (hostname === publicHostname) {
|
|
950
|
-
return true;
|
|
951
|
-
}
|
|
1581
|
+
// Also allow if `client.webSocketURL.hostname` provided
|
|
1582
|
+
if (
|
|
1583
|
+
this.options.client &&
|
|
1584
|
+
typeof this.options.client.webSocketURL !== "undefined"
|
|
1585
|
+
) {
|
|
1586
|
+
return this.options.client.webSocketURL.hostname === hostname;
|
|
952
1587
|
}
|
|
953
1588
|
|
|
954
1589
|
// disallow
|
|
955
1590
|
return false;
|
|
956
1591
|
}
|
|
957
1592
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1593
|
+
// eslint-disable-next-line class-methods-use-this
|
|
1594
|
+
sendMessage(clients, type, data) {
|
|
1595
|
+
clients.forEach((client) => {
|
|
1596
|
+
// `sockjs` uses `1` to indicate client is ready to accept data
|
|
1597
|
+
// `ws` uses `WebSocket.OPEN`, but it is mean `1` too
|
|
1598
|
+
if (client.readyState === 1) {
|
|
1599
|
+
client.send(JSON.stringify({ type, data }));
|
|
1600
|
+
}
|
|
961
1601
|
});
|
|
962
1602
|
}
|
|
963
1603
|
|
|
964
1604
|
serveMagicHtml(req, res, next) {
|
|
965
|
-
|
|
1605
|
+
this.middleware.waitUntilValid(() => {
|
|
1606
|
+
const _path = req.path;
|
|
966
1607
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
this.middleware.context
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const isFile = this.middleware.context.outputFileSystem
|
|
973
|
-
.statSync(filename)
|
|
974
|
-
.isFile();
|
|
1608
|
+
try {
|
|
1609
|
+
const filename = this.middleware.getFilenameFromUrl(`${_path}.js`);
|
|
1610
|
+
const isFile = this.middleware.context.outputFileSystem
|
|
1611
|
+
.statSync(filename)
|
|
1612
|
+
.isFile();
|
|
975
1613
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1614
|
+
if (!isFile) {
|
|
1615
|
+
return next();
|
|
1616
|
+
}
|
|
979
1617
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1618
|
+
// Serve a page that executes the javascript
|
|
1619
|
+
const queries = req._parsedUrl.search || "";
|
|
1620
|
+
const responsePage = `<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="${_path}.js${queries}"></script></body></html>`;
|
|
983
1621
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1622
|
+
res.send(responsePage);
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
return next();
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
988
1627
|
}
|
|
989
1628
|
|
|
990
|
-
//
|
|
991
|
-
sendStats(
|
|
1629
|
+
// Send stats to a socket or multiple sockets
|
|
1630
|
+
sendStats(clients, stats, force) {
|
|
992
1631
|
const shouldEmit =
|
|
993
1632
|
!force &&
|
|
994
1633
|
stats &&
|
|
995
1634
|
(!stats.errors || stats.errors.length === 0) &&
|
|
1635
|
+
(!stats.warnings || stats.warnings.length === 0) &&
|
|
996
1636
|
stats.assets &&
|
|
997
1637
|
stats.assets.every((asset) => !asset.emitted);
|
|
998
1638
|
|
|
999
1639
|
if (shouldEmit) {
|
|
1000
|
-
|
|
1640
|
+
this.sendMessage(clients, "still-ok");
|
|
1641
|
+
|
|
1642
|
+
return;
|
|
1001
1643
|
}
|
|
1002
1644
|
|
|
1003
|
-
this.
|
|
1645
|
+
this.sendMessage(clients, "hash", stats.hash);
|
|
1646
|
+
|
|
1647
|
+
if (stats.errors.length > 0 || stats.warnings.length > 0) {
|
|
1648
|
+
if (stats.warnings.length > 0) {
|
|
1649
|
+
this.sendMessage(clients, "warnings", stats.warnings);
|
|
1650
|
+
}
|
|
1004
1651
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
this.sockWrite(sockets, 'warnings', stats.warnings);
|
|
1652
|
+
if (stats.errors.length > 0) {
|
|
1653
|
+
this.sendMessage(clients, "errors", stats.errors);
|
|
1654
|
+
}
|
|
1009
1655
|
} else {
|
|
1010
|
-
this.
|
|
1656
|
+
this.sendMessage(clients, "ok");
|
|
1011
1657
|
}
|
|
1012
1658
|
}
|
|
1013
1659
|
|
|
@@ -1016,9 +1662,15 @@ class Server {
|
|
|
1016
1662
|
// https://github.com/webpack/watchpack/blob/master/lib/DirectoryWatcher.js#L49
|
|
1017
1663
|
// this isn't an elegant solution, but we'll improve it in the future
|
|
1018
1664
|
// eslint-disable-next-line no-undefined
|
|
1019
|
-
const usePolling =
|
|
1665
|
+
const usePolling =
|
|
1666
|
+
typeof watchOptions.usePolling !== "undefined"
|
|
1667
|
+
? watchOptions.usePolling
|
|
1668
|
+
: Boolean(watchOptions.poll);
|
|
1020
1669
|
const interval =
|
|
1021
|
-
|
|
1670
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1671
|
+
typeof watchOptions.interval !== "undefined"
|
|
1672
|
+
? watchOptions.interval
|
|
1673
|
+
: typeof watchOptions.poll === "number"
|
|
1022
1674
|
? watchOptions.poll
|
|
1023
1675
|
: // eslint-disable-next-line no-undefined
|
|
1024
1676
|
undefined;
|
|
@@ -1035,12 +1687,20 @@ class Server {
|
|
|
1035
1687
|
interval,
|
|
1036
1688
|
};
|
|
1037
1689
|
|
|
1690
|
+
const chokidar = require("chokidar");
|
|
1691
|
+
|
|
1038
1692
|
const watcher = chokidar.watch(watchPath, finalWatchOptions);
|
|
1039
1693
|
|
|
1040
1694
|
// disabling refreshing on changing the content
|
|
1041
1695
|
if (this.options.liveReload) {
|
|
1042
|
-
watcher.on(
|
|
1043
|
-
|
|
1696
|
+
watcher.on("change", (item) => {
|
|
1697
|
+
if (this.webSocketServer) {
|
|
1698
|
+
this.sendMessage(
|
|
1699
|
+
this.webSocketServer.clients,
|
|
1700
|
+
"static-changed",
|
|
1701
|
+
item
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1044
1704
|
});
|
|
1045
1705
|
}
|
|
1046
1706
|
|
|
@@ -1052,6 +1712,263 @@ class Server {
|
|
|
1052
1712
|
this.middleware.invalidate(callback);
|
|
1053
1713
|
}
|
|
1054
1714
|
}
|
|
1715
|
+
|
|
1716
|
+
async start() {
|
|
1717
|
+
await this.normalizeOptions();
|
|
1718
|
+
|
|
1719
|
+
if (this.options.ipc) {
|
|
1720
|
+
await new Promise((resolve, reject) => {
|
|
1721
|
+
const net = require("net");
|
|
1722
|
+
const socket = new net.Socket();
|
|
1723
|
+
|
|
1724
|
+
socket.on("error", (error) => {
|
|
1725
|
+
if (error.code === "ECONNREFUSED") {
|
|
1726
|
+
// No other server listening on this socket so it can be safely removed
|
|
1727
|
+
fs.unlinkSync(this.options.ipc);
|
|
1728
|
+
|
|
1729
|
+
resolve();
|
|
1730
|
+
|
|
1731
|
+
return;
|
|
1732
|
+
} else if (error.code === "ENOENT") {
|
|
1733
|
+
resolve();
|
|
1734
|
+
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
reject(error);
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
socket.connect({ path: this.options.ipc }, () => {
|
|
1742
|
+
throw new Error(`IPC "${this.options.ipc}" is already used`);
|
|
1743
|
+
});
|
|
1744
|
+
});
|
|
1745
|
+
} else {
|
|
1746
|
+
this.options.host = await Server.getHostname(this.options.host);
|
|
1747
|
+
this.options.port = await Server.getFreePort(this.options.port);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
await this.initialize();
|
|
1751
|
+
|
|
1752
|
+
const listenOptions = this.options.ipc
|
|
1753
|
+
? { path: this.options.ipc }
|
|
1754
|
+
: { host: this.options.host, port: this.options.port };
|
|
1755
|
+
|
|
1756
|
+
await new Promise((resolve) => {
|
|
1757
|
+
this.server.listen(listenOptions, () => {
|
|
1758
|
+
resolve();
|
|
1759
|
+
});
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
if (this.options.ipc) {
|
|
1763
|
+
// chmod 666 (rw rw rw)
|
|
1764
|
+
const READ_WRITE = 438;
|
|
1765
|
+
|
|
1766
|
+
fs.chmodSync(this.options.ipc, READ_WRITE);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
if (this.options.webSocketServer) {
|
|
1770
|
+
this.createWebSocketServer();
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
if (this.options.bonjour) {
|
|
1774
|
+
this.runBonjour();
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
this.logStatus();
|
|
1778
|
+
|
|
1779
|
+
if (typeof this.options.onListening === "function") {
|
|
1780
|
+
this.options.onListening(this);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
startCallback(callback) {
|
|
1785
|
+
this.start().then(() => callback(null), callback);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async stop() {
|
|
1789
|
+
this.webSocketProxies = [];
|
|
1790
|
+
|
|
1791
|
+
await Promise.all(this.staticWatchers.map((watcher) => watcher.close()));
|
|
1792
|
+
|
|
1793
|
+
this.staticWatchers = [];
|
|
1794
|
+
|
|
1795
|
+
if (this.webSocketServer) {
|
|
1796
|
+
await new Promise((resolve) => {
|
|
1797
|
+
this.webSocketServer.implementation.close(() => {
|
|
1798
|
+
this.webSocketServer = null;
|
|
1799
|
+
|
|
1800
|
+
resolve();
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
for (const client of this.webSocketServer.clients) {
|
|
1804
|
+
client.terminate();
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
this.webSocketServer.clients = [];
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
if (this.server) {
|
|
1812
|
+
await new Promise((resolve) => {
|
|
1813
|
+
this.server.close(() => {
|
|
1814
|
+
this.server = null;
|
|
1815
|
+
|
|
1816
|
+
resolve();
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
for (const socket of this.sockets) {
|
|
1820
|
+
socket.destroy();
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
this.sockets = [];
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
if (this.middleware) {
|
|
1827
|
+
await new Promise((resolve, reject) => {
|
|
1828
|
+
this.middleware.close((error) => {
|
|
1829
|
+
if (error) {
|
|
1830
|
+
reject(error);
|
|
1831
|
+
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
resolve();
|
|
1836
|
+
});
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
this.middleware = null;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
stopCallback(callback) {
|
|
1845
|
+
this.stop().then(() => callback(null), callback);
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// TODO remove in the next major release
|
|
1849
|
+
listen(port, hostname, fn) {
|
|
1850
|
+
util.deprecate(
|
|
1851
|
+
() => {},
|
|
1852
|
+
"'listen' is deprecated. Please use async 'start' or 'startCallback' methods.",
|
|
1853
|
+
"DEP_WEBPACK_DEV_SERVER_LISTEN"
|
|
1854
|
+
)();
|
|
1855
|
+
|
|
1856
|
+
this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
|
|
1857
|
+
|
|
1858
|
+
if (typeof port === "function") {
|
|
1859
|
+
fn = port;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
if (
|
|
1863
|
+
typeof port !== "undefined" &&
|
|
1864
|
+
typeof this.options.port !== "undefined" &&
|
|
1865
|
+
port !== this.options.port
|
|
1866
|
+
) {
|
|
1867
|
+
this.options.port = port;
|
|
1868
|
+
|
|
1869
|
+
this.logger.warn(
|
|
1870
|
+
'The "port" specified in options is different from the port passed as an argument. Will be used from arguments.'
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
if (!this.options.port) {
|
|
1875
|
+
this.options.port = port;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (
|
|
1879
|
+
typeof hostname !== "undefined" &&
|
|
1880
|
+
typeof this.options.host !== "undefined" &&
|
|
1881
|
+
hostname !== this.options.host
|
|
1882
|
+
) {
|
|
1883
|
+
this.options.host = hostname;
|
|
1884
|
+
|
|
1885
|
+
this.logger.warn(
|
|
1886
|
+
'The "host" specified in options is different from the host passed as an argument. Will be used from arguments.'
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (!this.options.host) {
|
|
1891
|
+
this.options.host = hostname;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
return this.start()
|
|
1895
|
+
.then(() => {
|
|
1896
|
+
if (fn) {
|
|
1897
|
+
fn.call(this.server);
|
|
1898
|
+
}
|
|
1899
|
+
})
|
|
1900
|
+
.catch((error) => {
|
|
1901
|
+
// Nothing
|
|
1902
|
+
if (fn) {
|
|
1903
|
+
fn.call(this.server, error);
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// TODO remove in the next major release
|
|
1909
|
+
close(callback) {
|
|
1910
|
+
util.deprecate(
|
|
1911
|
+
() => {},
|
|
1912
|
+
"'close' is deprecated. Please use async 'stop' or 'stopCallback' methods.",
|
|
1913
|
+
"DEP_WEBPACK_DEV_SERVER_CLOSE"
|
|
1914
|
+
)();
|
|
1915
|
+
|
|
1916
|
+
return this.stop()
|
|
1917
|
+
.then(() => {
|
|
1918
|
+
if (callback) {
|
|
1919
|
+
callback(null);
|
|
1920
|
+
}
|
|
1921
|
+
})
|
|
1922
|
+
.catch((error) => {
|
|
1923
|
+
if (callback) {
|
|
1924
|
+
callback(error);
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1055
1928
|
}
|
|
1056
1929
|
|
|
1057
|
-
|
|
1930
|
+
const mergeExports = (obj, exports) => {
|
|
1931
|
+
const descriptors = Object.getOwnPropertyDescriptors(exports);
|
|
1932
|
+
|
|
1933
|
+
for (const name of Object.keys(descriptors)) {
|
|
1934
|
+
const descriptor = descriptors[name];
|
|
1935
|
+
|
|
1936
|
+
if (descriptor.get) {
|
|
1937
|
+
const fn = descriptor.get;
|
|
1938
|
+
|
|
1939
|
+
Object.defineProperty(obj, name, {
|
|
1940
|
+
configurable: false,
|
|
1941
|
+
enumerable: true,
|
|
1942
|
+
get: fn,
|
|
1943
|
+
});
|
|
1944
|
+
} else if (typeof descriptor.value === "object") {
|
|
1945
|
+
Object.defineProperty(obj, name, {
|
|
1946
|
+
configurable: false,
|
|
1947
|
+
enumerable: true,
|
|
1948
|
+
writable: false,
|
|
1949
|
+
value: mergeExports({}, descriptor.value),
|
|
1950
|
+
});
|
|
1951
|
+
} else {
|
|
1952
|
+
throw new Error(
|
|
1953
|
+
"Exposed values must be either a getter or an nested object"
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
return Object.freeze(obj);
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
module.exports = mergeExports(Server, {
|
|
1962
|
+
get schema() {
|
|
1963
|
+
return schema;
|
|
1964
|
+
},
|
|
1965
|
+
// TODO compatibility with webpack v4, remove it after drop
|
|
1966
|
+
cli: {
|
|
1967
|
+
get getArguments() {
|
|
1968
|
+
return () => require("../bin/cli-flags");
|
|
1969
|
+
},
|
|
1970
|
+
get processArguments() {
|
|
1971
|
+
return require("../bin/process-arguments");
|
|
1972
|
+
},
|
|
1973
|
+
},
|
|
1974
|
+
});
|