@rspack/dev-server 0.0.20 → 0.0.22

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 (106) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +1 -1
  3. package/dist/config.d.ts +20 -15
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +0 -52
  6. package/dist/config.js.map +1 -1
  7. package/dist/server.d.ts +14 -41
  8. package/dist/server.d.ts.map +1 -1
  9. package/dist/server.js +308 -255
  10. package/dist/server.js.map +1 -1
  11. package/jest.config.js +6 -3
  12. package/package.json +13 -10
  13. package/src/config.ts +22 -68
  14. package/src/server.ts +350 -309
  15. package/tests/__snapshots__/normalizeOptions.test.ts.snap +226 -16
  16. package/tests/e2e/hot-reaload.test.ts +115 -0
  17. package/tests/e2e-fixtures/react/app.jsx +14 -0
  18. package/tests/e2e-fixtures/react/index.css +3 -0
  19. package/tests/e2e-fixtures/react/index.html +12 -0
  20. package/tests/e2e-fixtures/react/index.jsx +6 -0
  21. package/tests/e2e-fixtures/react/node_modules/react/LICENSE +21 -0
  22. package/tests/e2e-fixtures/react/node_modules/react/README.md +37 -0
  23. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-dev-runtime.development.js +1296 -0
  24. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-dev-runtime.production.min.js +10 -0
  25. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-dev-runtime.profiling.min.js +10 -0
  26. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-runtime.development.js +1314 -0
  27. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-runtime.production.min.js +11 -0
  28. package/tests/e2e-fixtures/react/node_modules/react/cjs/react-jsx-runtime.profiling.min.js +11 -0
  29. package/tests/e2e-fixtures/react/node_modules/react/cjs/react.development.js +2739 -0
  30. package/tests/e2e-fixtures/react/node_modules/react/cjs/react.production.min.js +26 -0
  31. package/tests/e2e-fixtures/react/node_modules/react/cjs/react.shared-subset.development.js +20 -0
  32. package/tests/e2e-fixtures/react/node_modules/react/cjs/react.shared-subset.production.min.js +10 -0
  33. package/tests/e2e-fixtures/react/node_modules/react/index.js +7 -0
  34. package/tests/e2e-fixtures/react/node_modules/react/jsx-dev-runtime.js +7 -0
  35. package/tests/e2e-fixtures/react/node_modules/react/jsx-runtime.js +7 -0
  36. package/tests/e2e-fixtures/react/node_modules/react/package.json +47 -0
  37. package/tests/e2e-fixtures/react/node_modules/react/react.shared-subset.js +7 -0
  38. package/tests/e2e-fixtures/react/node_modules/react/umd/react.development.js +3342 -0
  39. package/tests/e2e-fixtures/react/node_modules/react/umd/react.production.min.js +31 -0
  40. package/tests/e2e-fixtures/react/node_modules/react/umd/react.profiling.min.js +31 -0
  41. package/tests/e2e-fixtures/react/node_modules/react-dom/LICENSE +21 -0
  42. package/tests/e2e-fixtures/react/node_modules/react-dom/README.md +60 -0
  43. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js +7018 -0
  44. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +93 -0
  45. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js +7078 -0
  46. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.min.js +101 -0
  47. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server.browser.development.js +7003 -0
  48. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js +96 -0
  49. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server.node.development.js +7059 -0
  50. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-server.node.production.min.js +102 -0
  51. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-test-utils.development.js +1741 -0
  52. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom-test-utils.production.min.js +40 -0
  53. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom.development.js +29868 -0
  54. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom.production.min.js +323 -0
  55. package/tests/e2e-fixtures/react/node_modules/react-dom/cjs/react-dom.profiling.min.js +367 -0
  56. package/tests/e2e-fixtures/react/node_modules/react-dom/client.js +25 -0
  57. package/tests/e2e-fixtures/react/node_modules/react-dom/index.js +38 -0
  58. package/tests/e2e-fixtures/react/node_modules/react-dom/package.json +62 -0
  59. package/tests/e2e-fixtures/react/node_modules/react-dom/profiling.js +38 -0
  60. package/tests/e2e-fixtures/react/node_modules/react-dom/server.browser.js +17 -0
  61. package/tests/e2e-fixtures/react/node_modules/react-dom/server.js +3 -0
  62. package/tests/e2e-fixtures/react/node_modules/react-dom/server.node.js +17 -0
  63. package/tests/e2e-fixtures/react/node_modules/react-dom/test-utils.js +7 -0
  64. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-server-legacy.browser.development.js +7015 -0
  65. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-server-legacy.browser.production.min.js +75 -0
  66. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-server.browser.development.js +7000 -0
  67. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-server.browser.production.min.js +76 -0
  68. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-test-utils.development.js +1737 -0
  69. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom-test-utils.production.min.js +33 -0
  70. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom.development.js +29869 -0
  71. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom.production.min.js +267 -0
  72. package/tests/e2e-fixtures/react/node_modules/react-dom/umd/react-dom.profiling.min.js +285 -0
  73. package/tests/e2e-fixtures/react/node_modules/scheduler/LICENSE +21 -0
  74. package/tests/e2e-fixtures/react/node_modules/scheduler/README.md +9 -0
  75. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler-unstable_mock.development.js +700 -0
  76. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler-unstable_mock.production.min.js +20 -0
  77. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler-unstable_post_task.development.js +207 -0
  78. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler-unstable_post_task.production.min.js +14 -0
  79. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler.development.js +634 -0
  80. package/tests/e2e-fixtures/react/node_modules/scheduler/cjs/scheduler.production.min.js +19 -0
  81. package/tests/e2e-fixtures/react/node_modules/scheduler/index.js +7 -0
  82. package/tests/e2e-fixtures/react/node_modules/scheduler/package.json +36 -0
  83. package/tests/e2e-fixtures/react/node_modules/scheduler/umd/scheduler-unstable_mock.development.js +699 -0
  84. package/tests/e2e-fixtures/react/node_modules/scheduler/umd/scheduler-unstable_mock.production.min.js +19 -0
  85. package/tests/e2e-fixtures/react/node_modules/scheduler/umd/scheduler.development.js +152 -0
  86. package/tests/e2e-fixtures/react/node_modules/scheduler/umd/scheduler.production.min.js +146 -0
  87. package/tests/e2e-fixtures/react/node_modules/scheduler/umd/scheduler.profiling.min.js +146 -0
  88. package/tests/e2e-fixtures/react/node_modules/scheduler/unstable_mock.js +7 -0
  89. package/tests/e2e-fixtures/react/node_modules/scheduler/unstable_post_task.js +7 -0
  90. package/tests/e2e-fixtures/react/package.json +8 -0
  91. package/tests/e2e-fixtures/react/webpack.config.js +26 -0
  92. package/tests/helpers/emitFile.ts +69 -0
  93. package/tests/helpers/runBrowser.ts +85 -0
  94. package/tests/helpers/tempDir.ts +58 -0
  95. package/tests/normalizeOptions.test.ts +59 -17
  96. package/tsconfig.tsbuildinfo +1 -1
  97. package/dist/logger.d.ts +0 -7
  98. package/dist/logger.d.ts.map +0 -1
  99. package/dist/logger.js +0 -28
  100. package/dist/logger.js.map +0 -1
  101. package/dist/ws.d.ts +0 -13
  102. package/dist/ws.d.ts.map +0 -1
  103. package/dist/ws.js +0 -12
  104. package/dist/ws.js.map +0 -1
  105. package/src/logger.ts +0 -30
  106. package/src/ws.ts +0 -18
package/src/server.ts CHANGED
@@ -1,153 +1,207 @@
1
- import type { Compiler, Dev, RspackOptionsNormalized } from "@rspack/core";
2
- import type { Logger } from "./logger";
1
+ import { Compiler, MultiCompiler } from "@rspack/core";
3
2
  import type { Socket } from "net";
4
3
  import type { FSWatcher, WatchOptions } from "chokidar";
5
- import type { WebSocketServer, ClientConnection } from "./ws";
6
- import type {
7
- Application,
8
- RequestHandler as ExpressRequestHandler,
9
- ErrorRequestHandler as ExpressErrorRequestHandler
10
- } from "express";
11
- import type { DevMiddleware } from "@rspack/dev-middleware";
4
+ import rdm, { getRspackMemoryAssets } from "@rspack/dev-middleware";
12
5
  import type { Server } from "http";
13
- import type { ResolvedDev } from "./config";
14
6
  import fs from "fs";
15
- import chokidar from "chokidar";
16
- import http from "http";
17
- import { createLogger } from "./logger";
18
7
  import WebpackDevServer from "webpack-dev-server";
19
- import express from "express";
8
+ import type { ResolvedDevServer, DevServer } from "./config";
20
9
 
21
- import rdm from "@rspack/dev-middleware";
22
- import { createWebsocketServer } from "./ws";
23
- import { resolveDevOptions } from "./config";
24
-
25
- interface Middleware {
26
- name?: string;
27
- path?: string;
28
- middleware: ExpressErrorRequestHandler | ExpressRequestHandler;
29
- }
30
- interface Listener {
31
- name: string | Symbol;
32
- listener: (...args: any) => void;
33
- }
34
- type Host = "local-ip" | "local-ipv4" | "local-ipv6" | string;
35
- type Port = number | string | "auto";
36
-
37
- // copy from webpack-dev-server
38
- export class RspackDevServer {
39
- options: ResolvedDev;
40
- logger: Logger;
10
+ export class RspackDevServer extends WebpackDevServer {
11
+ /**
12
+ * resolved after `normalizedOptions`
13
+ */
14
+ options: ResolvedDevServer;
41
15
  staticWatchers: FSWatcher[];
42
16
  sockets: Socket[];
43
- app: Application;
44
17
  server: Server;
45
- private listeners: Listener[];
46
- private currentHash: string;
47
- private middleware: DevMiddleware | undefined;
48
- // TODO: now only support 'ws'
49
- webSocketServer: WebSocketServer | undefined;
50
-
51
- constructor(public compiler: Compiler) {
52
- this.logger = createLogger("rspack-dev-server");
53
- this.staticWatchers = [];
54
- this.listeners = [];
55
- this.sockets = [];
56
- this.currentHash = "";
57
- this.options = this.normalizeOptions(compiler.options.devServer);
58
- this.rewriteCompilerOptions();
59
- this.addAdditionEntires();
60
- }
18
+ // @ts-expect-error
19
+ public compiler: Compiler | MultiCompiler;
20
+ webSocketServer: WebpackDevServer.WebSocketServerImplementation | undefined;
61
21
 
62
- normalizeOptions(dev: Dev = {}) {
63
- return resolveDevOptions(dev, this.compiler.options);
22
+ constructor(options: DevServer, compiler: Compiler | MultiCompiler) {
23
+ // @ts-expect-error
24
+ super(options, compiler);
64
25
  }
65
26
 
66
- rewriteCompilerOptions() {
67
- if (!this.compiler.options.builtins.react) {
68
- this.compiler.options.builtins.react = {};
69
- }
70
- this.compiler.options.builtins.react.development =
71
- this.compiler.options.builtins.react.development ?? true;
72
- if (this.options.hot) {
73
- this.compiler.options.builtins.react.refresh =
74
- this.compiler.options.builtins.react.refresh ?? true;
75
- }
76
- }
27
+ addAdditionEntires(compiler: Compiler) {
28
+ const additionalEntries: string[] = [];
29
+ // TODO: align with webpack-dev-server after options.target is aligned
30
+ const isWebTarget =
31
+ compiler.options.target.includes("web") ||
32
+ compiler.options.target.includes("webworker");
33
+ if (this.options.client && isWebTarget) {
34
+ let webSocketURLStr = "";
35
+
36
+ if (this.options.webSocketServer) {
37
+ const webSocketURL = this.options.client
38
+ .webSocketURL as WebpackDevServer.WebSocketURL;
39
+ const webSocketServer = this.options.webSocketServer;
40
+ const searchParams = new URLSearchParams();
41
+
42
+ let protocol: string;
43
+
44
+ // We are proxying dev server and need to specify custom `hostname`
45
+ if (typeof webSocketURL.protocol !== "undefined") {
46
+ protocol = webSocketURL.protocol;
47
+ } else {
48
+ protocol = this.options.server.type === "http" ? "ws:" : "wss:";
49
+ }
77
50
 
78
- addAdditionEntires() {
79
- const entries: string[] = [];
51
+ searchParams.set("protocol", protocol);
80
52
 
81
- if (this.options.hot) {
82
- const hotUpdateEntryPath = require.resolve(
83
- "@rspack/dev-client/devServer"
84
- );
85
- entries.push(hotUpdateEntryPath);
53
+ if (typeof webSocketURL.username !== "undefined") {
54
+ searchParams.set("username", webSocketURL.username);
55
+ }
86
56
 
87
- if (this.compiler.options.builtins.react?.refresh) {
88
- const reactRefreshEntryPath = require.resolve(
89
- "@rspack/dev-client/react-refresh"
90
- );
91
- entries.push(reactRefreshEntryPath);
92
- }
93
- }
57
+ if (typeof webSocketURL.password !== "undefined") {
58
+ searchParams.set("password", webSocketURL.password);
59
+ }
94
60
 
95
- const devClientEntryPath = require.resolve("@rspack/dev-client");
96
- entries.push(devClientEntryPath);
97
- for (const key in this.compiler.options.entry) {
98
- this.compiler.options.entry[key].import.unshift(...entries);
99
- }
100
- }
61
+ let hostname: string;
101
62
 
102
- static isAbsoluteURL(URL: string): boolean {
103
- return WebpackDevServer.isAbsoluteURL(URL);
104
- }
63
+ // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
64
+ // TODO show warning about this
65
+ const isSockJSType = webSocketServer.type === "sockjs";
105
66
 
106
- static findIp(gateway: string): string | undefined {
107
- return WebpackDevServer.findIp(gateway);
108
- }
67
+ // We are proxying dev server and need to specify custom `hostname`
68
+ if (typeof webSocketURL.hostname !== "undefined") {
69
+ hostname = webSocketURL.hostname;
70
+ }
71
+ // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
72
+ else if (
73
+ typeof webSocketServer.options.host !== "undefined" &&
74
+ !isSockJSType
75
+ ) {
76
+ hostname = webSocketServer.options.host;
77
+ }
78
+ // The `host` option is specified
79
+ else if (typeof this.options.host !== "undefined") {
80
+ hostname = this.options.host;
81
+ }
82
+ // The `port` option is not specified
83
+ else {
84
+ hostname = "0.0.0.0";
85
+ }
109
86
 
110
- static async internalIP(family: "v6" | "v4"): Promise<string | undefined> {
111
- return WebpackDevServer.internalIP(family);
112
- }
87
+ searchParams.set("hostname", hostname);
113
88
 
114
- static async internalIPSync(
115
- family: "v6" | "v4"
116
- ): Promise<string | undefined> {
117
- return WebpackDevServer.internalIPSync(family);
118
- }
89
+ let port: number | string;
119
90
 
120
- static async getHostname(hostname?: Host): Promise<string> {
121
- return WebpackDevServer.getHostname(hostname);
122
- }
91
+ // We are proxying dev server and need to specify custom `port`
92
+ if (typeof webSocketURL.port !== "undefined") {
93
+ port = webSocketURL.port;
94
+ }
95
+ // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
96
+ else if (
97
+ typeof webSocketServer.options.port !== "undefined" &&
98
+ !isSockJSType
99
+ ) {
100
+ port = webSocketServer.options.port;
101
+ }
102
+ // The `port` option is specified
103
+ else if (typeof this.options.port === "number") {
104
+ port = this.options.port;
105
+ }
106
+ // The `port` option is specified using `string`
107
+ else if (
108
+ typeof this.options.port === "string" &&
109
+ this.options.port !== "auto"
110
+ ) {
111
+ port = Number(this.options.port);
112
+ }
113
+ // The `port` option is not specified or set to `auto`
114
+ else {
115
+ port = "0";
116
+ }
123
117
 
124
- static async getFreePort(port: Port, host: string): Promise<string | number> {
125
- return WebpackDevServer.getFreePort(port, host);
126
- }
118
+ searchParams.set("port", String(port));
127
119
 
128
- static findCacheDir(): string {
129
- // TODO: we need remove the `webpack-dev-server` tag in WebpackDevServer;
130
- return "";
131
- }
120
+ let pathname = "";
132
121
 
133
- private getCompilerOptions(): RspackOptionsNormalized {
134
- return this.compiler.options;
135
- }
122
+ // We are proxying dev server and need to specify custom `pathname`
123
+ if (typeof webSocketURL.pathname !== "undefined") {
124
+ pathname = webSocketURL.pathname;
125
+ }
126
+ // Web socket server works on custom `path`
127
+ else if (
128
+ typeof webSocketServer.options.prefix !== "undefined" ||
129
+ typeof webSocketServer.options.path !== "undefined"
130
+ ) {
131
+ pathname =
132
+ webSocketServer.options.prefix || webSocketServer.options.path;
133
+ }
134
+
135
+ searchParams.set("pathname", pathname);
136
+
137
+ const client = /** @type {ClientConfiguration} */ this.options.client;
138
+
139
+ if (typeof client.logging !== "undefined") {
140
+ searchParams.set("logging", client.logging);
141
+ }
142
+
143
+ if (typeof client.progress !== "undefined") {
144
+ searchParams.set("progress", String(client.progress));
145
+ }
146
+
147
+ if (typeof client.overlay !== "undefined") {
148
+ searchParams.set(
149
+ "overlay",
150
+ typeof client.overlay === "boolean"
151
+ ? String(client.overlay)
152
+ : JSON.stringify(client.overlay)
153
+ );
154
+ }
136
155
 
137
- sendMessage(
138
- clients: ClientConnection[],
139
- type: string,
140
- data?: any,
141
- params?: any
142
- ) {
143
- for (const client of clients) {
144
- if (client.readyState === 1) {
145
- client.send(JSON.stringify({ type, data, params }));
156
+ if (typeof client.reconnect !== "undefined") {
157
+ searchParams.set(
158
+ "reconnect",
159
+ typeof client.reconnect === "number"
160
+ ? String(client.reconnect)
161
+ : "10"
162
+ );
163
+ }
164
+
165
+ if (typeof this.options.hot !== "undefined") {
166
+ searchParams.set("hot", String(this.options.hot));
167
+ }
168
+
169
+ if (typeof this.options.liveReload !== "undefined") {
170
+ searchParams.set("live-reload", String(this.options.liveReload));
171
+ }
172
+
173
+ webSocketURLStr = searchParams.toString();
146
174
  }
175
+
176
+ // TODO: should use providerPlugin
177
+ additionalEntries.push(this.getClientTransport());
178
+
179
+ additionalEntries.push(
180
+ `${require.resolve("@rspack/dev-client")}?${webSocketURLStr}`
181
+ );
182
+ }
183
+
184
+ if (this.options.hot) {
185
+ const hotUpdateEntryPath = require.resolve(
186
+ "@rspack/dev-client/devServer"
187
+ );
188
+ additionalEntries.push(hotUpdateEntryPath);
189
+
190
+ if (compiler.options.builtins.react?.refresh) {
191
+ const reactRefreshEntryPath = require.resolve(
192
+ "@rspack/dev-client/react-refresh"
193
+ );
194
+ additionalEntries.push(reactRefreshEntryPath);
195
+ }
196
+ }
197
+
198
+ for (const key in compiler.options.entry) {
199
+ compiler.options.entry[key].import.unshift(...additionalEntries);
147
200
  }
148
201
  }
149
202
 
150
203
  watchFiles(watchPath: string | string[], watchOptions?: WatchOptions): void {
204
+ const chokidar = require("chokidar");
151
205
  const watcher = chokidar.watch(watchPath, watchOptions);
152
206
 
153
207
  // disabling refreshing on changing the content
@@ -171,79 +225,154 @@ export class RspackDevServer {
171
225
  this.staticWatchers.push(watcher);
172
226
  }
173
227
 
174
- invalidate(callback = () => {}): void {
175
- if (this.middleware) {
176
- this.middleware.invalidate(callback);
228
+ getClientTransport(): string {
229
+ // WARNING: we can't use `super.getClientTransport`,
230
+ // because we doesn't had same directory structure.
231
+ // and TODO: we need impelement `webpack.providerPlugin`
232
+ let clientImplementation: string | undefined;
233
+ let clientImplementationFound = true;
234
+ const isKnownWebSocketServerImplementation =
235
+ this.options.webSocketServer &&
236
+ typeof this.options.webSocketServer.type === "string" &&
237
+ (this.options.webSocketServer.type === "ws" ||
238
+ this.options.webSocketServer.type === "sockjs");
239
+
240
+ let clientTransport: string | undefined;
241
+
242
+ if (this.options.client) {
243
+ if (
244
+ // @ts-ignore
245
+ typeof this.options.client.webSocketTransport !== "undefined"
246
+ ) {
247
+ // @ts-ignore
248
+ clientTransport = this.options.client.webSocketTransport;
249
+ } else if (isKnownWebSocketServerImplementation) {
250
+ // @ts-ignore
251
+ clientTransport = this.options.webSocketServer.type;
252
+ } else {
253
+ clientTransport = "ws";
254
+ }
255
+ } else {
256
+ clientTransport = "ws";
257
+ }
258
+
259
+ switch (typeof clientTransport) {
260
+ case "string":
261
+ // could be 'sockjs', 'ws', or a path that should be required
262
+ if (clientTransport === "sockjs") {
263
+ clientImplementation = require.resolve(
264
+ "@rspack/dev-client/clients/SockJSClient"
265
+ );
266
+ } else if (clientTransport === "ws") {
267
+ clientImplementation = require.resolve(
268
+ "@rspack/dev-client/clients/WebSocketClient"
269
+ );
270
+ } else {
271
+ try {
272
+ clientImplementation = require.resolve(clientTransport);
273
+ throw Error("Do not support custom ws client now");
274
+ } catch (e) {
275
+ clientImplementationFound = false;
276
+ }
277
+ }
278
+ break;
279
+ default:
280
+ clientImplementationFound = false;
281
+ }
282
+ if (!clientImplementationFound) {
283
+ throw new Error(
284
+ `${
285
+ !isKnownWebSocketServerImplementation
286
+ ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. "
287
+ : ""
288
+ }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 `
289
+ );
177
290
  }
291
+
292
+ return clientImplementation;
178
293
  }
179
294
 
180
- async start(): Promise<void> {
295
+ async initialize() {
296
+ const compilers =
297
+ this.compiler instanceof MultiCompiler
298
+ ? this.compiler.compilers
299
+ : [this.compiler];
300
+
301
+ compilers.forEach(compiler => {
302
+ compiler.options.builtins.react ??= {};
303
+ if (this.options.hot) {
304
+ compiler.options.builtins.react.refresh ??= true;
305
+ compiler.options.builtins.react.development ??= true;
306
+ } else if (compiler.options.builtins.react.refresh) {
307
+ this.logger.warn(
308
+ "builtins.react.refresh needs builtins.react.development and devServer.hot enabled"
309
+ );
310
+ }
311
+ });
312
+
313
+ if (this.options.webSocketServer) {
314
+ compilers.forEach(compiler => {
315
+ this.addAdditionEntires(compiler);
316
+ });
317
+ }
318
+
181
319
  this.setupHooks();
320
+ // @ts-expect-error: `setupApp` is private function in base class.
182
321
  this.setupApp();
183
- this.createServer();
184
- this.setupWatchStaticFiles();
185
- this.createWebsocketServer();
322
+ // @ts-expect-error: `setupHostHeaderCheck` is private function in base class.
323
+ this.setupHostHeaderCheck();
186
324
  this.setupDevMiddleware();
325
+ // @ts-expect-error: `setupBuiltInRoutes` is private function in base class.
326
+ this.setupBuiltInRoutes();
327
+ // @ts-expect-error: `setupWatchFiles` is private function in base class.
328
+ this.setupWatchFiles();
329
+ // @ts-expect-error: `setupWatchStaticFiles` is private function in base class.
330
+ this.setupWatchStaticFiles();
187
331
  this.setupMiddlewares();
188
- const host = await RspackDevServer.getHostname(this.options.host);
189
- const port = await RspackDevServer.getFreePort(this.options.port, host);
190
- await new Promise(resolve =>
191
- this.server.listen(
192
- {
193
- port,
194
- host
195
- },
196
- () => {
197
- this.logger.info(`Loopback: http://localhost:${port}`);
198
- let internalIPv4 = WebpackDevServer.internalIPSync("v4");
332
+ // @ts-expect-error: `createServer` is private function in base class.
333
+ this.createServer();
334
+
335
+ if (this.options.setupExitSignals) {
336
+ const signals = ["SIGINT", "SIGTERM"];
337
+
338
+ let needForceShutdown = false;
339
+
340
+ signals.forEach(signal => {
341
+ const listener = () => {
342
+ if (needForceShutdown) {
343
+ process.exit();
344
+ }
345
+
199
346
  this.logger.info(
200
- `Your Network (IPV4) http://${internalIPv4}:${port}`
347
+ "Gracefully shutting down. To force exit, press ^C again. Please wait..."
201
348
  );
202
- resolve({});
203
- }
204
- )
205
- );
206
- }
207
349
 
208
- startCallback(callback?: (err?: Error) => void): void {
209
- throw new Error("Method not implemented.");
210
- }
211
- stopCallback(callback?: (err?: Error) => void): void {
212
- throw new Error("Method not implemented.");
213
- }
214
- listen(port: Port, hostname: string, fn: (err?: Error) => void): void {
215
- throw new Error("Method not implemented.");
216
- }
217
- close(callback?: (err?: Error) => void): void {
218
- throw new Error("Method not implemented.");
219
- }
350
+ needForceShutdown = true;
220
351
 
221
- async stop(): Promise<void> {
222
- await Promise.all(this.staticWatchers.map(watcher => watcher.close()));
223
- this.middleware = null;
224
- this.staticWatchers = [];
225
- if (this.server) {
226
- this.server.close();
227
- }
228
- if (this.webSocketServer) {
229
- await new Promise(resolve => {
230
- this.webSocketServer.implementation.close(() => {
231
- resolve(void 0);
232
- });
233
- for (const client of this.webSocketServer.clients) client.terminate();
234
- });
235
- }
236
- }
352
+ this.stopCallback(() => {
353
+ if (typeof this.compiler.close === "function") {
354
+ this.compiler.close(() => {
355
+ process.exit();
356
+ });
357
+ } else {
358
+ process.exit();
359
+ }
360
+ });
361
+ };
237
362
 
238
- private setupApp() {
239
- this.app = express();
240
- }
363
+ // @ts-expect-error: `listeners` is private function in base class.
364
+ this.listeners.push({ name: signal, listener });
241
365
 
242
- private setupWatchStaticFiles() {
243
- if (this.options.static.watch === false) {
244
- return;
366
+ process.on(signal, listener);
367
+ });
245
368
  }
246
- this.watchFiles(this.options.static.directory, this.options.static.watch);
369
+
370
+ // Proxy WebSocket without the initial http request
371
+ // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
372
+ // @ts-expect-error: `webSocketProxies` is private function in base class.
373
+ this.webSocketProxies.forEach(webSocketProxy => {
374
+ this.server.on("upgrade", webSocketProxy.upgrade);
375
+ }, this);
247
376
  }
248
377
 
249
378
  private setupDevMiddleware() {
@@ -251,151 +380,63 @@ export class RspackDevServer {
251
380
  this.middleware = rdm(this.compiler, this.options.devMiddleware);
252
381
  }
253
382
 
254
- private createWebsocketServer() {
255
- if (this.options.webSocketServer !== false) {
256
- this.webSocketServer = createWebsocketServer(this);
257
- }
258
- }
259
-
260
383
  private setupMiddlewares() {
261
- const options = this.options;
262
- const middlewares: Middleware[] = [];
263
- middlewares.push({
264
- name: "rdm",
265
- middleware: this.middleware
266
- });
267
-
268
- if (this.compiler.options.experiments.lazyCompilation) {
269
- middlewares.push({
270
- middleware: (req, res, next) => {
271
- if (req.url.indexOf("/lazy-compilation-web/") > -1) {
272
- const path = req.url.replace("/lazy-compilation-web/", "");
273
- if (fs.existsSync(path)) {
274
- this.compiler.rebuild([path], (error, stats) => {
275
- if (error) {
276
- throw error;
277
- }
278
- res.write("");
279
- res.end();
280
- console.log("lazy compiler success");
384
+ const middlewares: WebpackDevServer.Middleware[] = [];
385
+ const compilers =
386
+ this.compiler instanceof MultiCompiler
387
+ ? this.compiler.compilers
388
+ : [this.compiler];
389
+
390
+ if (Array.isArray(this.options.static)) {
391
+ this.options.static.forEach(staticOptions => {
392
+ staticOptions.publicPath.forEach(publicPath => {
393
+ compilers.forEach(compiler => {
394
+ if (compiler.options.builtins.noEmitAssets) {
395
+ middlewares.push({
396
+ name: "rspack-memory-assets",
397
+ path: publicPath,
398
+ middleware: getRspackMemoryAssets(compiler, this.middleware)
281
399
  });
282
400
  }
283
- }
284
- }
401
+ });
402
+ });
285
403
  });
286
404
  }
287
405
 
288
- // Todo Add options
289
- const connectHistoryApiFallback = require("connect-history-api-fallback");
290
- middlewares.push({
291
- name: "[connect-history-api-fallback]",
292
- middleware: connectHistoryApiFallback({
293
- verbose: true,
294
- logger: console.log.bind(console)
295
- })
296
- });
297
- /**
298
- * supports three kinds of proxy configuration
299
- * {context: 'xxxx', target: 'yyy}
300
- * {['xxx']: { target: 'yyy}}
301
- * [{context: 'xxx',target:'yyy'}, {context: 'aaa', target: 'zzzz'}]
302
- */
303
- if (typeof options.proxy !== "undefined") {
304
- const { createProxyMiddleware } = require("http-proxy-middleware");
305
- function getProxyMiddleware(proxyConfig) {
306
- if (proxyConfig.target) {
307
- const context = proxyConfig.context || proxyConfig.path;
308
- return createProxyMiddleware(context, proxyConfig);
309
- }
310
- if (proxyConfig.router) {
311
- return createProxyMiddleware(proxyConfig);
312
- }
313
- }
314
- if (!Array.isArray(options.proxy)) {
315
- if (
316
- Object.prototype.hasOwnProperty.call(options.proxy, "target") ||
317
- Object.prototype.hasOwnProperty.call(options.proxy, "router")
318
- ) {
319
- options.proxy = [options.proxy];
320
- } else {
321
- options.proxy = Object.keys(options.proxy).map(context => {
322
- let proxyOptions;
323
- // For backwards compatibility reasons.
324
- const correctedContext = context
325
- .replace(/^\*$/, "**")
326
- .replace(/\/\*$/, "");
327
-
328
- if (
329
- typeof (/** @type {ProxyConfigMap} */ options.proxy[context]) ===
330
- "string"
331
- ) {
332
- proxyOptions = {
333
- context: correctedContext,
334
- target:
335
- /** @type {ProxyConfigMap} */
336
- options.proxy[context]
337
- };
338
- } else {
339
- proxyOptions = {
340
- // @ts-ignore
341
- .../** @type {ProxyConfigMap} */ options.proxy[context]
342
- };
343
- proxyOptions.context = correctedContext;
406
+ compilers.forEach(compiler => {
407
+ if (compiler.options.experiments.lazyCompilation) {
408
+ middlewares.push({
409
+ middleware: (req, res, next) => {
410
+ if (req.url.indexOf("/lazy-compilation-web/") > -1) {
411
+ const path = req.url.replace("/lazy-compilation-web/", "");
412
+ if (fs.existsSync(path)) {
413
+ compiler.rebuild(new Set([path]), new Set(), error => {
414
+ if (error) {
415
+ throw error;
416
+ }
417
+ res.write("");
418
+ res.end();
419
+ console.log("lazy compiler success");
420
+ });
421
+ }
344
422
  }
345
-
346
- return proxyOptions;
347
- });
348
- }
349
- }
350
- options.proxy.forEach(proxyConfig => {
351
- const handler = async (req, res, next) => {
352
- let proxyMiddleware = getProxyMiddleware(proxyConfig);
353
- const isByPassFuncDefined = typeof proxyConfig.bypass === "function";
354
- const bypassUrl = isByPassFuncDefined
355
- ? await proxyConfig.bypass(req, res, proxyConfig)
356
- : null;
357
- if (typeof bypassUrl === "boolean") {
358
- req.url = null;
359
- next();
360
- } else if (typeof bypassUrl === "string") {
361
- req.url = bypassUrl;
362
- } else if (proxyMiddleware) {
363
- return proxyMiddleware(req, res, next);
364
- } else {
365
- next();
366
423
  }
367
- };
368
- middlewares.push({
369
- name: "http-proxy-middleware",
370
- middleware: handler
371
- });
372
- middlewares.push({
373
- name: "http-proxy-middleware-error-handler",
374
- middleware: (error, req, res, next) => handler(req, res, next)
375
424
  });
376
- });
377
- }
378
- const publicPath =
379
- this.compiler.options.output.publicPath === "auto"
380
- ? ""
381
- : this.compiler.options.output.publicPath;
382
- middlewares.push({
383
- name: "express-static",
384
- path: publicPath,
385
- middleware: express.static(this.options.static.directory)
425
+ }
386
426
  });
387
427
 
388
- middlewares.forEach(m => {
389
- if (m.path) {
390
- this.app.use(m.path, m.middleware);
428
+ middlewares.forEach(middleware => {
429
+ if (typeof middleware === "function") {
430
+ this.app.use(middleware);
431
+ } else if (typeof middleware.path !== "undefined") {
432
+ this.app.use(middleware.path, middleware.middleware);
391
433
  } else {
392
- this.app.use(m.middleware);
434
+ this.app.use(middleware.middleware);
393
435
  }
394
436
  });
395
- }
396
437
 
397
- private createServer() {
398
- this.server = http.createServer(this.app);
438
+ // @ts-expect-error
439
+ super.setupMiddlewares();
399
440
  }
400
441
 
401
442
  private setupHooks() {