@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/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
- exports.RspackDevServer = void 0;
7
- const core_1 = require("@rspack/core");
8
- const webpack_dev_server_1 = __importDefault(require("webpack-dev-server"));
9
- // @ts-ignore 'package.json' is not under 'rootDir'
10
- const package_json_1 = require("../package.json");
11
- const patch_1 = require("./patch");
12
- (0, patch_1.applyDevServerPatch)();
13
- const getFreePort = async function getFreePort(port, host) {
14
- if (typeof port !== "undefined" && port !== null && port !== "auto") {
15
- return port;
16
- }
17
- const { default: pRetry } = await import("p-retry");
18
- const getPort = require("webpack-dev-server/lib/getPort");
19
- const basePort = typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined"
20
- ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10)
21
- : 8080;
22
- // Try to find unused port and listen on it for 3 times,
23
- // if port is not specified in options.
24
- const defaultPortRetry = typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined"
25
- ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10)
26
- : 3;
27
- return pRetry(() => getPort(basePort, host), {
28
- retries: defaultPortRetry,
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
- webpack_dev_server_1.default.getFreePort = getFreePort;
32
- class RspackDevServer extends webpack_dev_server_1.default {
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
- // biome-ignore lint/suspicious/noExplicitAny: _
35
- super(options, compiler);
36
- // override
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 instanceof core_1.MultiCompiler
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 === "production") {
46
- this.logger.warn("Hot Module Replacement (HMR) is enabled for the production build. \n" +
47
- "Make sure to disable HMR for production by setting `devServer.hot` to `false` in the configuration.");
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
- "ansi-html-community": require.resolve("@rspack/dev-server/client/utils/ansiHTML"),
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
- // @ts-expect-error
56
- await super.initialize();
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
- getClientEntry() {
59
- return require.resolve("@rspack/dev-server/client/index");
1190
+ async setupApp() {
1191
+ this.app = (typeof this.options.app === 'function'
1192
+ ? await this.options.app()
1193
+ : getExpress()());
60
1194
  }
61
- getClientHotEntry() {
62
- if (this.options.hot === "only") {
63
- return require.resolve("@rspack/core/hot/only-dev-server");
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
- if (this.options.hot) {
66
- return require.resolve("@rspack/core/hot/dev-server");
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
- RspackDevServer.getFreePort = getFreePort;
71
- RspackDevServer.version = package_json_1.version;
72
- exports.RspackDevServer = RspackDevServer;
2221
+ exports.default = Server;