@rspack/dev-server 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type {
2
+ Dev,
3
+ WebSocketServerOptions,
4
+ RspackOptionsNormalized
5
+ } from "@rspack/core";
6
+ import type { WatchOptions } from "chokidar";
7
+ import path from "node:path";
8
+ import { resolveWatchOption } from "@rspack/core";
9
+
10
+ export interface ResolvedDev {
11
+ port: number;
12
+ static: {
13
+ directory: string;
14
+ watch: false | WatchOptions;
15
+ };
16
+ devMiddleware: {};
17
+ hot: boolean;
18
+ open: boolean;
19
+ liveReload: boolean;
20
+ webSocketServer: false | WebSocketServerOptions;
21
+ }
22
+
23
+ export function resolveDevOptions(
24
+ devConfig: Dev,
25
+ compilerOptions: RspackOptionsNormalized
26
+ ): ResolvedDev {
27
+ const port = Number(devConfig.port ?? 8080);
28
+
29
+ const open = true;
30
+ const hot = devConfig.hot ?? true;
31
+ // --- static
32
+ const directory =
33
+ devConfig.static?.directory ??
34
+ path.resolve(compilerOptions.context, "dist");
35
+ let watch: false | WatchOptions = {};
36
+ if (devConfig.static?.watch === false) {
37
+ watch = false;
38
+ } else if (devConfig.static?.watch === true) {
39
+ watch = resolveWatchOption({});
40
+ } else if (devConfig.static?.watch) {
41
+ watch = devConfig.static?.watch;
42
+ }
43
+ // ---
44
+ const devMiddleware = devConfig.devMiddleware ?? {};
45
+ const liveReload = devConfig.liveReload ?? true;
46
+
47
+ let webSocketServer: false | WebSocketServerOptions = {};
48
+ if (devConfig.webSocketServer === false) {
49
+ webSocketServer = false;
50
+ } else if (devConfig.webSocketServer === true) {
51
+ webSocketServer = {};
52
+ } else if (devConfig.webSocketServer) {
53
+ webSocketServer = devConfig.webSocketServer;
54
+ }
55
+
56
+ return {
57
+ port,
58
+ static: {
59
+ directory,
60
+ watch
61
+ },
62
+ devMiddleware,
63
+ open,
64
+ hot,
65
+ liveReload,
66
+ webSocketServer
67
+ };
68
+ }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export * from "./server";
1
+ export { RspackDevServer } from "./server";
package/src/logger.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface Logger {
2
+ error(...args: any[]): void;
3
+ warn(...args: any[]): void;
4
+ info(...args: any[]): void;
5
+ }
6
+
7
+ const COLOR = {
8
+ RESET: "\x1b[0m",
9
+ //
10
+ RED: "\x1b[31m",
11
+ GREEN: "\x1b[32m",
12
+ YELLOW: "\x1b[33m"
13
+ };
14
+
15
+ export function createLogger(name: string): Logger {
16
+ if (!name.length) {
17
+ throw Error();
18
+ }
19
+ return {
20
+ error(...msgs: any[]) {
21
+ console.log("[", name, "]:", COLOR.RED, msgs, COLOR.RESET);
22
+ },
23
+ warn(...msgs: any[]) {
24
+ console.log("[", name, "]:", COLOR.YELLOW, msgs, COLOR.RESET);
25
+ },
26
+ info(...msgs: any[]) {
27
+ console.log("[", name, "]:", COLOR.GREEN, msgs, COLOR.RESET);
28
+ }
29
+ };
30
+ }
package/src/reload.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { RspackOptionsNormalized } from "@rspack/core";
2
+
3
+ interface Status {
4
+ isUnloading: boolean;
5
+ currentHash: string;
6
+ previousHash: string[];
7
+ }
8
+
9
+ export function reloadApp(
10
+ { liveReload, hot }: RspackOptionsNormalized["devServer"],
11
+ status: Status
12
+ ) {
13
+ if (status.isUnloading) {
14
+ return;
15
+ }
16
+
17
+ function applyReload(rootWindow: Window, intervalId: number) {
18
+ clearInterval(intervalId);
19
+ console.log("App update, Reloading...");
20
+ rootWindow.location.reload();
21
+ }
22
+
23
+ if (liveReload) {
24
+ let rootWindow = self;
25
+ const intervalId = self.setInterval(() => {
26
+ if (rootWindow.location.protocol !== "about:") {
27
+ applyReload(rootWindow, intervalId);
28
+ }
29
+ });
30
+ }
31
+ }
package/src/server.ts ADDED
@@ -0,0 +1,273 @@
1
+ import type { Compiler, Dev, RspackOptionsNormalized } from "@rspack/core";
2
+ import type { Logger } from "./logger";
3
+ import type { Socket } from "net";
4
+ import type { FSWatcher, WatchOptions } from "chokidar";
5
+ import type { WebSocketServer, ClientConnection } from "./ws";
6
+ import type { RspackDevMiddleware } from "@rspack/dev-middleware";
7
+ import type {
8
+ Application,
9
+ RequestHandler as ExpressRequestHandler,
10
+ ErrorRequestHandler as ExpressErrorRequestHandler
11
+ } from "express";
12
+ import type { Server } from "http";
13
+ import type { ResolvedDev } from "./config";
14
+
15
+ import chokidar from "chokidar";
16
+ import http from "http";
17
+ import { createLogger } from "./logger";
18
+ import WebpackDevServer from "webpack-dev-server";
19
+ import express from "express";
20
+ import { rdm } from "@rspack/dev-middleware";
21
+ import { createWebsocketServer } from "./ws";
22
+ import { resolveDevOptions } from "./config";
23
+
24
+ interface Middleware {
25
+ name?: string;
26
+ path?: string;
27
+ middleware: ExpressErrorRequestHandler | ExpressRequestHandler;
28
+ }
29
+ interface Listener {
30
+ name: string | Symbol;
31
+ listener: (...args: any) => void;
32
+ }
33
+ type Host = "local-ip" | "local-ipv4" | "local-ipv6" | string;
34
+ type Port = number | string | "auto";
35
+
36
+ // copy from webpack-dev-server
37
+ export class RspackDevServer {
38
+ options: ResolvedDev;
39
+ logger: Logger;
40
+ staticWatchers: FSWatcher[];
41
+ sockets: Socket[];
42
+ app: Application;
43
+ server: Server;
44
+ private listeners: Listener[];
45
+ private currentHash: string;
46
+ private middleware: RspackDevMiddleware | undefined;
47
+ // TODO: now only support 'ws'
48
+ webSocketServer: WebSocketServer | undefined;
49
+
50
+ constructor(public compiler: Compiler) {
51
+ this.logger = createLogger("rspack-dev-server");
52
+ this.staticWatchers = [];
53
+ this.listeners = [];
54
+ this.sockets = [];
55
+ this.currentHash = "";
56
+ this.options = this.normalizeOptions(compiler.options.devServer);
57
+ this.addAdditionEntires();
58
+ }
59
+
60
+ normalizeOptions(dev: Dev = {}) {
61
+ return resolveDevOptions(dev, this.compiler.options);
62
+ }
63
+
64
+ addAdditionEntires() {
65
+ const entries: string[] = [];
66
+
67
+ if (this.options.hot) {
68
+ const hotUpdateEntryPath = require.resolve(
69
+ "@rspack/dev-client/devServer"
70
+ );
71
+
72
+ entries.push(hotUpdateEntryPath);
73
+
74
+ if (this.compiler.options.builtins.react?.refresh) {
75
+ const reactRefreshEntryPath = require.resolve(
76
+ "@rspack/dev-client/react-refresh"
77
+ );
78
+ entries.push(reactRefreshEntryPath);
79
+ }
80
+ }
81
+
82
+ const devClientEntryPath = require.resolve("@rspack/dev-client");
83
+ entries.push(devClientEntryPath);
84
+ for (const key in this.compiler.options.entry) {
85
+ this.compiler.options.entry[key].unshift(...entries);
86
+ }
87
+ }
88
+
89
+ static isAbsoluteURL(URL: string): boolean {
90
+ return WebpackDevServer.isAbsoluteURL(URL);
91
+ }
92
+
93
+ static findIp(gateway: string): string | undefined {
94
+ return WebpackDevServer.findIp(gateway);
95
+ }
96
+
97
+ static async internalIP(family: "v6" | "v4"): Promise<string | undefined> {
98
+ return WebpackDevServer.internalIP(family);
99
+ }
100
+
101
+ static async internalIPSync(
102
+ family: "v6" | "v4"
103
+ ): Promise<string | undefined> {
104
+ return WebpackDevServer.internalIPSync(family);
105
+ }
106
+
107
+ static async getHostname(hostname: Host): Promise<string> {
108
+ return WebpackDevServer.getHostname(hostname);
109
+ }
110
+
111
+ static async getFreePort(port: Port, host: string): Promise<string | number> {
112
+ return WebpackDevServer.getFreePort(port, host);
113
+ }
114
+
115
+ static findCacheDir(): string {
116
+ // TODO: we need remove the `webpack-dev-server` tag in WebpackDevServer;
117
+ return "";
118
+ }
119
+
120
+ private getCompilerOptions(): RspackOptionsNormalized {
121
+ return this.compiler.options;
122
+ }
123
+
124
+ sendMessage(
125
+ clients: ClientConnection[],
126
+ type: string,
127
+ data?: any,
128
+ params?: any
129
+ ) {
130
+ for (const client of clients) {
131
+ if (client.readyState === 1) {
132
+ client.send(JSON.stringify({ type, data, params }));
133
+ }
134
+ }
135
+ }
136
+
137
+ watchFiles(watchPath: string | string[], watchOptions?: WatchOptions): void {
138
+ const watcher = chokidar.watch(watchPath, watchOptions);
139
+
140
+ // disabling refreshing on changing the content
141
+ if (this.options.liveReload) {
142
+ // TODO: remove this after we had memory filesystem
143
+ if (this.options.hot) {
144
+ return;
145
+ }
146
+
147
+ watcher.on("change", item => {
148
+ if (this.webSocketServer) {
149
+ this.sendMessage(
150
+ this.webSocketServer.clients,
151
+ "static-changed",
152
+ item
153
+ );
154
+ }
155
+ });
156
+ }
157
+
158
+ this.staticWatchers.push(watcher);
159
+ }
160
+
161
+ invalidate(callback = () => {}): void {
162
+ if (this.middleware) {
163
+ this.middleware.invalidate(callback);
164
+ }
165
+ }
166
+
167
+ async start(): Promise<void> {
168
+ this.setupApp();
169
+ this.createServer();
170
+ this.setupWatchStaticFiles();
171
+ this.createWebsocketServer();
172
+ this.setupDevMiddleware();
173
+ this.setupMiddlewares();
174
+ await new Promise(resolve =>
175
+ this.server.listen(
176
+ {
177
+ port: this.options.port,
178
+ host: "localhost"
179
+ },
180
+ () => {
181
+ console.log(`begin server at http://localhost:${this.options.port}`);
182
+ resolve({});
183
+ }
184
+ )
185
+ );
186
+ }
187
+
188
+ startCallback(callback?: (err?: Error) => void): void {
189
+ throw new Error("Method not implemented.");
190
+ }
191
+ stopCallback(callback?: (err?: Error) => void): void {
192
+ throw new Error("Method not implemented.");
193
+ }
194
+ listen(port: Port, hostname: string, fn: (err?: Error) => void): void {
195
+ throw new Error("Method not implemented.");
196
+ }
197
+ close(callback?: (err?: Error) => void): void {
198
+ throw new Error("Method not implemented.");
199
+ }
200
+
201
+ async stop(): Promise<void> {
202
+ await Promise.all(this.staticWatchers.map(watcher => watcher.close()));
203
+ this.middleware = null;
204
+ this.staticWatchers = [];
205
+ if (this.server) {
206
+ this.server.close();
207
+ }
208
+ if (this.webSocketServer) {
209
+ await new Promise(resolve => {
210
+ this.webSocketServer;
211
+ });
212
+ }
213
+ }
214
+
215
+ private setupApp() {
216
+ this.app = express();
217
+ }
218
+
219
+ private setupWatchStaticFiles() {
220
+ if (this.options.static.watch === false) {
221
+ return;
222
+ }
223
+ this.watchFiles(this.options.static.directory, this.options.static.watch);
224
+ }
225
+
226
+ private setupDevMiddleware() {
227
+ this.middleware = rdm(
228
+ this.compiler,
229
+ this.options.devMiddleware,
230
+ this.webSocketServer
231
+ );
232
+ }
233
+
234
+ private createWebsocketServer() {
235
+ if (this.options.webSocketServer !== false) {
236
+ this.webSocketServer = createWebsocketServer(this);
237
+ }
238
+ }
239
+
240
+ private setupMiddlewares() {
241
+ const middlewares: Middleware[] = [];
242
+ middlewares.push({
243
+ name: "rdm",
244
+ middleware: this.middleware
245
+ });
246
+
247
+ // Todo Add options
248
+ const connectHistoryApiFallback = require("connect-history-api-fallback");
249
+ middlewares.push({
250
+ name: "[connect-history-api-fallback]",
251
+ middleware: connectHistoryApiFallback({
252
+ verbose: true,
253
+ logger: console.log.bind(console)
254
+ })
255
+ });
256
+ middlewares.push({
257
+ name: "express-static",
258
+ middleware: express.static(this.options.static.directory)
259
+ });
260
+
261
+ middlewares.forEach(m => {
262
+ if (m.path) {
263
+ this.app.use(m.path, m.middleware);
264
+ } else {
265
+ this.app.use(m.middleware);
266
+ }
267
+ });
268
+ }
269
+
270
+ private createServer() {
271
+ this.server = http.createServer(this.app);
272
+ }
273
+ }
package/src/ws.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { RspackDevServer } from "./server";
2
+ import WebSocket from "ws";
3
+ import WebpackWsServer from "webpack-dev-server/lib/servers/WebsocketServer";
4
+
5
+ export type ClientConnection = WebSocket & { isAlive?: boolean };
6
+
7
+ export interface WebSocketServer {
8
+ heartbeatInterval: number;
9
+ implementation: WebSocket.Server<WebSocket.WebSocket>;
10
+ server: RspackDevServer;
11
+ clients: ClientConnection[];
12
+ }
13
+
14
+ export function createWebsocketServer(
15
+ server: RspackDevServer
16
+ ): WebSocketServer {
17
+ return new WebpackWsServer(server);
18
+ }
@@ -0,0 +1,52 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`normalize options snapshot additional entires should added 1`] = `
4
+ {
5
+ "main": [
6
+ "<prefix>/rspack-dev-client/dist/devServer.js",
7
+ "<prefix>/rspack-dev-client/dist/index.js",
8
+ "<prefix>/something",
9
+ ],
10
+ }
11
+ `;
12
+
13
+ exports[`normalize options snapshot no options 1`] = `
14
+ {
15
+ "devMiddleware": {},
16
+ "hot": true,
17
+ "liveReload": true,
18
+ "open": true,
19
+ "port": 8080,
20
+ "static": {
21
+ "directory": "<PROJECT_ROOT>/dist",
22
+ "watch": {},
23
+ },
24
+ "webSocketServer": {},
25
+ }
26
+ `;
27
+
28
+ exports[`normalize options snapshot port string 1`] = `
29
+ {
30
+ "devMiddleware": {},
31
+ "hot": true,
32
+ "liveReload": true,
33
+ "open": true,
34
+ "port": 9000,
35
+ "static": {
36
+ "directory": "<PROJECT_ROOT>/dist",
37
+ "watch": {},
38
+ },
39
+ "webSocketServer": {},
40
+ }
41
+ `;
42
+
43
+ exports[`normalize options snapshot react-refresh added when react/refresh enabled 1`] = `
44
+ {
45
+ "main": [
46
+ "<prefix>/rspack-dev-client/dist/devServer.js",
47
+ "<prefix>/rspack-dev-client/dist/reactRefresh.js",
48
+ "<prefix>/rspack-dev-client/dist/index.js",
49
+ "<prefix>/something",
50
+ ],
51
+ }
52
+ `;
@@ -0,0 +1,60 @@
1
+ import type { RspackOptions } from "@rspack/core";
2
+ import { RspackDevServer } from "@rspack/dev-server";
3
+ import { createCompiler } from "@rspack/core";
4
+ import serializer from "jest-serializer-path";
5
+ import os from "os";
6
+ expect.addSnapshotSerializer(serializer);
7
+
8
+ describe("normalize options snapshot", () => {
9
+ it("no options", () => {
10
+ match({});
11
+ });
12
+
13
+ it("port string", () => {
14
+ match({
15
+ devServer: {
16
+ port: "9000"
17
+ }
18
+ });
19
+ });
20
+
21
+ it("additional entires should added", () => {
22
+ matchAdditionEntries({
23
+ entry: ["something"]
24
+ });
25
+ });
26
+
27
+ it("react-refresh added when react/refresh enabled", () => {
28
+ matchAdditionEntries({
29
+ entry: ["something"],
30
+ builtins: {
31
+ react: {
32
+ refresh: true
33
+ }
34
+ }
35
+ });
36
+ });
37
+ });
38
+
39
+ function match(config: RspackOptions) {
40
+ const compiler = createCompiler(config);
41
+ const server = new RspackDevServer(compiler);
42
+ expect(server.options).toMatchSnapshot();
43
+ }
44
+
45
+ function matchAdditionEntries(config: RspackOptions) {
46
+ const compiler = createCompiler(config);
47
+ const server = new RspackDevServer(compiler);
48
+ const entires = Object.entries(compiler.options.entry);
49
+ // some hack for snapshot
50
+ const value = Object.fromEntries(
51
+ entires.map(([key, item]) => {
52
+ const replaced = item.map(entry => {
53
+ const array = entry.replace(/\\/g, "/").split("/");
54
+ return "<prefix>" + "/" + array.slice(-3).join("/");
55
+ });
56
+ return [key, replaced];
57
+ })
58
+ );
59
+ expect(value).toMatchSnapshot();
60
+ }
package/tsconfig.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": [
4
+ "src"
5
+ ],
2
6
  "compilerOptions": {
3
- "module": "CommonJS",
4
- "target": "ES2018",
5
- "moduleResolution": "node",
6
- "esModuleInterop": true,
7
7
  "outDir": "dist",
8
- "declaration": true
8
+ "rootDir": "src"
9
9
  },
10
- "include": [
11
- "src"
10
+ "references": [
11
+ {
12
+ "path": "../rspack-dev-middleware"
13
+ }
12
14
  ]
13
15
  }