@rspack/dev-server 2.0.0-beta.1 → 2.0.0-beta.2

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