Skip to content

Commit 7bf5b45

Browse files
authored
feat!: replace express with connect as default server (#143)
1 parent 23f4389 commit 7bf5b45

7 files changed

Lines changed: 79 additions & 62 deletions

File tree

docs/migrate-v1-to-v2.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,25 @@ The minimum supported Node.js version is now `^20.19.0 || >=22.12.0`.
5757

5858
> Refer to the [http-proxy-middleware v3 migration guide](https://github.com/chimurai/http-proxy-middleware/blob/master/MIGRATION.md) for details.
5959
60-
### Upgraded Express to v5
60+
### Default app now uses `connect`
6161

62-
`express` has been updated to v5, see [Introducing Express v5: A New Era for the Node.js Framework](https://expressjs.com/2024/10/15/v5-release.html) for details.
62+
When `devServer.app` is omitted, `@rspack/dev-server` now creates a
63+
`connect` app instead of an Express app.
64+
65+
The dev server only needs a minimal middleware pipeline. Connect provides the same `(req, res, next)` interface as Express while being significantly smaller and having fewer dependencies, making it a better fit for this use case.
66+
67+
If you relied on Express-only APIs on `devServer.app`, such as `app.get()`,
68+
`app.post()`, or `res.send()`, provide your own Express app explicitly:
69+
70+
```js
71+
import express from 'express';
72+
73+
export default {
74+
devServer: {
75+
app: async () => express(),
76+
},
77+
};
78+
```
6379

6480
### Removed `spdy` support
6581

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@
4545
]
4646
},
4747
"dependencies": {
48+
"@types/connect": "^3.4.38",
4849
"@types/connect-history-api-fallback": "^1.5.4",
4950
"@types/serve-index": "^1.9.4",
5051
"@types/serve-static": "^2.2.0",
5152
"@types/ws": "^8.18.1",
5253
"chokidar": "^3.6.0",
54+
"connect": "^3.7.0",
5355
"connect-history-api-fallback": "^2.0.0",
54-
"express": "^5.2.1",
5556
"http-proxy-middleware": "^3.0.5",
5657
"ipaddr.js": "^2.3.0",
58+
"serve-static": "^2.2.1",
5759
"serve-index": "^1.9.2",
5860
"webpack-dev-middleware": "^7.4.5",
5961
"ws": "^8.19.0"
@@ -66,14 +68,13 @@
6668
"@rspack/plugin-react-refresh": "1.6.1",
6769
"@rstest/core": "^0.9.0",
6870
"@types/compression": "^1.8.1",
69-
"@types/express": "5.0.6",
7071
"@types/mime-types": "3.0.1",
7172
"@types/node": "^24.12.0",
7273
"@types/node-forge": "^1.3.14",
7374
"@types/trusted-types": "^2.0.7",
7475
"@types/ws": "8.18.1",
76+
"express": "^5.2.1",
7577
"compression": "^1.8.1",
76-
"connect": "^3.7.0",
7778
"cross-env": "^10.1.0",
7879
"css-loader": "^7.1.4",
7980
"hono": "^4.12.5",

pnpm-lock.yaml

Lines changed: 12 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rslib.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ export default defineConfig({
88
dts: true,
99
output: {
1010
externals: {
11-
express: 'commonjs express',
11+
connect: 'commonjs connect',
1212
'connect-history-api-fallback':
1313
'commonjs connect-history-api-fallback',
1414
'webpack-dev-middleware': 'commonjs webpack-dev-middleware',
1515
'http-proxy-middleware': 'commonjs http-proxy-middleware',
16+
'serve-static': 'commonjs serve-static',
1617
'serve-index': 'commonjs serve-index',
1718
selfsigned: 'commonjs selfsigned',
1819
compression: 'commonjs compression',

src/server.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import type {
2828
DevMiddlewareOptions,
2929
DevServer,
3030
EXPECTED_ANY,
31-
ExpressApplication,
3231
FSWatcher,
3332
HTTPServer,
3433
HandleFunction,
@@ -74,12 +73,13 @@ import type {
7473
WebSocketServerImplementation,
7574
WebSocketURL,
7675
} from './types';
76+
import type { ConnectApplication } from './types';
7777

7878
const { styleText } = util;
7979
const require = createRequire(import.meta.url);
8080

8181
export interface Configuration<
82-
A extends BasicApplication = ExpressApplication,
82+
A extends BasicApplication = ConnectApplication,
8383
S extends HTTPServer = HTTPServer,
8484
> {
8585
ipc?: boolean | string;
@@ -142,7 +142,8 @@ const memoize = <T>(fn: FunctionReturning<T>): FunctionReturning<T> => {
142142
};
143143
};
144144

145-
const getExpress = memoize(() => require('express'));
145+
const getConnect = memoize(() => require('connect'));
146+
const getServeStatic = memoize(() => require('serve-static'));
146147

147148
const encodeOverlaySettings = (
148149
setting?: OverlayMessageOptions,
@@ -184,7 +185,7 @@ function isMultiCompiler(
184185
}
185186

186187
class Server<
187-
A extends BasicApplication = ExpressApplication,
188+
A extends BasicApplication = ConnectApplication,
188189
S extends import('http').Server = HTTPServer,
189190
> {
190191
compiler: Compiler | MultiCompiler;
@@ -1519,7 +1520,7 @@ class Server<
15191520
this.app = (
15201521
typeof this.options.app === 'function'
15211522
? await this.options.app()
1522-
: getExpress()()
1523+
: getConnect()()
15231524
) as A;
15241525
}
15251526

@@ -1901,9 +1902,9 @@ class Server<
19011902
for (const staticOption of staticOptions) {
19021903
for (const publicPath of staticOption.publicPath) {
19031904
middlewares.push({
1904-
name: 'express-static',
1905+
name: 'serve-static',
19051906
path: publicPath,
1906-
middleware: getExpress().static(
1907+
middleware: getServeStatic()(
19071908
staticOption.directory,
19081909
staticOption.staticOptions,
19091910
),
@@ -1947,9 +1948,9 @@ class Server<
19471948
for (const staticOption of staticOptions) {
19481949
for (const publicPath of staticOption.publicPath) {
19491950
middlewares.push({
1950-
name: 'express-static',
1951+
name: 'serve-static',
19511952
path: publicPath,
1952-
middleware: getExpress().static(
1953+
middleware: getServeStatic()(
19531954
staticOption.directory,
19541955
staticOption.staticOptions,
19551956
),
@@ -2021,11 +2022,11 @@ class Server<
20212022

20222023
for (const i of middlewares) {
20232024
if (i.name === 'webpack-dev-middleware') {
2024-
const item = i as MiddlewareObject<A> | RequestHandler;
2025+
const item = i as MiddlewareObject | RequestHandler;
20252026

2026-
if (typeof (item as MiddlewareObject<A>).middleware === 'undefined') {
2027-
(item as MiddlewareObject<A>).middleware =
2028-
lazyInitDevMiddleware() as unknown as MiddlewareHandler<A>;
2027+
if (typeof (item as MiddlewareObject).middleware === 'undefined') {
2028+
(item as MiddlewareObject).middleware =
2029+
lazyInitDevMiddleware() as unknown as MiddlewareHandler;
20292030
}
20302031
}
20312032
}

src/types.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,13 @@ export type {
1818
} from '@rspack/core';
1919
import type { FSWatcher, WatchOptions } from 'chokidar';
2020
export type { FSWatcher, WatchOptions };
21+
import type {
22+
Server as ConnectApplication,
23+
IncomingMessage as ConnectIncomingMessage,
24+
} from 'connect';
25+
export type { ConnectApplication };
2126
import type { Options as ConnectHistoryApiFallbackOptions } from 'connect-history-api-fallback';
2227
export type { ConnectHistoryApiFallbackOptions };
23-
import type {
24-
Application as ExpressApplication,
25-
ErrorRequestHandler as ExpressErrorRequestHandler,
26-
Request as ExpressRequest,
27-
RequestHandler as ExpressRequestHandler,
28-
Response as ExpressResponse,
29-
} from 'express';
30-
31-
export type { ExpressApplication };
3228
import type {
3329
Options as HttpProxyMiddlewareOptions,
3430
Filter as HttpProxyMiddlewareOptionsFilter,
@@ -70,10 +66,9 @@ export type HandleFunction =
7066
export type ServerOptions = import('https').ServerOptions;
7167

7268
// type-level helpers, inferred as util types
73-
export type Request<T extends BasicApplication = ExpressApplication> =
74-
T extends ExpressApplication ? ExpressRequest : IncomingMessage;
75-
export type Response<T extends BasicApplication = ExpressApplication> =
76-
T extends ExpressApplication ? ExpressResponse : ServerResponse;
69+
export type Request<T extends BasicApplication = ConnectApplication> =
70+
T extends ConnectApplication ? ConnectIncomingMessage : IncomingMessage;
71+
export type Response = ServerResponse;
7772

7873
export type DevMiddlewareOptions<
7974
T extends Request,
@@ -122,14 +117,14 @@ export interface NormalizedStatic {
122117
}
123118

124119
export type ServerType<
125-
A extends BasicApplication = ExpressApplication,
120+
A extends BasicApplication = ConnectApplication,
126121
S extends import('http').Server = import('http').Server,
127122
> =
128123
| LiteralUnion<'http' | 'https' | 'http2', string>
129124
| ((serverOptions: ServerOptions, application: A) => S);
130125

131126
export interface ServerConfiguration<
132-
A extends BasicApplication = ExpressApplication,
127+
A extends BasicApplication = ConnectApplication,
133128
S extends import('http').Server = import('http').Server,
134129
> {
135130
type?: ServerType<A, S>;
@@ -210,28 +205,21 @@ export type Headers =
210205
| Array<{ key: string; value: string }>
211206
| Record<string, string | string[]>;
212207

213-
export type MiddlewareHandler<T extends BasicApplication = ExpressApplication> =
214-
T extends ExpressApplication
215-
? ExpressRequestHandler | ExpressErrorRequestHandler
216-
: HandleFunction;
208+
export type MiddlewareHandler = (...args: EXPECTED_ANY[]) => EXPECTED_ANY;
217209

218-
export interface MiddlewareObject<
219-
T extends BasicApplication = ExpressApplication,
220-
> {
210+
export interface MiddlewareObject {
221211
name?: string;
222212
path?: string;
223-
middleware: MiddlewareHandler<T>;
213+
middleware: MiddlewareHandler;
224214
}
225215

226-
export type Middleware<T extends BasicApplication = ExpressApplication> =
227-
| MiddlewareObject<T>
228-
| MiddlewareHandler<T>;
216+
export type Middleware = MiddlewareObject | MiddlewareHandler;
229217

230218
export type BasicServer = import('net').Server | import('tls').Server;
231219

232220
export type OverlayMessageOptions = boolean | ((error: Error) => void);
233221

234-
// TypeScript overloads for express-like use
222+
// TypeScript overloads for connect-like use
235223
function useFn(fn: NextHandleFunction): BasicApplication;
236224
function useFn(fn: HandleFunction): BasicApplication;
237225
function useFn(route: string, fn: NextHandleFunction): BasicApplication;

tests/e2e/on-listening.test.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,20 @@ describe('onListening option', () => {
2424

2525
onListeningIsRunning = true;
2626

27-
devServer.app.get('/listening/some/path', (_, response) => {
28-
response.send('listening');
29-
});
30-
31-
devServer.app.post('/listening/some/path', (_, response) => {
32-
response.send('listening POST');
33-
});
27+
devServer.app.use(
28+
'/listening/some/path',
29+
(request, response, next) => {
30+
if (request.method !== 'GET' && request.method !== 'POST') {
31+
next();
32+
return;
33+
}
34+
35+
response.setHeader('Content-Type', 'text/html; charset=utf-8');
36+
response.end(
37+
request.method === 'POST' ? 'listening POST' : 'listening',
38+
);
39+
},
40+
);
3441
},
3542
port,
3643
},

0 commit comments

Comments
 (0)