@rspack/dev-server 1.1.4 → 1.2.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 +14 -17
- package/client/clients/SockJSClient.js +34 -0
- package/client/clients/WebSocketClient.js +32 -0
- package/client/index.js +131 -214
- package/client/modules/logger/index.js +725 -0
- package/client/modules/sockjs-client/index.js +4506 -0
- package/client/overlay.js +503 -0
- package/client/progress.js +130 -0
- package/client/socket.js +60 -0
- package/client/utils/ansiHTML.js +63 -64
- package/client/utils/log.js +21 -0
- package/client/utils/sendMessage.js +17 -0
- package/dist/config.d.ts +14 -15
- package/dist/getPort.d.ts +10 -0
- package/dist/getPort.js +131 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -1
- package/dist/options.json +1034 -0
- package/dist/server.d.ts +106 -23
- package/dist/server.js +2195 -46
- package/dist/servers/BaseServer.d.ts +17 -0
- package/dist/servers/BaseServer.js +20 -0
- package/dist/servers/SockJSServer.d.ts +10 -0
- package/dist/servers/SockJSServer.js +110 -0
- package/dist/servers/WebsocketServer.d.ts +10 -0
- package/dist/servers/WebsocketServer.js +72 -0
- package/dist/types.d.ts +158 -0
- package/dist/types.js +5 -0
- package/package.json +66 -29
- package/dist/patch.d.ts +0 -3
- package/dist/patch.js +0 -32
package/dist/server.js
CHANGED
|
@@ -1,72 +1,2221 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The following code is modified based on
|
|
4
|
+
* https://github.com/webpack/webpack-dev-server
|
|
5
|
+
*
|
|
6
|
+
* MIT Licensed
|
|
7
|
+
* Author Tobias Koppers @sokra
|
|
8
|
+
* Copyright (c) JS Foundation and other contributors
|
|
9
|
+
* https://github.com/webpack/webpack-dev-server/blob/main/LICENSE
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
2
34
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
36
|
};
|
|
5
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const url = __importStar(require("node:url"));
|
|
41
|
+
const util = __importStar(require("node:util"));
|
|
42
|
+
const fs = __importStar(require("graceful-fs"));
|
|
43
|
+
const ipaddr = __importStar(require("ipaddr.js"));
|
|
44
|
+
const schema_utils_1 = require("schema-utils");
|
|
45
|
+
const options_json_1 = __importDefault(require("./options.json"));
|
|
46
|
+
// Define BasicApplication and Server as ambient, or import them
|
|
47
|
+
if (!process.env.WEBPACK_SERVE) {
|
|
48
|
+
process.env.WEBPACK_SERVE = 'true';
|
|
49
|
+
}
|
|
50
|
+
const memoize = (fn) => {
|
|
51
|
+
let cache = false;
|
|
52
|
+
let result;
|
|
53
|
+
let fnRef = fn;
|
|
54
|
+
return () => {
|
|
55
|
+
if (cache) {
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
result = fnRef();
|
|
59
|
+
cache = true;
|
|
60
|
+
// Allow to clean up memory for fn and all dependent resources
|
|
61
|
+
fnRef = undefined;
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const getExpress = memoize(() => require('express'));
|
|
66
|
+
const encodeOverlaySettings = (setting) => {
|
|
67
|
+
return typeof setting === 'function'
|
|
68
|
+
? encodeURIComponent(setting.toString())
|
|
69
|
+
: setting;
|
|
30
70
|
};
|
|
31
|
-
|
|
32
|
-
|
|
71
|
+
const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i;
|
|
72
|
+
function isMultiCompiler(compiler) {
|
|
73
|
+
return Array.isArray(compiler.compilers);
|
|
74
|
+
}
|
|
75
|
+
class Server {
|
|
33
76
|
constructor(options, compiler) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
77
|
+
this.isTlsServer = false;
|
|
78
|
+
(0, schema_utils_1.validate)(options_json_1.default, options, {
|
|
79
|
+
name: 'Dev Server',
|
|
80
|
+
baseDataPath: 'options',
|
|
81
|
+
});
|
|
82
|
+
this.compiler = compiler;
|
|
83
|
+
this.logger = this.compiler.getInfrastructureLogger('webpack-dev-server');
|
|
84
|
+
this.options = options;
|
|
85
|
+
this.staticWatchers = [];
|
|
86
|
+
this.listeners = [];
|
|
87
|
+
this.webSocketProxies = [];
|
|
88
|
+
this.sockets = [];
|
|
89
|
+
this.currentHash = undefined;
|
|
90
|
+
}
|
|
91
|
+
static get schema() {
|
|
92
|
+
return options_json_1.default;
|
|
93
|
+
}
|
|
94
|
+
static get DEFAULT_STATS() {
|
|
95
|
+
return {
|
|
96
|
+
all: false,
|
|
97
|
+
hash: true,
|
|
98
|
+
warnings: true,
|
|
99
|
+
errors: true,
|
|
100
|
+
errorDetails: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
static isAbsoluteURL(URL) {
|
|
104
|
+
// Don't match Windows paths `c:\`
|
|
105
|
+
if (/^[a-zA-Z]:\\/.test(URL)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
|
|
109
|
+
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
|
|
110
|
+
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
|
|
111
|
+
}
|
|
112
|
+
static findIp(gatewayOrFamily, isInternal) {
|
|
113
|
+
if (gatewayOrFamily === 'v4' || gatewayOrFamily === 'v6') {
|
|
114
|
+
let host;
|
|
115
|
+
const networks = Object.values(os.networkInterfaces())
|
|
116
|
+
.flatMap((networks) => networks ?? [])
|
|
117
|
+
.filter((network) => {
|
|
118
|
+
if (!network || !network.address) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (network.family !== `IP${gatewayOrFamily}`) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (typeof isInternal !== 'undefined' &&
|
|
125
|
+
network.internal !== isInternal) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (gatewayOrFamily === 'v6') {
|
|
129
|
+
const range = ipaddr.parse(network.address).range();
|
|
130
|
+
if (range !== 'ipv4Mapped' &&
|
|
131
|
+
range !== 'uniqueLocal' &&
|
|
132
|
+
range !== 'loopback') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return network.address;
|
|
137
|
+
});
|
|
138
|
+
if (networks.length > 0) {
|
|
139
|
+
// Take the first network found
|
|
140
|
+
host = networks[0].address;
|
|
141
|
+
if (host.includes(':')) {
|
|
142
|
+
host = `[${host}]`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return host;
|
|
146
|
+
}
|
|
147
|
+
const gatewayIp = ipaddr.parse(gatewayOrFamily);
|
|
148
|
+
// Look for the matching interface in all local interfaces.
|
|
149
|
+
for (const addresses of Object.values(os.networkInterfaces())) {
|
|
150
|
+
for (const { cidr } of addresses) {
|
|
151
|
+
const net = ipaddr.parseCIDR(cidr);
|
|
152
|
+
if (net[0] &&
|
|
153
|
+
net[0].kind() === gatewayIp.kind() &&
|
|
154
|
+
gatewayIp.match(net)) {
|
|
155
|
+
return net[0].toString();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// TODO remove me in the next major release, we have `findIp`
|
|
161
|
+
static async internalIP(family) {
|
|
162
|
+
return Server.findIp(family, false);
|
|
163
|
+
}
|
|
164
|
+
// TODO remove me in the next major release, we have `findIp`
|
|
165
|
+
static internalIPSync(family) {
|
|
166
|
+
return Server.findIp(family, false);
|
|
37
167
|
}
|
|
168
|
+
static async getHostname(hostname) {
|
|
169
|
+
if (hostname === 'local-ip') {
|
|
170
|
+
return (Server.findIp('v4', false) || Server.findIp('v6', false) || '0.0.0.0');
|
|
171
|
+
}
|
|
172
|
+
if (hostname === 'local-ipv4') {
|
|
173
|
+
return Server.findIp('v4', false) || '0.0.0.0';
|
|
174
|
+
}
|
|
175
|
+
if (hostname === 'local-ipv6') {
|
|
176
|
+
return Server.findIp('v6', false) || '::';
|
|
177
|
+
}
|
|
178
|
+
return hostname;
|
|
179
|
+
}
|
|
180
|
+
static async getFreePort(port, host) {
|
|
181
|
+
if (typeof port !== 'undefined' && port !== null && port !== 'auto') {
|
|
182
|
+
return port;
|
|
183
|
+
}
|
|
184
|
+
const { default: pRetry } = await import('p-retry');
|
|
185
|
+
const getPort = require('./getPort');
|
|
186
|
+
const basePort = typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== 'undefined'
|
|
187
|
+
? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10)
|
|
188
|
+
: 8080;
|
|
189
|
+
// Try to find unused port and listen on it for 3 times,
|
|
190
|
+
// if port is not specified in options.
|
|
191
|
+
const defaultPortRetry = typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== 'undefined'
|
|
192
|
+
? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10)
|
|
193
|
+
: 3;
|
|
194
|
+
return pRetry(() => getPort(basePort, host), {
|
|
195
|
+
retries: defaultPortRetry,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
static findCacheDir() {
|
|
199
|
+
const cwd = process.cwd();
|
|
200
|
+
let dir = cwd;
|
|
201
|
+
for (;;) {
|
|
202
|
+
try {
|
|
203
|
+
if (fs.statSync(path.join(dir, 'package.json')).isFile())
|
|
204
|
+
break;
|
|
205
|
+
// eslint-disable-next-line no-empty
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
208
|
+
const parent = path.dirname(dir);
|
|
209
|
+
if (dir === parent) {
|
|
210
|
+
dir = undefined;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
dir = parent;
|
|
214
|
+
}
|
|
215
|
+
if (!dir) {
|
|
216
|
+
return path.resolve(cwd, '.cache/webpack-dev-server');
|
|
217
|
+
}
|
|
218
|
+
if (process.versions.pnp === '1') {
|
|
219
|
+
return path.resolve(dir, '.pnp/.cache/webpack-dev-server');
|
|
220
|
+
}
|
|
221
|
+
if (process.versions.pnp === '3') {
|
|
222
|
+
return path.resolve(dir, '.yarn/.cache/webpack-dev-server');
|
|
223
|
+
}
|
|
224
|
+
return path.resolve(dir, 'node_modules/.cache/webpack-dev-server');
|
|
225
|
+
}
|
|
226
|
+
static isWebTarget(compiler) {
|
|
227
|
+
if (compiler.platform?.web) {
|
|
228
|
+
return compiler.platform.web;
|
|
229
|
+
}
|
|
230
|
+
// TODO improve for the next major version and keep only `webTargets` to fallback for old versions
|
|
231
|
+
if (compiler.options.externalsPresets?.web) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (compiler.options.resolve?.conditionNames?.includes('browser')) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
const webTargets = [
|
|
238
|
+
'web',
|
|
239
|
+
'webworker',
|
|
240
|
+
'electron-preload',
|
|
241
|
+
'electron-renderer',
|
|
242
|
+
'nwjs',
|
|
243
|
+
'node-webkit',
|
|
244
|
+
undefined,
|
|
245
|
+
null,
|
|
246
|
+
];
|
|
247
|
+
if (Array.isArray(compiler.options.target)) {
|
|
248
|
+
return compiler.options.target.some((r) => webTargets.includes(r));
|
|
249
|
+
}
|
|
250
|
+
return webTargets.includes(compiler.options.target);
|
|
251
|
+
}
|
|
252
|
+
addAdditionalEntries(compiler) {
|
|
253
|
+
const additionalEntries = [];
|
|
254
|
+
const isWebTarget = Server.isWebTarget(compiler);
|
|
255
|
+
// TODO maybe empty client
|
|
256
|
+
if (this.options.client && isWebTarget) {
|
|
257
|
+
let webSocketURLStr = '';
|
|
258
|
+
if (this.options.webSocketServer) {
|
|
259
|
+
const webSocketURL = this.options.client
|
|
260
|
+
.webSocketURL;
|
|
261
|
+
const webSocketServer = this.options.webSocketServer;
|
|
262
|
+
const searchParams = new URLSearchParams();
|
|
263
|
+
let protocol;
|
|
264
|
+
// We are proxying dev server and need to specify custom `hostname`
|
|
265
|
+
if (typeof webSocketURL.protocol !== 'undefined') {
|
|
266
|
+
protocol = webSocketURL.protocol;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
protocol = this.isTlsServer ? 'wss:' : 'ws:';
|
|
270
|
+
}
|
|
271
|
+
searchParams.set('protocol', protocol);
|
|
272
|
+
if (typeof webSocketURL.username !== 'undefined') {
|
|
273
|
+
searchParams.set('username', webSocketURL.username);
|
|
274
|
+
}
|
|
275
|
+
if (typeof webSocketURL.password !== 'undefined') {
|
|
276
|
+
searchParams.set('password', webSocketURL.password);
|
|
277
|
+
}
|
|
278
|
+
let hostname;
|
|
279
|
+
// SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
|
|
280
|
+
const isSockJSType = webSocketServer.type === 'sockjs';
|
|
281
|
+
const isWebSocketServerHostDefined = typeof webSocketServer.options.host !== 'undefined';
|
|
282
|
+
const isWebSocketServerPortDefined = typeof webSocketServer.options.port !== 'undefined';
|
|
283
|
+
if (isSockJSType &&
|
|
284
|
+
(isWebSocketServerHostDefined || isWebSocketServerPortDefined)) {
|
|
285
|
+
this.logger.warn("SockJS only supports client mode and does not support custom hostname and port options. Please consider using 'ws' if you need to customize these options.");
|
|
286
|
+
}
|
|
287
|
+
// We are proxying dev server and need to specify custom `hostname`
|
|
288
|
+
if (typeof webSocketURL.hostname !== 'undefined') {
|
|
289
|
+
hostname = webSocketURL.hostname;
|
|
290
|
+
}
|
|
291
|
+
// Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
|
|
292
|
+
else if (isWebSocketServerHostDefined && !isSockJSType) {
|
|
293
|
+
hostname = webSocketServer.options.host;
|
|
294
|
+
}
|
|
295
|
+
// The `host` option is specified
|
|
296
|
+
else if (typeof this.options.host !== 'undefined') {
|
|
297
|
+
hostname = this.options.host;
|
|
298
|
+
}
|
|
299
|
+
// The `port` option is not specified
|
|
300
|
+
else {
|
|
301
|
+
hostname = '0.0.0.0';
|
|
302
|
+
}
|
|
303
|
+
searchParams.set('hostname', hostname);
|
|
304
|
+
let port;
|
|
305
|
+
// We are proxying dev server and need to specify custom `port`
|
|
306
|
+
if (typeof webSocketURL.port !== 'undefined') {
|
|
307
|
+
port = webSocketURL.port;
|
|
308
|
+
}
|
|
309
|
+
// Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
|
|
310
|
+
else if (isWebSocketServerPortDefined && !isSockJSType) {
|
|
311
|
+
port = webSocketServer.options.port;
|
|
312
|
+
}
|
|
313
|
+
// The `port` option is specified
|
|
314
|
+
else if (typeof this.options.port === 'number') {
|
|
315
|
+
port = this.options.port;
|
|
316
|
+
}
|
|
317
|
+
// The `port` option is specified using `string`
|
|
318
|
+
else if (typeof this.options.port === 'string' &&
|
|
319
|
+
this.options.port !== 'auto') {
|
|
320
|
+
port = Number(this.options.port);
|
|
321
|
+
}
|
|
322
|
+
// The `port` option is not specified or set to `auto`
|
|
323
|
+
else {
|
|
324
|
+
port = '0';
|
|
325
|
+
}
|
|
326
|
+
searchParams.set('port', String(port));
|
|
327
|
+
let pathname = '';
|
|
328
|
+
// We are proxying dev server and need to specify custom `pathname`
|
|
329
|
+
if (typeof webSocketURL.pathname !== 'undefined') {
|
|
330
|
+
pathname = webSocketURL.pathname;
|
|
331
|
+
}
|
|
332
|
+
// Web socket server works on custom `path`
|
|
333
|
+
else if (typeof webSocketServer.options.prefix !== 'undefined' ||
|
|
334
|
+
typeof webSocketServer.options.path !== 'undefined') {
|
|
335
|
+
pathname =
|
|
336
|
+
webSocketServer.options.prefix || webSocketServer.options.path;
|
|
337
|
+
}
|
|
338
|
+
searchParams.set('pathname', pathname);
|
|
339
|
+
const client = this.options.client;
|
|
340
|
+
if (typeof client.logging !== 'undefined') {
|
|
341
|
+
searchParams.set('logging', client.logging);
|
|
342
|
+
}
|
|
343
|
+
if (typeof client.progress !== 'undefined') {
|
|
344
|
+
searchParams.set('progress', String(client.progress));
|
|
345
|
+
}
|
|
346
|
+
if (typeof client.overlay !== 'undefined') {
|
|
347
|
+
const overlayString = typeof client.overlay === 'boolean'
|
|
348
|
+
? String(client.overlay)
|
|
349
|
+
: JSON.stringify({
|
|
350
|
+
...client.overlay,
|
|
351
|
+
errors: encodeOverlaySettings(client.overlay.errors),
|
|
352
|
+
warnings: encodeOverlaySettings(client.overlay.warnings),
|
|
353
|
+
runtimeErrors: encodeOverlaySettings(client.overlay.runtimeErrors),
|
|
354
|
+
});
|
|
355
|
+
searchParams.set('overlay', overlayString);
|
|
356
|
+
}
|
|
357
|
+
if (typeof client.reconnect !== 'undefined') {
|
|
358
|
+
searchParams.set('reconnect', typeof client.reconnect === 'number'
|
|
359
|
+
? String(client.reconnect)
|
|
360
|
+
: '10');
|
|
361
|
+
}
|
|
362
|
+
if (typeof this.options.hot !== 'undefined') {
|
|
363
|
+
searchParams.set('hot', String(this.options.hot));
|
|
364
|
+
}
|
|
365
|
+
if (typeof this.options.liveReload !== 'undefined') {
|
|
366
|
+
searchParams.set('live-reload', String(this.options.liveReload));
|
|
367
|
+
}
|
|
368
|
+
webSocketURLStr = searchParams.toString();
|
|
369
|
+
}
|
|
370
|
+
additionalEntries.push(`${this.getClientEntry()}?${webSocketURLStr}`);
|
|
371
|
+
}
|
|
372
|
+
const clientHotEntry = this.getClientHotEntry();
|
|
373
|
+
if (clientHotEntry) {
|
|
374
|
+
additionalEntries.push(clientHotEntry);
|
|
375
|
+
}
|
|
376
|
+
const webpack = compiler.webpack || require('webpack');
|
|
377
|
+
// use a hook to add entries if available
|
|
378
|
+
for (const additionalEntry of additionalEntries) {
|
|
379
|
+
new webpack.EntryPlugin(compiler.context, additionalEntry, {
|
|
380
|
+
name: undefined,
|
|
381
|
+
}).apply(compiler);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* @private
|
|
386
|
+
* @returns {Compiler["options"]} compiler options
|
|
387
|
+
*/
|
|
388
|
+
getCompilerOptions() {
|
|
389
|
+
if (typeof this.compiler.compilers !== 'undefined') {
|
|
390
|
+
if (this.compiler.compilers.length === 1) {
|
|
391
|
+
return this.compiler.compilers[0].options;
|
|
392
|
+
}
|
|
393
|
+
// Configuration with the `devServer` options
|
|
394
|
+
const compilerWithDevServer = this.compiler.compilers.find((config) => config.options.devServer);
|
|
395
|
+
if (compilerWithDevServer) {
|
|
396
|
+
return compilerWithDevServer.options;
|
|
397
|
+
}
|
|
398
|
+
// Configuration with `web` preset
|
|
399
|
+
const compilerWithWebPreset = this.compiler.compilers.find((config) => config.options.externalsPresets?.web ||
|
|
400
|
+
[
|
|
401
|
+
'web',
|
|
402
|
+
'webworker',
|
|
403
|
+
'electron-preload',
|
|
404
|
+
'electron-renderer',
|
|
405
|
+
'node-webkit',
|
|
406
|
+
undefined,
|
|
407
|
+
null,
|
|
408
|
+
].includes(config.options.target));
|
|
409
|
+
if (compilerWithWebPreset) {
|
|
410
|
+
return compilerWithWebPreset.options;
|
|
411
|
+
}
|
|
412
|
+
// Fallback
|
|
413
|
+
return this.compiler.compilers[0].options;
|
|
414
|
+
}
|
|
415
|
+
return this.compiler.options;
|
|
416
|
+
}
|
|
417
|
+
async normalizeOptions() {
|
|
418
|
+
const { options } = this;
|
|
419
|
+
const compilerOptions = this.getCompilerOptions();
|
|
420
|
+
const compilerWatchOptions = compilerOptions.watchOptions;
|
|
421
|
+
const getWatchOptions = (watchOptions = {}) => {
|
|
422
|
+
const getPolling = () => {
|
|
423
|
+
if (typeof watchOptions.usePolling !== 'undefined') {
|
|
424
|
+
return watchOptions.usePolling;
|
|
425
|
+
}
|
|
426
|
+
if (typeof watchOptions.poll !== 'undefined') {
|
|
427
|
+
return Boolean(watchOptions.poll);
|
|
428
|
+
}
|
|
429
|
+
if (typeof compilerWatchOptions.poll !== 'undefined') {
|
|
430
|
+
return Boolean(compilerWatchOptions.poll);
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
};
|
|
434
|
+
const getInterval = () => {
|
|
435
|
+
if (typeof watchOptions.interval !== 'undefined') {
|
|
436
|
+
return watchOptions.interval;
|
|
437
|
+
}
|
|
438
|
+
if (typeof watchOptions.poll === 'number') {
|
|
439
|
+
return watchOptions.poll;
|
|
440
|
+
}
|
|
441
|
+
if (typeof compilerWatchOptions.poll === 'number') {
|
|
442
|
+
return compilerWatchOptions.poll;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const usePolling = getPolling();
|
|
446
|
+
const interval = getInterval();
|
|
447
|
+
const { poll, ...rest } = watchOptions;
|
|
448
|
+
return {
|
|
449
|
+
ignoreInitial: true,
|
|
450
|
+
persistent: true,
|
|
451
|
+
followSymlinks: false,
|
|
452
|
+
atomic: false,
|
|
453
|
+
alwaysStat: true,
|
|
454
|
+
ignorePermissionErrors: true,
|
|
455
|
+
// Respect options from compiler watchOptions
|
|
456
|
+
usePolling,
|
|
457
|
+
interval,
|
|
458
|
+
ignored: watchOptions.ignored,
|
|
459
|
+
// TODO: we respect these options for all watch options and allow developers to pass them to chokidar, but chokidar doesn't have these options maybe we need revisit that in future
|
|
460
|
+
...rest,
|
|
461
|
+
};
|
|
462
|
+
};
|
|
463
|
+
const getStaticItem = (optionsForStatic) => {
|
|
464
|
+
const getDefaultStaticOptions = () => {
|
|
465
|
+
return {
|
|
466
|
+
directory: path.join(process.cwd(), 'public'),
|
|
467
|
+
staticOptions: {},
|
|
468
|
+
publicPath: ['/'],
|
|
469
|
+
serveIndex: { icons: true },
|
|
470
|
+
watch: getWatchOptions(),
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
let item;
|
|
474
|
+
if (typeof optionsForStatic === 'undefined') {
|
|
475
|
+
item = getDefaultStaticOptions();
|
|
476
|
+
}
|
|
477
|
+
else if (typeof optionsForStatic === 'string') {
|
|
478
|
+
item = {
|
|
479
|
+
...getDefaultStaticOptions(),
|
|
480
|
+
directory: optionsForStatic,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const def = getDefaultStaticOptions();
|
|
485
|
+
item = {
|
|
486
|
+
directory: typeof optionsForStatic.directory !== 'undefined'
|
|
487
|
+
? optionsForStatic.directory
|
|
488
|
+
: def.directory,
|
|
489
|
+
staticOptions: typeof optionsForStatic.staticOptions !== 'undefined'
|
|
490
|
+
? { ...def.staticOptions, ...optionsForStatic.staticOptions }
|
|
491
|
+
: def.staticOptions,
|
|
492
|
+
publicPath:
|
|
493
|
+
// eslint-disable-next-line no-nested-ternary
|
|
494
|
+
typeof optionsForStatic.publicPath !== 'undefined'
|
|
495
|
+
? Array.isArray(optionsForStatic.publicPath)
|
|
496
|
+
? optionsForStatic.publicPath
|
|
497
|
+
: [optionsForStatic.publicPath]
|
|
498
|
+
: def.publicPath,
|
|
499
|
+
serveIndex:
|
|
500
|
+
// Check if 'serveIndex' property is defined in 'optionsForStatic'
|
|
501
|
+
// If 'serveIndex' is a boolean and true, use default 'serveIndex'
|
|
502
|
+
// If 'serveIndex' is an object, merge its properties with default 'serveIndex'
|
|
503
|
+
// If 'serveIndex' is neither a boolean true nor an object, use it as-is
|
|
504
|
+
// If 'serveIndex' is not defined in 'optionsForStatic', use default 'serveIndex'
|
|
505
|
+
// eslint-disable-next-line no-nested-ternary
|
|
506
|
+
typeof optionsForStatic.serveIndex !== 'undefined'
|
|
507
|
+
? // eslint-disable-next-line no-nested-ternary
|
|
508
|
+
typeof optionsForStatic.serveIndex === 'boolean' &&
|
|
509
|
+
optionsForStatic.serveIndex
|
|
510
|
+
? def.serveIndex
|
|
511
|
+
: typeof optionsForStatic.serveIndex === 'object'
|
|
512
|
+
? { ...def.serveIndex, ...optionsForStatic.serveIndex }
|
|
513
|
+
: optionsForStatic.serveIndex
|
|
514
|
+
: def.serveIndex,
|
|
515
|
+
watch:
|
|
516
|
+
// eslint-disable-next-line no-nested-ternary
|
|
517
|
+
typeof optionsForStatic.watch !== 'undefined'
|
|
518
|
+
? // eslint-disable-next-line no-nested-ternary
|
|
519
|
+
typeof optionsForStatic.watch === 'boolean'
|
|
520
|
+
? optionsForStatic.watch
|
|
521
|
+
? def.watch
|
|
522
|
+
: false
|
|
523
|
+
: getWatchOptions(optionsForStatic.watch)
|
|
524
|
+
: def.watch,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (Server.isAbsoluteURL(item.directory)) {
|
|
528
|
+
throw new Error('Using a URL as static.directory is not supported');
|
|
529
|
+
}
|
|
530
|
+
return item;
|
|
531
|
+
};
|
|
532
|
+
if (typeof options.allowedHosts === 'undefined') {
|
|
533
|
+
// AllowedHosts allows some default hosts picked from `options.host` or `webSocketURL.hostname` and `localhost`
|
|
534
|
+
options.allowedHosts = 'auto';
|
|
535
|
+
}
|
|
536
|
+
// We store allowedHosts as array when supplied as string
|
|
537
|
+
else if (typeof options.allowedHosts === 'string' &&
|
|
538
|
+
options.allowedHosts !== 'auto' &&
|
|
539
|
+
options.allowedHosts !== 'all') {
|
|
540
|
+
options.allowedHosts = [options.allowedHosts];
|
|
541
|
+
}
|
|
542
|
+
// CLI pass options as array, we should normalize them
|
|
543
|
+
else if (Array.isArray(options.allowedHosts) &&
|
|
544
|
+
options.allowedHosts.includes('all')) {
|
|
545
|
+
options.allowedHosts = 'all';
|
|
546
|
+
}
|
|
547
|
+
if (typeof options.bonjour === 'undefined') {
|
|
548
|
+
options.bonjour = false;
|
|
549
|
+
}
|
|
550
|
+
else if (typeof options.bonjour === 'boolean') {
|
|
551
|
+
options.bonjour = options.bonjour ? {} : false;
|
|
552
|
+
}
|
|
553
|
+
if (typeof options.client === 'undefined' ||
|
|
554
|
+
(typeof options.client === 'object' && options.client !== null)) {
|
|
555
|
+
if (!options.client) {
|
|
556
|
+
options.client = {};
|
|
557
|
+
}
|
|
558
|
+
if (typeof options.client.webSocketURL === 'undefined') {
|
|
559
|
+
options.client.webSocketURL = {};
|
|
560
|
+
}
|
|
561
|
+
else if (typeof options.client.webSocketURL === 'string') {
|
|
562
|
+
const parsedURL = new URL(options.client.webSocketURL);
|
|
563
|
+
options.client.webSocketURL = {
|
|
564
|
+
protocol: parsedURL.protocol,
|
|
565
|
+
hostname: parsedURL.hostname,
|
|
566
|
+
port: parsedURL.port.length > 0 ? Number(parsedURL.port) : '',
|
|
567
|
+
pathname: parsedURL.pathname,
|
|
568
|
+
username: parsedURL.username,
|
|
569
|
+
password: parsedURL.password,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
else if (typeof options.client.webSocketURL.port === 'string') {
|
|
573
|
+
options.client.webSocketURL.port = Number(options.client.webSocketURL.port);
|
|
574
|
+
}
|
|
575
|
+
// Enable client overlay by default
|
|
576
|
+
if (typeof options.client.overlay === 'undefined') {
|
|
577
|
+
options.client.overlay = true;
|
|
578
|
+
}
|
|
579
|
+
else if (typeof options.client.overlay !== 'boolean') {
|
|
580
|
+
options.client.overlay = {
|
|
581
|
+
errors: true,
|
|
582
|
+
warnings: true,
|
|
583
|
+
...options.client.overlay,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (typeof options.client.reconnect === 'undefined') {
|
|
587
|
+
options.client.reconnect = 10;
|
|
588
|
+
}
|
|
589
|
+
else if (options.client.reconnect === true) {
|
|
590
|
+
options.client.reconnect = Number.POSITIVE_INFINITY;
|
|
591
|
+
}
|
|
592
|
+
else if (options.client.reconnect === false) {
|
|
593
|
+
options.client.reconnect = 0;
|
|
594
|
+
}
|
|
595
|
+
// Respect infrastructureLogging.level
|
|
596
|
+
if (typeof options.client.logging === 'undefined') {
|
|
597
|
+
options.client.logging = compilerOptions.infrastructureLogging
|
|
598
|
+
? compilerOptions.infrastructureLogging.level
|
|
599
|
+
: 'info';
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (typeof options.compress === 'undefined') {
|
|
603
|
+
options.compress = true;
|
|
604
|
+
}
|
|
605
|
+
if (typeof options.devMiddleware === 'undefined') {
|
|
606
|
+
options.devMiddleware = {};
|
|
607
|
+
}
|
|
608
|
+
// No need to normalize `headers`
|
|
609
|
+
if (typeof options.historyApiFallback === 'undefined') {
|
|
610
|
+
options.historyApiFallback = false;
|
|
611
|
+
}
|
|
612
|
+
else if (typeof options.historyApiFallback === 'boolean' &&
|
|
613
|
+
options.historyApiFallback) {
|
|
614
|
+
options.historyApiFallback = {};
|
|
615
|
+
}
|
|
616
|
+
// No need to normalize `host`
|
|
617
|
+
options.hot =
|
|
618
|
+
typeof options.hot === 'boolean' || options.hot === 'only'
|
|
619
|
+
? options.hot
|
|
620
|
+
: true;
|
|
621
|
+
if (typeof options.server === 'function' ||
|
|
622
|
+
typeof options.server === 'string') {
|
|
623
|
+
options.server = {
|
|
624
|
+
type: options.server,
|
|
625
|
+
options: {},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const serverOptions = options.server || {};
|
|
630
|
+
options.server = {
|
|
631
|
+
type: serverOptions.type || 'http',
|
|
632
|
+
options: { ...serverOptions.options },
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
const serverOptions = options.server.options;
|
|
636
|
+
if (options.server.type === 'spdy' &&
|
|
637
|
+
typeof serverOptions.spdy === 'undefined') {
|
|
638
|
+
serverOptions.spdy = { protocols: ['h2', 'http/1.1'] };
|
|
639
|
+
}
|
|
640
|
+
if (options.server.type === 'https' ||
|
|
641
|
+
options.server.type === 'http2' ||
|
|
642
|
+
options.server.type === 'spdy') {
|
|
643
|
+
if (typeof serverOptions.requestCert === 'undefined') {
|
|
644
|
+
serverOptions.requestCert = false;
|
|
645
|
+
}
|
|
646
|
+
const httpsProperties = [
|
|
647
|
+
'ca',
|
|
648
|
+
'cert',
|
|
649
|
+
'crl',
|
|
650
|
+
'key',
|
|
651
|
+
'pfx',
|
|
652
|
+
];
|
|
653
|
+
for (const property of httpsProperties) {
|
|
654
|
+
if (typeof serverOptions[property] === 'undefined') {
|
|
655
|
+
// eslint-disable-next-line no-continue
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const value = serverOptions[property];
|
|
659
|
+
const readFile = (item) => {
|
|
660
|
+
if (Buffer.isBuffer(item) ||
|
|
661
|
+
(typeof item === 'object' && item !== null && !Array.isArray(item))) {
|
|
662
|
+
return item;
|
|
663
|
+
}
|
|
664
|
+
if (item) {
|
|
665
|
+
let stats = null;
|
|
666
|
+
try {
|
|
667
|
+
stats = fs.lstatSync(fs.realpathSync(item)).isFile();
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
// Ignore error
|
|
671
|
+
}
|
|
672
|
+
// It is a file
|
|
673
|
+
return stats ? fs.readFileSync(item) : item;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
// @ts-expect-error too complex
|
|
677
|
+
serverOptions[property] = (Array.isArray(value)
|
|
678
|
+
? value.map((item) => readFile(item))
|
|
679
|
+
: readFile(value));
|
|
680
|
+
}
|
|
681
|
+
let fakeCert;
|
|
682
|
+
if (!serverOptions.key || !serverOptions.cert) {
|
|
683
|
+
const certificateDir = Server.findCacheDir();
|
|
684
|
+
const certificatePath = path.join(certificateDir, 'server.pem');
|
|
685
|
+
let certificateExists;
|
|
686
|
+
try {
|
|
687
|
+
const certificate = await fs.promises.stat(certificatePath);
|
|
688
|
+
certificateExists = certificate.isFile();
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
certificateExists = false;
|
|
692
|
+
}
|
|
693
|
+
if (certificateExists) {
|
|
694
|
+
const certificateTtl = 1000 * 60 * 60 * 24;
|
|
695
|
+
const certificateStat = await fs.promises.stat(certificatePath);
|
|
696
|
+
const now = Number(new Date());
|
|
697
|
+
// cert is more than 30 days old, kill it with fire
|
|
698
|
+
if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
|
|
699
|
+
this.logger.info('SSL certificate is more than 30 days old. Removing...');
|
|
700
|
+
await fs.promises.rm(certificatePath, { recursive: true });
|
|
701
|
+
certificateExists = false;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (!certificateExists) {
|
|
705
|
+
this.logger.info('Generating SSL certificate...');
|
|
706
|
+
const selfsigned = require('selfsigned');
|
|
707
|
+
const attributes = [{ name: 'commonName', value: 'localhost' }];
|
|
708
|
+
const pems = selfsigned.generate(attributes, {
|
|
709
|
+
algorithm: 'sha256',
|
|
710
|
+
days: 30,
|
|
711
|
+
keySize: 2048,
|
|
712
|
+
extensions: [
|
|
713
|
+
{
|
|
714
|
+
name: 'basicConstraints',
|
|
715
|
+
cA: true,
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
name: 'keyUsage',
|
|
719
|
+
keyCertSign: true,
|
|
720
|
+
digitalSignature: true,
|
|
721
|
+
nonRepudiation: true,
|
|
722
|
+
keyEncipherment: true,
|
|
723
|
+
dataEncipherment: true,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: 'extKeyUsage',
|
|
727
|
+
serverAuth: true,
|
|
728
|
+
clientAuth: true,
|
|
729
|
+
codeSigning: true,
|
|
730
|
+
timeStamping: true,
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: 'subjectAltName',
|
|
734
|
+
altNames: [
|
|
735
|
+
{
|
|
736
|
+
// type 2 is DNS
|
|
737
|
+
type: 2,
|
|
738
|
+
value: 'localhost',
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
type: 2,
|
|
742
|
+
value: 'localhost.localdomain',
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
type: 2,
|
|
746
|
+
value: 'lvh.me',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
type: 2,
|
|
750
|
+
value: '*.lvh.me',
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
type: 2,
|
|
754
|
+
value: '[::1]',
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
// type 7 is IP
|
|
758
|
+
type: 7,
|
|
759
|
+
ip: '127.0.0.1',
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
type: 7,
|
|
763
|
+
ip: 'fe80::1',
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
},
|
|
767
|
+
],
|
|
768
|
+
});
|
|
769
|
+
await fs.promises.mkdir(certificateDir, { recursive: true });
|
|
770
|
+
await fs.promises.writeFile(certificatePath, pems.private + pems.cert, {
|
|
771
|
+
encoding: 'utf8',
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
fakeCert = await fs.promises.readFile(certificatePath);
|
|
775
|
+
this.logger.info(`SSL certificate: ${certificatePath}`);
|
|
776
|
+
}
|
|
777
|
+
serverOptions.key = serverOptions.key || fakeCert;
|
|
778
|
+
serverOptions.cert = serverOptions.cert || fakeCert;
|
|
779
|
+
}
|
|
780
|
+
if (typeof options.ipc === 'boolean') {
|
|
781
|
+
const isWindows = process.platform === 'win32';
|
|
782
|
+
const pipePrefix = isWindows ? '\\\\.\\pipe\\' : os.tmpdir();
|
|
783
|
+
const pipeName = 'webpack-dev-server.sock';
|
|
784
|
+
options.ipc = path.join(pipePrefix, pipeName);
|
|
785
|
+
}
|
|
786
|
+
options.liveReload =
|
|
787
|
+
typeof options.liveReload !== 'undefined' ? options.liveReload : true;
|
|
788
|
+
// https://github.com/webpack/webpack-dev-server/issues/1990
|
|
789
|
+
const defaultOpenOptions = { wait: false };
|
|
790
|
+
const getOpenItemsFromObject = ({ target, ...rest }) => {
|
|
791
|
+
const normalizedOptions = {
|
|
792
|
+
...defaultOpenOptions,
|
|
793
|
+
...rest,
|
|
794
|
+
};
|
|
795
|
+
if (typeof normalizedOptions.app === 'string') {
|
|
796
|
+
normalizedOptions.app = {
|
|
797
|
+
name: normalizedOptions.app,
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const normalizedTarget = typeof target === 'undefined' ? '<url>' : target;
|
|
801
|
+
if (Array.isArray(normalizedTarget)) {
|
|
802
|
+
return normalizedTarget.map((singleTarget) => {
|
|
803
|
+
return { target: singleTarget, options: normalizedOptions };
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return [{ target: normalizedTarget, options: normalizedOptions }];
|
|
807
|
+
};
|
|
808
|
+
if (typeof options.open === 'undefined') {
|
|
809
|
+
options.open = [];
|
|
810
|
+
}
|
|
811
|
+
else if (typeof options.open === 'boolean') {
|
|
812
|
+
options.open = options.open
|
|
813
|
+
? [
|
|
814
|
+
{
|
|
815
|
+
target: '<url>',
|
|
816
|
+
options: defaultOpenOptions,
|
|
817
|
+
},
|
|
818
|
+
]
|
|
819
|
+
: [];
|
|
820
|
+
}
|
|
821
|
+
else if (typeof options.open === 'string') {
|
|
822
|
+
options.open = [
|
|
823
|
+
{ target: options.open, options: defaultOpenOptions },
|
|
824
|
+
];
|
|
825
|
+
}
|
|
826
|
+
else if (Array.isArray(options.open)) {
|
|
827
|
+
const result = [];
|
|
828
|
+
for (const item of options.open) {
|
|
829
|
+
if (typeof item === 'string') {
|
|
830
|
+
result.push({ target: item, options: defaultOpenOptions });
|
|
831
|
+
// eslint-disable-next-line no-continue
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
result.push(...getOpenItemsFromObject(item));
|
|
835
|
+
}
|
|
836
|
+
options.open = result;
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
options.open = [
|
|
840
|
+
...getOpenItemsFromObject(options.open),
|
|
841
|
+
];
|
|
842
|
+
}
|
|
843
|
+
if (typeof options.port === 'string' && options.port !== 'auto') {
|
|
844
|
+
options.port = Number(options.port);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Assume a proxy configuration specified as:
|
|
848
|
+
* proxy: {
|
|
849
|
+
* 'context': { options }
|
|
850
|
+
* }
|
|
851
|
+
* OR
|
|
852
|
+
* proxy: {
|
|
853
|
+
* 'context': 'target'
|
|
854
|
+
* }
|
|
855
|
+
*/
|
|
856
|
+
if (typeof options.proxy !== 'undefined') {
|
|
857
|
+
options.proxy = options.proxy.map((item) => {
|
|
858
|
+
if (typeof item === 'function') {
|
|
859
|
+
return item;
|
|
860
|
+
}
|
|
861
|
+
const getLogLevelForProxy = (level) => {
|
|
862
|
+
if (level === 'none') {
|
|
863
|
+
return 'silent';
|
|
864
|
+
}
|
|
865
|
+
if (level === 'log') {
|
|
866
|
+
return 'info';
|
|
867
|
+
}
|
|
868
|
+
if (level === 'verbose') {
|
|
869
|
+
return 'debug';
|
|
870
|
+
}
|
|
871
|
+
return level;
|
|
872
|
+
};
|
|
873
|
+
if (typeof item.logLevel === 'undefined') {
|
|
874
|
+
item.logLevel = getLogLevelForProxy(compilerOptions.infrastructureLogging
|
|
875
|
+
? compilerOptions.infrastructureLogging.level
|
|
876
|
+
: 'info');
|
|
877
|
+
}
|
|
878
|
+
if (typeof item.logProvider === 'undefined') {
|
|
879
|
+
item.logProvider = () => this.logger;
|
|
880
|
+
}
|
|
881
|
+
return item;
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
if (typeof options.setupExitSignals === 'undefined') {
|
|
885
|
+
options.setupExitSignals = true;
|
|
886
|
+
}
|
|
887
|
+
if (typeof options.static === 'undefined') {
|
|
888
|
+
options.static = [getStaticItem()];
|
|
889
|
+
}
|
|
890
|
+
else if (typeof options.static === 'boolean') {
|
|
891
|
+
options.static = options.static ? [getStaticItem()] : false;
|
|
892
|
+
}
|
|
893
|
+
else if (typeof options.static === 'string') {
|
|
894
|
+
options.static = [getStaticItem(options.static)];
|
|
895
|
+
}
|
|
896
|
+
else if (Array.isArray(options.static)) {
|
|
897
|
+
options.static = options.static.map((item) => getStaticItem(item));
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
options.static = [getStaticItem(options.static)];
|
|
901
|
+
}
|
|
902
|
+
if (typeof options.watchFiles === 'string') {
|
|
903
|
+
options.watchFiles = [
|
|
904
|
+
{ paths: options.watchFiles, options: getWatchOptions() },
|
|
905
|
+
];
|
|
906
|
+
}
|
|
907
|
+
else if (typeof options.watchFiles === 'object' &&
|
|
908
|
+
options.watchFiles !== null &&
|
|
909
|
+
!Array.isArray(options.watchFiles)) {
|
|
910
|
+
options.watchFiles = [
|
|
911
|
+
{
|
|
912
|
+
paths: options.watchFiles.paths,
|
|
913
|
+
options: getWatchOptions(options.watchFiles.options || {}),
|
|
914
|
+
},
|
|
915
|
+
];
|
|
916
|
+
}
|
|
917
|
+
else if (Array.isArray(options.watchFiles)) {
|
|
918
|
+
options.watchFiles = options.watchFiles.map((item) => {
|
|
919
|
+
if (typeof item === 'string') {
|
|
920
|
+
return { paths: item, options: getWatchOptions() };
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
paths: item.paths,
|
|
924
|
+
options: getWatchOptions(item.options || {}),
|
|
925
|
+
};
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
options.watchFiles = [];
|
|
930
|
+
}
|
|
931
|
+
const defaultWebSocketServerType = 'ws';
|
|
932
|
+
const defaultWebSocketServerOptions = { path: '/ws' };
|
|
933
|
+
if (typeof options.webSocketServer === 'undefined') {
|
|
934
|
+
options.webSocketServer = {
|
|
935
|
+
type: defaultWebSocketServerType,
|
|
936
|
+
options: defaultWebSocketServerOptions,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
else if (typeof options.webSocketServer === 'boolean' &&
|
|
940
|
+
!options.webSocketServer) {
|
|
941
|
+
options.webSocketServer = false;
|
|
942
|
+
}
|
|
943
|
+
else if (typeof options.webSocketServer === 'string' ||
|
|
944
|
+
typeof options.webSocketServer === 'function') {
|
|
945
|
+
options.webSocketServer = {
|
|
946
|
+
type: options.webSocketServer,
|
|
947
|
+
options: defaultWebSocketServerOptions,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
options.webSocketServer = {
|
|
952
|
+
type: options.webSocketServer.type ||
|
|
953
|
+
defaultWebSocketServerType,
|
|
954
|
+
options: {
|
|
955
|
+
...defaultWebSocketServerOptions,
|
|
956
|
+
...options.webSocketServer.options,
|
|
957
|
+
},
|
|
958
|
+
};
|
|
959
|
+
const webSocketServer = options.webSocketServer;
|
|
960
|
+
if (typeof webSocketServer.options.port === 'string') {
|
|
961
|
+
webSocketServer.options.port = Number(webSocketServer.options.port);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* @private
|
|
967
|
+
* @returns {string} client transport
|
|
968
|
+
*/
|
|
969
|
+
getClientTransport() {
|
|
970
|
+
let clientImplementation;
|
|
971
|
+
let clientImplementationFound = true;
|
|
972
|
+
const isKnownWebSocketServerImplementation = this.options.webSocketServer &&
|
|
973
|
+
typeof this.options.webSocketServer
|
|
974
|
+
.type === 'string' &&
|
|
975
|
+
// @ts-expect-error
|
|
976
|
+
(this.options.webSocketServer.type === 'ws' ||
|
|
977
|
+
this.options.webSocketServer.type ===
|
|
978
|
+
'sockjs');
|
|
979
|
+
let clientTransport;
|
|
980
|
+
if (this.options.client) {
|
|
981
|
+
if (typeof this.options.client
|
|
982
|
+
.webSocketTransport !== 'undefined') {
|
|
983
|
+
clientTransport = this.options.client
|
|
984
|
+
.webSocketTransport;
|
|
985
|
+
}
|
|
986
|
+
else if (isKnownWebSocketServerImplementation) {
|
|
987
|
+
clientTransport = this.options.webSocketServer.type;
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
clientTransport = 'ws';
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
clientTransport = 'ws';
|
|
995
|
+
}
|
|
996
|
+
switch (typeof clientTransport) {
|
|
997
|
+
case 'string':
|
|
998
|
+
// could be 'sockjs', 'ws', or a path that should be required
|
|
999
|
+
if (clientTransport === 'sockjs') {
|
|
1000
|
+
clientImplementation = require.resolve('../client/clients/SockJSClient');
|
|
1001
|
+
}
|
|
1002
|
+
else if (clientTransport === 'ws') {
|
|
1003
|
+
clientImplementation = require.resolve('../client/clients/WebSocketClient');
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
try {
|
|
1007
|
+
clientImplementation = require.resolve(clientTransport);
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
clientImplementationFound = false;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
break;
|
|
1014
|
+
default:
|
|
1015
|
+
clientImplementationFound = false;
|
|
1016
|
+
}
|
|
1017
|
+
if (!clientImplementationFound) {
|
|
1018
|
+
throw new Error(`${!isKnownWebSocketServerImplementation
|
|
1019
|
+
? 'When you use custom web socket implementation you must explicitly specify client.webSocketTransport. '
|
|
1020
|
+
: ''}client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class `);
|
|
1021
|
+
}
|
|
1022
|
+
return clientImplementation;
|
|
1023
|
+
}
|
|
1024
|
+
getServerTransport() {
|
|
1025
|
+
let implementation;
|
|
1026
|
+
let implementationFound = true;
|
|
1027
|
+
switch (typeof this.options.webSocketServer.type) {
|
|
1028
|
+
case 'string':
|
|
1029
|
+
// Could be 'sockjs', in the future 'ws', or a path that should be required
|
|
1030
|
+
if (this.options.webSocketServer
|
|
1031
|
+
.type === 'sockjs') {
|
|
1032
|
+
implementation = require('./servers/SockJSServer');
|
|
1033
|
+
}
|
|
1034
|
+
else if (this.options.webSocketServer
|
|
1035
|
+
.type === 'ws') {
|
|
1036
|
+
implementation = require('./servers/WebsocketServer');
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
try {
|
|
1040
|
+
implementation = require(this.options.webSocketServer
|
|
1041
|
+
.type);
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
implementationFound = false;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
case 'function':
|
|
1049
|
+
implementation = this.options.webSocketServer.type;
|
|
1050
|
+
break;
|
|
1051
|
+
default:
|
|
1052
|
+
implementationFound = false;
|
|
1053
|
+
}
|
|
1054
|
+
if (!implementationFound) {
|
|
1055
|
+
throw new Error("webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to " +
|
|
1056
|
+
'a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) ' +
|
|
1057
|
+
'via require.resolve(...), or the class itself which extends BaseServer');
|
|
1058
|
+
}
|
|
1059
|
+
return implementation;
|
|
1060
|
+
}
|
|
1061
|
+
getClientEntry() {
|
|
1062
|
+
return require.resolve('@rspack/dev-server/client/index');
|
|
1063
|
+
}
|
|
1064
|
+
getClientHotEntry() {
|
|
1065
|
+
if (this.options.hot === 'only') {
|
|
1066
|
+
return require.resolve('@rspack/core/hot/only-dev-server');
|
|
1067
|
+
}
|
|
1068
|
+
if (this.options.hot) {
|
|
1069
|
+
return require.resolve('@rspack/core/hot/dev-server');
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
setupProgressPlugin() {
|
|
1073
|
+
const { ProgressPlugin } = this.compiler.compilers
|
|
1074
|
+
? this.compiler.compilers[0].webpack
|
|
1075
|
+
: this.compiler.webpack;
|
|
1076
|
+
new ProgressPlugin((percent, msg, addInfo, pluginName) => {
|
|
1077
|
+
const percentValue = Math.floor(percent * 100);
|
|
1078
|
+
let msgValue = msg;
|
|
1079
|
+
if (percentValue === 100) {
|
|
1080
|
+
msgValue = 'Compilation completed';
|
|
1081
|
+
}
|
|
1082
|
+
if (addInfo) {
|
|
1083
|
+
msgValue = `${msgValue} (${addInfo})`;
|
|
1084
|
+
}
|
|
1085
|
+
if (this.webSocketServer) {
|
|
1086
|
+
this.sendMessage(this.webSocketServer.clients, 'progress-update', {
|
|
1087
|
+
percent: percentValue,
|
|
1088
|
+
msg: msgValue,
|
|
1089
|
+
pluginName,
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
if (this.server) {
|
|
1093
|
+
this.server.emit('progress-update', { percent, msg, pluginName });
|
|
1094
|
+
}
|
|
1095
|
+
}).apply(this.compiler);
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* @private
|
|
1099
|
+
* @returns {Promise<void>}
|
|
1100
|
+
*/
|
|
38
1101
|
async initialize() {
|
|
39
|
-
const compilers = this.compiler
|
|
1102
|
+
const compilers = isMultiCompiler(this.compiler)
|
|
40
1103
|
? this.compiler.compilers
|
|
41
1104
|
: [this.compiler];
|
|
42
1105
|
for (const compiler of compilers) {
|
|
43
1106
|
const mode = compiler.options.mode || process.env.NODE_ENV;
|
|
44
1107
|
if (this.options.hot) {
|
|
45
|
-
if (mode ===
|
|
46
|
-
this.logger.warn(
|
|
47
|
-
|
|
1108
|
+
if (mode === 'production') {
|
|
1109
|
+
this.logger.warn('Hot Module Replacement (HMR) is enabled for the production build. \n' +
|
|
1110
|
+
'Make sure to disable HMR for production by setting `devServer.hot` to `false` in the configuration.');
|
|
48
1111
|
}
|
|
49
1112
|
compiler.options.resolve.alias = {
|
|
50
|
-
|
|
1113
|
+
'ansi-html-community': require.resolve('@rspack/dev-server/client/utils/ansiHTML'),
|
|
51
1114
|
...compiler.options.resolve.alias,
|
|
52
1115
|
};
|
|
53
1116
|
}
|
|
54
1117
|
}
|
|
55
|
-
|
|
56
|
-
await
|
|
1118
|
+
this.setupHooks();
|
|
1119
|
+
await this.setupApp();
|
|
1120
|
+
await this.createServer();
|
|
1121
|
+
if (this.options.webSocketServer) {
|
|
1122
|
+
const compilers = this.compiler.compilers ||
|
|
1123
|
+
[this.compiler];
|
|
1124
|
+
for (const compiler of compilers) {
|
|
1125
|
+
if (compiler.options.devServer === false) {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
this.addAdditionalEntries(compiler);
|
|
1129
|
+
const webpack = compiler.webpack || require('webpack');
|
|
1130
|
+
new webpack.ProvidePlugin({
|
|
1131
|
+
__webpack_dev_server_client__: this.getClientTransport(),
|
|
1132
|
+
}).apply(compiler);
|
|
1133
|
+
if (this.options.hot) {
|
|
1134
|
+
const HMRPluginExists = compiler.options.plugins.find((plugin) => plugin &&
|
|
1135
|
+
plugin.constructor === webpack.HotModuleReplacementPlugin);
|
|
1136
|
+
if (HMRPluginExists) {
|
|
1137
|
+
this.logger.warn('"hot: true" automatically applies HMR plugin, you don\'t have to add it manually to your webpack configuration.');
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
// Apply the HMR plugin
|
|
1141
|
+
const plugin = new webpack.HotModuleReplacementPlugin();
|
|
1142
|
+
plugin.apply(compiler);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (this.options.client &&
|
|
1147
|
+
this.options.client.progress) {
|
|
1148
|
+
this.setupProgressPlugin();
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
this.setupWatchFiles();
|
|
1152
|
+
this.setupWatchStaticFiles();
|
|
1153
|
+
this.setupMiddlewares();
|
|
1154
|
+
if (this.options.setupExitSignals) {
|
|
1155
|
+
const signals = ['SIGINT', 'SIGTERM'];
|
|
1156
|
+
let needForceShutdown = false;
|
|
1157
|
+
for (const signal of signals) {
|
|
1158
|
+
// eslint-disable-next-line no-loop-func
|
|
1159
|
+
const listener = () => {
|
|
1160
|
+
if (needForceShutdown) {
|
|
1161
|
+
// eslint-disable-next-line n/no-process-exit
|
|
1162
|
+
process.exit();
|
|
1163
|
+
}
|
|
1164
|
+
this.logger.info('Gracefully shutting down. To force exit, press ^C again. Please wait...');
|
|
1165
|
+
needForceShutdown = true;
|
|
1166
|
+
this.stopCallback(() => {
|
|
1167
|
+
if (typeof this.compiler.close === 'function') {
|
|
1168
|
+
this.compiler.close(() => {
|
|
1169
|
+
// eslint-disable-next-line n/no-process-exit
|
|
1170
|
+
process.exit();
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
// eslint-disable-next-line n/no-process-exit
|
|
1175
|
+
process.exit();
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
this.listeners.push({ name: signal, listener });
|
|
1180
|
+
process.on(signal, listener);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
// Proxy WebSocket without the initial http request
|
|
1184
|
+
// https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
|
|
1185
|
+
const webSocketProxies = this.webSocketProxies;
|
|
1186
|
+
for (const webSocketProxy of webSocketProxies) {
|
|
1187
|
+
this.server.on('upgrade', webSocketProxy.upgrade);
|
|
1188
|
+
}
|
|
57
1189
|
}
|
|
58
|
-
|
|
59
|
-
|
|
1190
|
+
async setupApp() {
|
|
1191
|
+
this.app = (typeof this.options.app === 'function'
|
|
1192
|
+
? await this.options.app()
|
|
1193
|
+
: getExpress()());
|
|
60
1194
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
1195
|
+
getStats(statsObj) {
|
|
1196
|
+
const stats = Server.DEFAULT_STATS;
|
|
1197
|
+
const compilerOptions = this.getCompilerOptions();
|
|
1198
|
+
if (compilerOptions.stats &&
|
|
1199
|
+
compilerOptions.stats
|
|
1200
|
+
.warningsFilter) {
|
|
1201
|
+
stats.warningsFilter = compilerOptions.stats.warningsFilter;
|
|
64
1202
|
}
|
|
65
|
-
|
|
66
|
-
|
|
1203
|
+
return statsObj.toJson(stats);
|
|
1204
|
+
}
|
|
1205
|
+
setupHooks() {
|
|
1206
|
+
this.compiler.hooks.invalid.tap('webpack-dev-server', () => {
|
|
1207
|
+
if (this.webSocketServer) {
|
|
1208
|
+
this.sendMessage(this.webSocketServer.clients, 'invalid');
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
this.compiler.hooks.done.tap('webpack-dev-server', (stats) => {
|
|
1212
|
+
if (this.webSocketServer) {
|
|
1213
|
+
this.sendStats(this.webSocketServer.clients, this.getStats(stats));
|
|
1214
|
+
}
|
|
1215
|
+
this.stats = stats;
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
setupWatchStaticFiles() {
|
|
1219
|
+
const watchFiles = this.options.static;
|
|
1220
|
+
if (watchFiles.length > 0) {
|
|
1221
|
+
for (const item of watchFiles) {
|
|
1222
|
+
if (item.watch) {
|
|
1223
|
+
this.watchFiles(item.directory, item.watch);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
setupWatchFiles() {
|
|
1229
|
+
const watchFiles = this.options.watchFiles;
|
|
1230
|
+
if (watchFiles.length > 0) {
|
|
1231
|
+
for (const item of watchFiles) {
|
|
1232
|
+
this.watchFiles(item.paths, item.options);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
setupMiddlewares() {
|
|
1237
|
+
let middlewares = [];
|
|
1238
|
+
// Register setup host header check for security
|
|
1239
|
+
middlewares.push({
|
|
1240
|
+
name: 'host-header-check',
|
|
1241
|
+
middleware: (req, res, next) => {
|
|
1242
|
+
const headers = req.headers;
|
|
1243
|
+
const headerName = headers[':authority'] ? ':authority' : 'host';
|
|
1244
|
+
if (this.isValidHost(headers, headerName)) {
|
|
1245
|
+
next();
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
res.statusCode = 403;
|
|
1249
|
+
res.end('Invalid Host header');
|
|
1250
|
+
},
|
|
1251
|
+
});
|
|
1252
|
+
// Register setup cross origin request check for security
|
|
1253
|
+
middlewares.push({
|
|
1254
|
+
name: 'cross-origin-header-check',
|
|
1255
|
+
middleware: (req, res, next) => {
|
|
1256
|
+
const headers = req.headers;
|
|
1257
|
+
const headerName = headers[':authority'] ? ':authority' : 'host';
|
|
1258
|
+
if (this.isValidHost(headers, headerName, false)) {
|
|
1259
|
+
next();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (headers['sec-fetch-mode'] === 'no-cors' &&
|
|
1263
|
+
headers['sec-fetch-site'] === 'cross-site') {
|
|
1264
|
+
res.statusCode = 403;
|
|
1265
|
+
res.end('Cross-Origin request blocked');
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
next();
|
|
1269
|
+
},
|
|
1270
|
+
});
|
|
1271
|
+
const isHTTP2 = this.options.server.type === 'http2';
|
|
1272
|
+
if (isHTTP2) {
|
|
1273
|
+
// TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
|
|
1274
|
+
middlewares.push({
|
|
1275
|
+
name: 'http2-status-message-patch',
|
|
1276
|
+
middleware: (_req, res, next) => {
|
|
1277
|
+
Object.defineProperty(res, 'statusMessage', {
|
|
1278
|
+
get() {
|
|
1279
|
+
return '';
|
|
1280
|
+
},
|
|
1281
|
+
set() { },
|
|
1282
|
+
});
|
|
1283
|
+
next();
|
|
1284
|
+
},
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
// compress is placed last and uses unshift so that it will be the first middleware used
|
|
1288
|
+
if (this.options.compress && !isHTTP2) {
|
|
1289
|
+
const compression = require('compression');
|
|
1290
|
+
middlewares.push({ name: 'compression', middleware: compression() });
|
|
1291
|
+
}
|
|
1292
|
+
if (typeof this.options.headers !== 'undefined') {
|
|
1293
|
+
middlewares.push({
|
|
1294
|
+
name: 'set-headers',
|
|
1295
|
+
middleware: this.setHeaders.bind(this),
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
middlewares.push({
|
|
1299
|
+
name: 'webpack-dev-middleware',
|
|
1300
|
+
middleware: this.middleware,
|
|
1301
|
+
});
|
|
1302
|
+
// Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
|
|
1303
|
+
middlewares.push({
|
|
1304
|
+
name: 'webpack-dev-server-sockjs-bundle',
|
|
1305
|
+
path: '/__webpack_dev_server__/sockjs.bundle.js',
|
|
1306
|
+
middleware: (req, res, next) => {
|
|
1307
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1308
|
+
next();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const clientPath = path.join(__dirname, '../', 'client/modules/sockjs-client/index.js');
|
|
1312
|
+
// Express send Etag and other headers by default, so let's keep them for compatibility reasons
|
|
1313
|
+
if (typeof res.sendFile === 'function') {
|
|
1314
|
+
res.sendFile(clientPath);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
let stats;
|
|
1318
|
+
try {
|
|
1319
|
+
// TODO implement `inputFileSystem.createReadStream` in webpack
|
|
1320
|
+
stats = fs.statSync(clientPath);
|
|
1321
|
+
}
|
|
1322
|
+
catch {
|
|
1323
|
+
next();
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
|
|
1327
|
+
res.setHeader('Content-Length', stats.size);
|
|
1328
|
+
if (req.method === 'HEAD') {
|
|
1329
|
+
res.end();
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
fs.createReadStream(clientPath).pipe(res);
|
|
1333
|
+
},
|
|
1334
|
+
});
|
|
1335
|
+
middlewares.push({
|
|
1336
|
+
name: 'webpack-dev-server-invalidate',
|
|
1337
|
+
path: '/webpack-dev-server/invalidate',
|
|
1338
|
+
middleware: (req, res, next) => {
|
|
1339
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1340
|
+
next();
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
this.invalidate();
|
|
1344
|
+
res.end();
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
middlewares.push({
|
|
1348
|
+
name: 'webpack-dev-server-open-editor',
|
|
1349
|
+
path: '/webpack-dev-server/open-editor',
|
|
1350
|
+
middleware: (req, res, next) => {
|
|
1351
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1352
|
+
next();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (!req.url) {
|
|
1356
|
+
next();
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
1360
|
+
const params = new URLSearchParams(resolveUrl.search);
|
|
1361
|
+
const fileName = params.get('fileName');
|
|
1362
|
+
if (typeof fileName === 'string') {
|
|
1363
|
+
const launchEditor = require('launch-editor');
|
|
1364
|
+
launchEditor(fileName);
|
|
1365
|
+
}
|
|
1366
|
+
res.end();
|
|
1367
|
+
},
|
|
1368
|
+
});
|
|
1369
|
+
middlewares.push({
|
|
1370
|
+
name: 'webpack-dev-server-assets',
|
|
1371
|
+
path: '/webpack-dev-server',
|
|
1372
|
+
middleware: (req, res, next) => {
|
|
1373
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1374
|
+
next();
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (!this.middleware) {
|
|
1378
|
+
next();
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
this.middleware.waitUntilValid((stats) => {
|
|
1382
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1383
|
+
// HEAD requests should not return body content
|
|
1384
|
+
if (req.method === 'HEAD') {
|
|
1385
|
+
res.end();
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
|
|
1389
|
+
const statsForPrint = typeof stats.stats !== 'undefined'
|
|
1390
|
+
? stats.toJson({})
|
|
1391
|
+
.children
|
|
1392
|
+
: [stats.toJson()];
|
|
1393
|
+
res.write('<h1>Assets Report:</h1>');
|
|
1394
|
+
for (const [index, item] of statsForPrint?.entries() ?? []) {
|
|
1395
|
+
res.write('<div>');
|
|
1396
|
+
const name = typeof item.name !== 'undefined'
|
|
1397
|
+
? item.name
|
|
1398
|
+
: stats.stats
|
|
1399
|
+
? `unnamed[${index}]`
|
|
1400
|
+
: 'unnamed';
|
|
1401
|
+
res.write(`<h2>Compilation: ${name}</h2>`);
|
|
1402
|
+
res.write('<ul>');
|
|
1403
|
+
const publicPath = item.publicPath === 'auto' ? '' : item.publicPath;
|
|
1404
|
+
const assets = item.assets;
|
|
1405
|
+
for (const asset of assets ?? []) {
|
|
1406
|
+
const assetName = asset.name;
|
|
1407
|
+
const assetURL = `${publicPath}${assetName}`;
|
|
1408
|
+
res.write(`<li>
|
|
1409
|
+
<strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
|
|
1410
|
+
</li>`);
|
|
1411
|
+
}
|
|
1412
|
+
res.write('</ul>');
|
|
1413
|
+
res.write('</div>');
|
|
1414
|
+
}
|
|
1415
|
+
res.end('</body></html>');
|
|
1416
|
+
});
|
|
1417
|
+
},
|
|
1418
|
+
});
|
|
1419
|
+
if (this.options.proxy) {
|
|
1420
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
1421
|
+
const getProxyMiddleware = (proxyConfig) => {
|
|
1422
|
+
// It is possible to use the `bypass` method without a `target` or `router`.
|
|
1423
|
+
// However, the proxy middleware has no use in this case, and will fail to instantiate.
|
|
1424
|
+
if (proxyConfig.target) {
|
|
1425
|
+
const context = proxyConfig.context || proxyConfig.path;
|
|
1426
|
+
return createProxyMiddleware(context, proxyConfig);
|
|
1427
|
+
}
|
|
1428
|
+
if (proxyConfig.router) {
|
|
1429
|
+
return createProxyMiddleware(proxyConfig);
|
|
1430
|
+
}
|
|
1431
|
+
// TODO improve me after drop `bypass` to always generate error when configuration is bad
|
|
1432
|
+
if (!proxyConfig.bypass) {
|
|
1433
|
+
util.deprecate(() => { }, `Invalid proxy configuration:\n\n${JSON.stringify(proxyConfig, null, 2)}\n\nThe use of proxy object notation as proxy routes has been removed.\nPlease use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options`, 'DEP_WEBPACK_DEV_SERVER_PROXY_ROUTES_ARGUMENT')();
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
/**
|
|
1437
|
+
* @example
|
|
1438
|
+
* Assume a proxy configuration specified as:
|
|
1439
|
+
* proxy: [
|
|
1440
|
+
* {
|
|
1441
|
+
* context: "value",
|
|
1442
|
+
* ...options,
|
|
1443
|
+
* },
|
|
1444
|
+
* // or:
|
|
1445
|
+
* function() {
|
|
1446
|
+
* return {
|
|
1447
|
+
* context: "context",
|
|
1448
|
+
* ...options,
|
|
1449
|
+
* };
|
|
1450
|
+
* }
|
|
1451
|
+
* ]
|
|
1452
|
+
*/
|
|
1453
|
+
for (const proxyConfigOrCallback of this.options.proxy) {
|
|
1454
|
+
let proxyMiddleware;
|
|
1455
|
+
let proxyConfig = typeof proxyConfigOrCallback === 'function'
|
|
1456
|
+
? proxyConfigOrCallback()
|
|
1457
|
+
: proxyConfigOrCallback;
|
|
1458
|
+
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
1459
|
+
if (proxyConfig.ws && proxyMiddleware) {
|
|
1460
|
+
this.webSocketProxies.push(proxyMiddleware);
|
|
1461
|
+
}
|
|
1462
|
+
const handler = async (req, res, next) => {
|
|
1463
|
+
if (typeof proxyConfigOrCallback === 'function') {
|
|
1464
|
+
const newProxyConfig = proxyConfigOrCallback(req, res, next);
|
|
1465
|
+
if (newProxyConfig !== proxyConfig) {
|
|
1466
|
+
proxyConfig = newProxyConfig;
|
|
1467
|
+
const socket = req.socket || req.connection;
|
|
1468
|
+
const server = socket ? socket.server : null;
|
|
1469
|
+
if (server) {
|
|
1470
|
+
server.removeAllListeners('close');
|
|
1471
|
+
}
|
|
1472
|
+
proxyMiddleware = getProxyMiddleware(proxyConfig);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
// - Check if we have a bypass function defined
|
|
1476
|
+
// - In case the bypass function is defined we'll retrieve the
|
|
1477
|
+
// bypassUrl from it otherwise bypassUrl would be null
|
|
1478
|
+
// TODO remove in the next major in favor `context` and `router` options
|
|
1479
|
+
const isByPassFuncDefined = typeof proxyConfig.bypass === 'function';
|
|
1480
|
+
if (isByPassFuncDefined) {
|
|
1481
|
+
util.deprecate(() => { }, "Using the 'bypass' option is deprecated. Please use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options", 'DEP_WEBPACK_DEV_SERVER_PROXY_BYPASS_ARGUMENT')();
|
|
1482
|
+
}
|
|
1483
|
+
const bypassUrl = isByPassFuncDefined
|
|
1484
|
+
? await proxyConfig.bypass(req, res, proxyConfig)
|
|
1485
|
+
: null;
|
|
1486
|
+
if (typeof bypassUrl === 'boolean') {
|
|
1487
|
+
// skip the proxy
|
|
1488
|
+
res.statusCode = 404;
|
|
1489
|
+
req.url = '';
|
|
1490
|
+
next();
|
|
1491
|
+
}
|
|
1492
|
+
else if (typeof bypassUrl === 'string') {
|
|
1493
|
+
// byPass to that url
|
|
1494
|
+
req.url = bypassUrl;
|
|
1495
|
+
next();
|
|
1496
|
+
}
|
|
1497
|
+
else if (proxyMiddleware) {
|
|
1498
|
+
return proxyMiddleware(req, res, next);
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
next();
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
middlewares.push({
|
|
1505
|
+
name: 'http-proxy-middleware',
|
|
1506
|
+
middleware: handler,
|
|
1507
|
+
});
|
|
1508
|
+
// Also forward error requests to the proxy so it can handle them.
|
|
1509
|
+
middlewares.push({
|
|
1510
|
+
name: 'http-proxy-middleware-error-handler',
|
|
1511
|
+
middleware: (error, req, res, next) => handler(req, res, next),
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
middlewares.push({
|
|
1515
|
+
name: 'webpack-dev-middleware',
|
|
1516
|
+
middleware: this.middleware,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
const staticOptions = this.options.static;
|
|
1520
|
+
if (staticOptions.length > 0) {
|
|
1521
|
+
for (const staticOption of staticOptions) {
|
|
1522
|
+
for (const publicPath of staticOption.publicPath) {
|
|
1523
|
+
middlewares.push({
|
|
1524
|
+
name: 'express-static',
|
|
1525
|
+
path: publicPath,
|
|
1526
|
+
middleware: getExpress().static(staticOption.directory, staticOption.staticOptions),
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (this.options.historyApiFallback) {
|
|
1532
|
+
const connectHistoryApiFallback = require('connect-history-api-fallback');
|
|
1533
|
+
const { historyApiFallback } = this.options;
|
|
1534
|
+
if (typeof historyApiFallback ===
|
|
1535
|
+
'undefined' &&
|
|
1536
|
+
!historyApiFallback.verbose) {
|
|
1537
|
+
historyApiFallback.logger = this.logger.log.bind(this.logger, '[connect-history-api-fallback]');
|
|
1538
|
+
}
|
|
1539
|
+
// Fall back to /index.html if nothing else matches.
|
|
1540
|
+
middlewares.push({
|
|
1541
|
+
name: 'connect-history-api-fallback',
|
|
1542
|
+
middleware: connectHistoryApiFallback(historyApiFallback),
|
|
1543
|
+
});
|
|
1544
|
+
// include our middleware to ensure
|
|
1545
|
+
// it is able to handle '/index.html' request after redirect
|
|
1546
|
+
middlewares.push({
|
|
1547
|
+
name: 'webpack-dev-middleware',
|
|
1548
|
+
middleware: this.middleware,
|
|
1549
|
+
});
|
|
1550
|
+
if (staticOptions.length > 0) {
|
|
1551
|
+
for (const staticOption of staticOptions) {
|
|
1552
|
+
for (const publicPath of staticOption.publicPath) {
|
|
1553
|
+
middlewares.push({
|
|
1554
|
+
name: 'express-static',
|
|
1555
|
+
path: publicPath,
|
|
1556
|
+
middleware: getExpress().static(staticOption.directory, staticOption.staticOptions),
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (staticOptions.length > 0) {
|
|
1563
|
+
const serveIndex = require('serve-index');
|
|
1564
|
+
for (const staticOption of staticOptions) {
|
|
1565
|
+
for (const publicPath of staticOption.publicPath) {
|
|
1566
|
+
if (staticOption.serveIndex) {
|
|
1567
|
+
middlewares.push({
|
|
1568
|
+
name: 'serve-index',
|
|
1569
|
+
path: publicPath,
|
|
1570
|
+
middleware: (req, res, next) => {
|
|
1571
|
+
// serve-index doesn't fallthrough non-get/head request to next middleware
|
|
1572
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1573
|
+
return next();
|
|
1574
|
+
}
|
|
1575
|
+
serveIndex(staticOption.directory, staticOption.serveIndex)(req, res, next);
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
// Register this middleware always as the last one so that it's only used as a
|
|
1583
|
+
// fallback when no other middleware responses.
|
|
1584
|
+
middlewares.push({
|
|
1585
|
+
name: 'options-middleware',
|
|
1586
|
+
middleware: (req, res, next) => {
|
|
1587
|
+
if (req.method === 'OPTIONS') {
|
|
1588
|
+
res.statusCode = 204;
|
|
1589
|
+
res.setHeader('Content-Length', '0');
|
|
1590
|
+
res.end();
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
next();
|
|
1594
|
+
},
|
|
1595
|
+
});
|
|
1596
|
+
if (typeof this.options.setupMiddlewares === 'function') {
|
|
1597
|
+
middlewares = this.options.setupMiddlewares(middlewares, this);
|
|
1598
|
+
}
|
|
1599
|
+
// Lazy init webpack dev middleware
|
|
1600
|
+
const lazyInitDevMiddleware = () => {
|
|
1601
|
+
if (!this.middleware) {
|
|
1602
|
+
const webpackDevMiddleware = require('webpack-dev-middleware');
|
|
1603
|
+
// middleware for serving webpack bundle
|
|
1604
|
+
this.middleware = webpackDevMiddleware(this.compiler, this.options.devMiddleware);
|
|
1605
|
+
}
|
|
1606
|
+
return this.middleware;
|
|
1607
|
+
};
|
|
1608
|
+
for (const i of middlewares) {
|
|
1609
|
+
if (i.name === 'webpack-dev-middleware') {
|
|
1610
|
+
const item = i;
|
|
1611
|
+
if (typeof item.middleware === 'undefined') {
|
|
1612
|
+
item.middleware =
|
|
1613
|
+
lazyInitDevMiddleware();
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
for (const middleware of middlewares) {
|
|
1618
|
+
if (typeof middleware === 'function') {
|
|
1619
|
+
this.app.use(middleware);
|
|
1620
|
+
}
|
|
1621
|
+
else if (typeof middleware.path !== 'undefined') {
|
|
1622
|
+
this.app.use(middleware.path, middleware.middleware);
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
this.app.use(middleware.middleware);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* @private
|
|
1631
|
+
* @returns {Promise<void>}
|
|
1632
|
+
*/
|
|
1633
|
+
async createServer() {
|
|
1634
|
+
const { type, options } = this.options.server;
|
|
1635
|
+
if (typeof type === 'function') {
|
|
1636
|
+
this.server = await type(options, this.app);
|
|
1637
|
+
}
|
|
1638
|
+
else {
|
|
1639
|
+
const serverType = require(type);
|
|
1640
|
+
this.server =
|
|
1641
|
+
type === 'http2'
|
|
1642
|
+
? serverType.createSecureServer({ ...options, allowHTTP1: true }, this.app)
|
|
1643
|
+
: serverType.createServer(options, this.app);
|
|
1644
|
+
}
|
|
1645
|
+
this.isTlsServer =
|
|
1646
|
+
typeof this.server
|
|
1647
|
+
.setSecureContext !== 'undefined';
|
|
1648
|
+
this.server.on('connection', (socket) => {
|
|
1649
|
+
// Add socket to list
|
|
1650
|
+
this.sockets.push(socket);
|
|
1651
|
+
socket.once('close', () => {
|
|
1652
|
+
// Remove socket from list
|
|
1653
|
+
this.sockets.splice(this.sockets.indexOf(socket), 1);
|
|
1654
|
+
});
|
|
1655
|
+
});
|
|
1656
|
+
this.server.on('error', (error) => {
|
|
1657
|
+
throw error;
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
createWebSocketServer() {
|
|
1661
|
+
// @ts-expect-error constructor
|
|
1662
|
+
this.webSocketServer = new (this.getServerTransport())(this);
|
|
1663
|
+
(this.webSocketServer?.implementation).on('connection', (client, request) => {
|
|
1664
|
+
const headers = typeof request !== 'undefined'
|
|
1665
|
+
? request.headers
|
|
1666
|
+
: typeof client
|
|
1667
|
+
.headers !== 'undefined'
|
|
1668
|
+
? client.headers
|
|
1669
|
+
: undefined;
|
|
1670
|
+
if (!headers) {
|
|
1671
|
+
this.logger.warn('webSocketServer implementation must pass headers for the "connection" event');
|
|
1672
|
+
}
|
|
1673
|
+
if (!headers ||
|
|
1674
|
+
!this.isValidHost(headers, 'host') ||
|
|
1675
|
+
!this.isValidHost(headers, 'origin') ||
|
|
1676
|
+
!this.isSameOrigin(headers)) {
|
|
1677
|
+
this.sendMessage([client], 'error', 'Invalid Host/Origin header');
|
|
1678
|
+
// With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
|
|
1679
|
+
// Terminate would prevent it sending, so use close to allow it to be sent
|
|
1680
|
+
client.close();
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (this.options.hot === true || this.options.hot === 'only') {
|
|
1684
|
+
this.sendMessage([client], 'hot');
|
|
1685
|
+
}
|
|
1686
|
+
if (this.options.liveReload) {
|
|
1687
|
+
this.sendMessage([client], 'liveReload');
|
|
1688
|
+
}
|
|
1689
|
+
if (this.options.client &&
|
|
1690
|
+
this.options.client.progress) {
|
|
1691
|
+
this.sendMessage([client], 'progress', this.options.client.progress);
|
|
1692
|
+
}
|
|
1693
|
+
if (this.options.client &&
|
|
1694
|
+
this.options.client.reconnect) {
|
|
1695
|
+
this.sendMessage([client], 'reconnect', this.options.client.reconnect);
|
|
1696
|
+
}
|
|
1697
|
+
if (this.options.client &&
|
|
1698
|
+
this.options.client.overlay) {
|
|
1699
|
+
const overlayConfig = this.options.client
|
|
1700
|
+
.overlay;
|
|
1701
|
+
this.sendMessage([client], 'overlay', typeof overlayConfig === 'object'
|
|
1702
|
+
? {
|
|
1703
|
+
...overlayConfig,
|
|
1704
|
+
errors: overlayConfig.errors &&
|
|
1705
|
+
encodeOverlaySettings(overlayConfig.errors),
|
|
1706
|
+
warnings: overlayConfig.warnings &&
|
|
1707
|
+
encodeOverlaySettings(overlayConfig.warnings),
|
|
1708
|
+
runtimeErrors: overlayConfig.runtimeErrors &&
|
|
1709
|
+
encodeOverlaySettings(overlayConfig.runtimeErrors),
|
|
1710
|
+
}
|
|
1711
|
+
: overlayConfig);
|
|
1712
|
+
}
|
|
1713
|
+
if (!this.stats) {
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
this.sendStats([client], this.getStats(this.stats), true);
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
async openBrowser(defaultOpenTarget) {
|
|
1720
|
+
const open = (await import('open')).default;
|
|
1721
|
+
Promise.all(this.options.open.map((item) => {
|
|
1722
|
+
let openTarget;
|
|
1723
|
+
if (item.target === '<url>') {
|
|
1724
|
+
openTarget = defaultOpenTarget;
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
openTarget = Server.isAbsoluteURL(item.target)
|
|
1728
|
+
? item.target
|
|
1729
|
+
: new URL(item.target, defaultOpenTarget).toString();
|
|
1730
|
+
}
|
|
1731
|
+
// Type assertion needed: OpenOptions is compatible at runtime but TypeScript can't verify
|
|
1732
|
+
// the type match between our type definition and the ES module's type in CommonJS context
|
|
1733
|
+
return open(openTarget, item.options).catch(() => {
|
|
1734
|
+
const app = item.options.app;
|
|
1735
|
+
this.logger.warn(`Unable to open "${openTarget}" page${app
|
|
1736
|
+
? ` in "${app.name}" app${app.arguments
|
|
1737
|
+
? ` with "${app.arguments.join(' ')}" arguments`
|
|
1738
|
+
: ''}`
|
|
1739
|
+
: ''}. 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-name".`);
|
|
1740
|
+
});
|
|
1741
|
+
}));
|
|
1742
|
+
}
|
|
1743
|
+
runBonjour() {
|
|
1744
|
+
const { Bonjour } = require('bonjour-service');
|
|
1745
|
+
const type = this.isTlsServer ? 'https' : 'http';
|
|
1746
|
+
this.bonjour = new Bonjour();
|
|
1747
|
+
this.bonjour?.publish({
|
|
1748
|
+
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
|
|
1749
|
+
port: this.options.port,
|
|
1750
|
+
type,
|
|
1751
|
+
subtypes: ['webpack'],
|
|
1752
|
+
...this.options.bonjour,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
stopBonjour(callback = () => { }) {
|
|
1756
|
+
this.bonjour?.unpublishAll(() => {
|
|
1757
|
+
this.bonjour?.destroy();
|
|
1758
|
+
if (callback) {
|
|
1759
|
+
callback();
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
async logStatus() {
|
|
1764
|
+
const { cyan, isColorSupported, red } = require('colorette');
|
|
1765
|
+
const getColorsOption = (compilerOptions) => {
|
|
1766
|
+
let colorsEnabled;
|
|
1767
|
+
if (compilerOptions.stats &&
|
|
1768
|
+
typeof compilerOptions.stats.colors !==
|
|
1769
|
+
'undefined') {
|
|
1770
|
+
colorsEnabled = compilerOptions.stats
|
|
1771
|
+
.colors;
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
colorsEnabled = isColorSupported;
|
|
1775
|
+
}
|
|
1776
|
+
return colorsEnabled;
|
|
1777
|
+
};
|
|
1778
|
+
const colors = {
|
|
1779
|
+
info(useColor, msg) {
|
|
1780
|
+
if (useColor) {
|
|
1781
|
+
return cyan(msg);
|
|
1782
|
+
}
|
|
1783
|
+
return msg;
|
|
1784
|
+
},
|
|
1785
|
+
error(useColor, msg) {
|
|
1786
|
+
if (useColor) {
|
|
1787
|
+
return red(msg);
|
|
1788
|
+
}
|
|
1789
|
+
return msg;
|
|
1790
|
+
},
|
|
1791
|
+
};
|
|
1792
|
+
const useColor = getColorsOption(this.getCompilerOptions());
|
|
1793
|
+
const server = this.server;
|
|
1794
|
+
if (this.options.ipc) {
|
|
1795
|
+
this.logger.info(`Project is running at: "${server?.address()}"`);
|
|
1796
|
+
}
|
|
1797
|
+
else {
|
|
1798
|
+
const protocol = this.isTlsServer ? 'https' : 'http';
|
|
1799
|
+
const addressInfo = server?.address();
|
|
1800
|
+
if (!addressInfo) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
const { address, port } = addressInfo;
|
|
1804
|
+
const prettyPrintURL = (newHostname) => url.format({ protocol, hostname: newHostname, port, pathname: '/' });
|
|
1805
|
+
let host;
|
|
1806
|
+
let localhost;
|
|
1807
|
+
let loopbackIPv4;
|
|
1808
|
+
let loopbackIPv6;
|
|
1809
|
+
let networkUrlIPv4;
|
|
1810
|
+
let networkUrlIPv6;
|
|
1811
|
+
if (this.options.host) {
|
|
1812
|
+
if (this.options.host === 'localhost') {
|
|
1813
|
+
localhost = prettyPrintURL('localhost');
|
|
1814
|
+
}
|
|
1815
|
+
else {
|
|
1816
|
+
let isIP;
|
|
1817
|
+
try {
|
|
1818
|
+
isIP = ipaddr.parse(this.options.host);
|
|
1819
|
+
}
|
|
1820
|
+
catch {
|
|
1821
|
+
// Ignore
|
|
1822
|
+
}
|
|
1823
|
+
if (!isIP) {
|
|
1824
|
+
host = prettyPrintURL(this.options.host);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
const parsedIP = ipaddr.parse(address);
|
|
1829
|
+
if (parsedIP.range() === 'unspecified') {
|
|
1830
|
+
localhost = prettyPrintURL('localhost');
|
|
1831
|
+
loopbackIPv6 = prettyPrintURL('::1');
|
|
1832
|
+
const networkIPv4 = Server.findIp('v4', false);
|
|
1833
|
+
if (networkIPv4) {
|
|
1834
|
+
networkUrlIPv4 = prettyPrintURL(networkIPv4);
|
|
1835
|
+
}
|
|
1836
|
+
const networkIPv6 = Server.findIp('v6', false);
|
|
1837
|
+
if (networkIPv6) {
|
|
1838
|
+
networkUrlIPv6 = prettyPrintURL(networkIPv6);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
else if (parsedIP.range() === 'loopback') {
|
|
1842
|
+
if (parsedIP.kind() === 'ipv4') {
|
|
1843
|
+
loopbackIPv4 = prettyPrintURL(parsedIP.toString());
|
|
1844
|
+
}
|
|
1845
|
+
else if (parsedIP.kind() === 'ipv6') {
|
|
1846
|
+
loopbackIPv6 = prettyPrintURL(parsedIP.toString());
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
else {
|
|
1850
|
+
networkUrlIPv4 =
|
|
1851
|
+
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
|
|
1852
|
+
? prettyPrintURL(parsedIP.toIPv4Address().toString())
|
|
1853
|
+
: prettyPrintURL(address);
|
|
1854
|
+
if (parsedIP.kind() === 'ipv6') {
|
|
1855
|
+
networkUrlIPv6 = prettyPrintURL(address);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
this.logger.info('Project is running at:');
|
|
1859
|
+
if (host) {
|
|
1860
|
+
this.logger.info(`Server: ${colors.info(useColor, host)}`);
|
|
1861
|
+
}
|
|
1862
|
+
if (localhost || loopbackIPv4 || loopbackIPv6) {
|
|
1863
|
+
const loopbacks = [];
|
|
1864
|
+
if (localhost) {
|
|
1865
|
+
loopbacks.push([colors.info(useColor, localhost)]);
|
|
1866
|
+
}
|
|
1867
|
+
if (loopbackIPv4) {
|
|
1868
|
+
loopbacks.push([colors.info(useColor, loopbackIPv4)]);
|
|
1869
|
+
}
|
|
1870
|
+
if (loopbackIPv6) {
|
|
1871
|
+
loopbacks.push([colors.info(useColor, loopbackIPv6)]);
|
|
1872
|
+
}
|
|
1873
|
+
this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
|
|
1874
|
+
}
|
|
1875
|
+
if (networkUrlIPv4) {
|
|
1876
|
+
this.logger.info(`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`);
|
|
1877
|
+
}
|
|
1878
|
+
if (networkUrlIPv6) {
|
|
1879
|
+
this.logger.info(`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`);
|
|
1880
|
+
}
|
|
1881
|
+
if (this.options.open?.length > 0) {
|
|
1882
|
+
const openTarget = prettyPrintURL(!this.options.host ||
|
|
1883
|
+
this.options.host === '0.0.0.0' ||
|
|
1884
|
+
this.options.host === '::'
|
|
1885
|
+
? 'localhost'
|
|
1886
|
+
: this.options.host);
|
|
1887
|
+
await this.openBrowser(openTarget);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (this.options.static?.length > 0) {
|
|
1891
|
+
this.logger.info(`Content not from webpack is served from '${colors.info(useColor, this.options.static
|
|
1892
|
+
.map((staticOption) => staticOption.directory)
|
|
1893
|
+
.join(', '))}' directory`);
|
|
1894
|
+
}
|
|
1895
|
+
if (this.options.historyApiFallback) {
|
|
1896
|
+
this.logger.info(`404s will fallback to '${colors.info(useColor, this.options.historyApiFallback
|
|
1897
|
+
.index || '/index.html')}'`);
|
|
1898
|
+
}
|
|
1899
|
+
if (this.options.bonjour) {
|
|
1900
|
+
const bonjourProtocol = this.options.bonjour?.type ||
|
|
1901
|
+
this.isTlsServer
|
|
1902
|
+
? 'https'
|
|
1903
|
+
: 'http';
|
|
1904
|
+
this.logger.info(`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
setHeaders(req, res, next) {
|
|
1908
|
+
let { headers } = this.options;
|
|
1909
|
+
if (headers) {
|
|
1910
|
+
if (typeof headers === 'function') {
|
|
1911
|
+
headers = headers(req, res, this.middleware ? this.middleware.context : undefined);
|
|
1912
|
+
}
|
|
1913
|
+
const allHeaders = [];
|
|
1914
|
+
if (!Array.isArray(headers)) {
|
|
1915
|
+
for (const name in headers) {
|
|
1916
|
+
allHeaders.push({
|
|
1917
|
+
key: name,
|
|
1918
|
+
value: headers[name],
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
headers = allHeaders;
|
|
1922
|
+
}
|
|
1923
|
+
for (const { key, value } of headers) {
|
|
1924
|
+
res.setHeader(key, value);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
next();
|
|
1928
|
+
}
|
|
1929
|
+
isHostAllowed(value) {
|
|
1930
|
+
const { allowedHosts } = this.options;
|
|
1931
|
+
// allow user to opt out of this security check, at their own risk
|
|
1932
|
+
// by explicitly enabling allowedHosts
|
|
1933
|
+
if (allowedHosts === 'all') {
|
|
1934
|
+
return true;
|
|
1935
|
+
}
|
|
1936
|
+
// always allow localhost host, for convenience
|
|
1937
|
+
// allow if value is in allowedHosts
|
|
1938
|
+
if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
|
|
1939
|
+
for (const allowedHost of allowedHosts) {
|
|
1940
|
+
if (allowedHost === value) {
|
|
1941
|
+
return true;
|
|
1942
|
+
}
|
|
1943
|
+
// support "." as a subdomain wildcard
|
|
1944
|
+
// e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
|
|
1945
|
+
if (allowedHost.startsWith('.') && // "example.com" (value === allowedHost.substring(1))
|
|
1946
|
+
// "*.example.com" (value.endsWith(allowedHost))
|
|
1947
|
+
(value === allowedHost.slice(1) || value.endsWith(allowedHost))) {
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
// Also allow if `client.webSocketURL.hostname` provided
|
|
1953
|
+
if (this.options.client &&
|
|
1954
|
+
typeof this.options.client.webSocketURL !==
|
|
1955
|
+
'undefined') {
|
|
1956
|
+
return (this.options.client.webSocketURL === value);
|
|
1957
|
+
}
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
isValidHost(headers, headerToCheck, validateHost = true) {
|
|
1961
|
+
if (this.options.allowedHosts === 'all') {
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
// get the Host header and extract hostname
|
|
1965
|
+
// we don't care about port not matching
|
|
1966
|
+
const header = headers[headerToCheck];
|
|
1967
|
+
if (!header) {
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(header)) {
|
|
1971
|
+
return true;
|
|
67
1972
|
}
|
|
1973
|
+
// use the node url-parser to retrieve the hostname from the host-header.
|
|
1974
|
+
// TODO resolve me in the next major release
|
|
1975
|
+
// eslint-disable-next-line n/no-deprecated-api
|
|
1976
|
+
const { hostname } = url.parse(
|
|
1977
|
+
// if header doesn't have scheme, add // for parsing.
|
|
1978
|
+
/^(.+:)?\/\//.test(header) ? header : `//${header}`, false, true);
|
|
1979
|
+
if (hostname === null) {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
if (this.isHostAllowed(hostname)) {
|
|
1983
|
+
return true;
|
|
1984
|
+
}
|
|
1985
|
+
// always allow requests with explicit IPv4 or IPv6-address.
|
|
1986
|
+
// A note on IPv6 addresses:
|
|
1987
|
+
// header will always contain the brackets denoting
|
|
1988
|
+
// an IPv6-address in URLs,
|
|
1989
|
+
// these are removed from the hostname in url.parse(),
|
|
1990
|
+
// so we have the pure IPv6-address in hostname.
|
|
1991
|
+
// For convenience, always allow localhost (hostname === 'localhost')
|
|
1992
|
+
// and its subdomains (hostname.endsWith(".localhost")).
|
|
1993
|
+
// allow hostname of listening address (hostname === this.options.host)
|
|
1994
|
+
const isValidHostname = validateHost
|
|
1995
|
+
? ipaddr.IPv4.isValid(hostname) ||
|
|
1996
|
+
ipaddr.IPv6.isValid(hostname) ||
|
|
1997
|
+
hostname === 'localhost' ||
|
|
1998
|
+
hostname.endsWith('.localhost') ||
|
|
1999
|
+
hostname === this.options.host
|
|
2000
|
+
: false;
|
|
2001
|
+
return isValidHostname;
|
|
2002
|
+
}
|
|
2003
|
+
isSameOrigin(headers) {
|
|
2004
|
+
if (this.options.allowedHosts === 'all') {
|
|
2005
|
+
return true;
|
|
2006
|
+
}
|
|
2007
|
+
const originHeader = headers.origin;
|
|
2008
|
+
if (!originHeader) {
|
|
2009
|
+
return this.options.allowedHosts === 'all';
|
|
2010
|
+
}
|
|
2011
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(originHeader)) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
// TODO resolve me in the next major release
|
|
2015
|
+
// eslint-disable-next-line n/no-deprecated-api
|
|
2016
|
+
const origin = url.parse(originHeader, false, true).hostname;
|
|
2017
|
+
if (origin === null) {
|
|
2018
|
+
return false;
|
|
2019
|
+
}
|
|
2020
|
+
if (this.isHostAllowed(origin)) {
|
|
2021
|
+
return true;
|
|
2022
|
+
}
|
|
2023
|
+
const hostHeader = headers.host;
|
|
2024
|
+
if (!hostHeader) {
|
|
2025
|
+
return this.options.allowedHosts === 'all';
|
|
2026
|
+
}
|
|
2027
|
+
if (DEFAULT_ALLOWED_PROTOCOLS.test(hostHeader)) {
|
|
2028
|
+
return true;
|
|
2029
|
+
}
|
|
2030
|
+
// eslint-disable-next-line n/no-deprecated-api
|
|
2031
|
+
const host = url.parse(
|
|
2032
|
+
// if hostHeader doesn't have scheme, add // for parsing.
|
|
2033
|
+
/^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`, false, true).hostname;
|
|
2034
|
+
if (host === null) {
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
if (this.isHostAllowed(host)) {
|
|
2038
|
+
return true;
|
|
2039
|
+
}
|
|
2040
|
+
return origin === host;
|
|
2041
|
+
}
|
|
2042
|
+
sendMessage(clients, type, data, params) {
|
|
2043
|
+
for (const client of clients) {
|
|
2044
|
+
// `sockjs` uses `1` to indicate client is ready to accept data
|
|
2045
|
+
// `ws` uses `WebSocket.OPEN`, but it is mean `1` too
|
|
2046
|
+
if (client.readyState === 1) {
|
|
2047
|
+
client.send(JSON.stringify({ type, data, params }));
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
// Send stats to a socket or multiple sockets
|
|
2052
|
+
sendStats(clients, stats, force) {
|
|
2053
|
+
if (!stats) {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
const shouldEmit = !force &&
|
|
2057
|
+
stats &&
|
|
2058
|
+
(!stats.errors || stats.errors.length === 0) &&
|
|
2059
|
+
(!stats.warnings || stats.warnings.length === 0) &&
|
|
2060
|
+
this.currentHash === stats.hash;
|
|
2061
|
+
if (shouldEmit) {
|
|
2062
|
+
this.sendMessage(clients, 'still-ok');
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
this.currentHash = stats.hash;
|
|
2066
|
+
this.sendMessage(clients, 'hash', stats.hash);
|
|
2067
|
+
if (stats.errors?.length > 0 ||
|
|
2068
|
+
stats.warnings?.length > 0) {
|
|
2069
|
+
const hasErrors = stats.errors?.length > 0;
|
|
2070
|
+
if (stats.warnings?.length >
|
|
2071
|
+
0) {
|
|
2072
|
+
let params;
|
|
2073
|
+
if (hasErrors) {
|
|
2074
|
+
params = { preventReloading: true };
|
|
2075
|
+
}
|
|
2076
|
+
this.sendMessage(clients, 'warnings', stats.warnings, params);
|
|
2077
|
+
}
|
|
2078
|
+
if (stats.errors?.length > 0) {
|
|
2079
|
+
this.sendMessage(clients, 'errors', stats.errors);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
else {
|
|
2083
|
+
this.sendMessage(clients, 'ok');
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
watchFiles(watchPath, watchOptions) {
|
|
2087
|
+
const chokidar = require('chokidar');
|
|
2088
|
+
const watcher = chokidar.watch(watchPath, watchOptions);
|
|
2089
|
+
// disabling refreshing on changing the content
|
|
2090
|
+
if (this.options.liveReload) {
|
|
2091
|
+
watcher.on('change', (item) => {
|
|
2092
|
+
if (this.webSocketServer) {
|
|
2093
|
+
this.sendMessage(this.webSocketServer.clients, 'static-changed', item);
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
this.staticWatchers.push(watcher);
|
|
2098
|
+
}
|
|
2099
|
+
invalidate(callback = () => { }) {
|
|
2100
|
+
if (this.middleware) {
|
|
2101
|
+
this.middleware.invalidate(callback);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
async start() {
|
|
2105
|
+
await this.normalizeOptions();
|
|
2106
|
+
if (this.options.ipc) {
|
|
2107
|
+
await new Promise((resolve, reject) => {
|
|
2108
|
+
const net = require('node:net');
|
|
2109
|
+
const socket = new net.Socket();
|
|
2110
|
+
socket.on('error', (error) => {
|
|
2111
|
+
if (error.code === 'ECONNREFUSED') {
|
|
2112
|
+
// No other server listening on this socket, so it can be safely removed
|
|
2113
|
+
fs.unlinkSync(this.options.ipc);
|
|
2114
|
+
resolve();
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (error.code === 'ENOENT') {
|
|
2118
|
+
resolve();
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
reject(error);
|
|
2122
|
+
});
|
|
2123
|
+
socket.connect({ path: this.options.ipc }, () => {
|
|
2124
|
+
throw new Error(`IPC "${this.options.ipc}" is already used`);
|
|
2125
|
+
});
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
else {
|
|
2129
|
+
this.options.host = await Server.getHostname(this.options.host);
|
|
2130
|
+
this.options.port = await Server.getFreePort(this.options.port, this.options.host);
|
|
2131
|
+
}
|
|
2132
|
+
await this.initialize();
|
|
2133
|
+
const listenOptions = this.options.ipc
|
|
2134
|
+
? { path: this.options.ipc }
|
|
2135
|
+
: { host: this.options.host, port: this.options.port };
|
|
2136
|
+
await new Promise((resolve) => {
|
|
2137
|
+
this.server.listen(listenOptions, () => {
|
|
2138
|
+
resolve();
|
|
2139
|
+
});
|
|
2140
|
+
});
|
|
2141
|
+
if (this.options.ipc) {
|
|
2142
|
+
// chmod 666 (rw rw rw)
|
|
2143
|
+
const READ_WRITE = 438;
|
|
2144
|
+
await fs.promises.chmod(this.options.ipc, READ_WRITE);
|
|
2145
|
+
}
|
|
2146
|
+
if (this.options.webSocketServer) {
|
|
2147
|
+
this.createWebSocketServer();
|
|
2148
|
+
}
|
|
2149
|
+
if (this.options.bonjour) {
|
|
2150
|
+
this.runBonjour();
|
|
2151
|
+
}
|
|
2152
|
+
await this.logStatus();
|
|
2153
|
+
if (typeof this.options.onListening === 'function') {
|
|
2154
|
+
this.options.onListening(this);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
startCallback(callback = () => { }) {
|
|
2158
|
+
this.start()
|
|
2159
|
+
.then(() => callback(), callback)
|
|
2160
|
+
.catch(callback);
|
|
2161
|
+
}
|
|
2162
|
+
async stop() {
|
|
2163
|
+
if (this.bonjour) {
|
|
2164
|
+
await new Promise((resolve) => {
|
|
2165
|
+
this.stopBonjour(() => {
|
|
2166
|
+
resolve();
|
|
2167
|
+
});
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
this.webSocketProxies = [];
|
|
2171
|
+
await Promise.all(this.staticWatchers.map((watcher) => watcher.close()));
|
|
2172
|
+
this.staticWatchers = [];
|
|
2173
|
+
if (this.webSocketServer) {
|
|
2174
|
+
await new Promise((resolve) => {
|
|
2175
|
+
this.webSocketServer.implementation.close(() => {
|
|
2176
|
+
this.webSocketServer = null;
|
|
2177
|
+
resolve();
|
|
2178
|
+
});
|
|
2179
|
+
for (const client of this.webSocketServer.clients) {
|
|
2180
|
+
client.terminate();
|
|
2181
|
+
}
|
|
2182
|
+
this.webSocketServer.clients = [];
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
if (this.server) {
|
|
2186
|
+
await new Promise((resolve) => {
|
|
2187
|
+
this.server.close(() => {
|
|
2188
|
+
this.server = undefined;
|
|
2189
|
+
resolve();
|
|
2190
|
+
});
|
|
2191
|
+
for (const socket of this.sockets) {
|
|
2192
|
+
socket.destroy();
|
|
2193
|
+
}
|
|
2194
|
+
this.sockets = [];
|
|
2195
|
+
});
|
|
2196
|
+
if (this.middleware) {
|
|
2197
|
+
await new Promise((resolve, reject) => {
|
|
2198
|
+
this.middleware.close((error) => {
|
|
2199
|
+
if (error) {
|
|
2200
|
+
reject(error);
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
resolve();
|
|
2204
|
+
});
|
|
2205
|
+
});
|
|
2206
|
+
this.middleware = undefined;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
// We add listeners to signals when creating a new Server instance
|
|
2210
|
+
// So ensure they are removed to prevent EventEmitter memory leak warnings
|
|
2211
|
+
for (const item of this.listeners) {
|
|
2212
|
+
process.removeListener(item.name, item.listener);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
stopCallback(callback = () => { }) {
|
|
2216
|
+
this.stop()
|
|
2217
|
+
.then(() => callback(), callback)
|
|
2218
|
+
.catch(callback);
|
|
68
2219
|
}
|
|
69
2220
|
}
|
|
70
|
-
|
|
71
|
-
RspackDevServer.version = package_json_1.version;
|
|
72
|
-
exports.RspackDevServer = RspackDevServer;
|
|
2221
|
+
exports.default = Server;
|