Skip to content

Commit 85e19ee

Browse files
authored
feat(core): support environment.hot.send API (#7475)
1 parent b2b0b1e commit 85e19ee

File tree

7 files changed

+180
-8
lines changed

7 files changed

+180
-8
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { expect, test } from '@e2e/helper';
2+
import { createRsbuild } from '@rsbuild/core';
3+
4+
test('should send HMR messages to the matched environment only', async ({
5+
page,
6+
context,
7+
}) => {
8+
const rsbuild = await createRsbuild({
9+
cwd: import.meta.dirname,
10+
config: {
11+
environments: {
12+
web: {},
13+
web1: {
14+
dev: {
15+
assetPrefix: 'auto',
16+
},
17+
source: {
18+
entry: {
19+
main: './src/web1.ts',
20+
},
21+
},
22+
output: {
23+
distPath: {
24+
root: 'dist/web1',
25+
html: 'html1',
26+
},
27+
},
28+
},
29+
},
30+
},
31+
});
32+
33+
const server = await rsbuild.createDevServer();
34+
35+
await server.listen();
36+
const web1Page = await context.newPage();
37+
38+
await page.goto(`http://localhost:${server.port}`);
39+
await web1Page.goto(`http://localhost:${server.port}/web1/html1/main`);
40+
41+
await expect(page.locator('#title')).toHaveText('web');
42+
await expect(web1Page.locator('#title')).toHaveText('web1');
43+
await expect(page.locator('#count')).toHaveText('0');
44+
await expect(web1Page.locator('#count')).toHaveText('0');
45+
46+
server.environments.web.hot.send('custom', {
47+
event: 'count',
48+
});
49+
50+
await expect(page.locator('#count')).toHaveText('1');
51+
await expect(web1Page.locator('#count')).toHaveText('0');
52+
53+
server.environments.web1.hot.send('custom', {
54+
event: 'count',
55+
});
56+
57+
await expect(page.locator('#count')).toHaveText('1');
58+
await expect(web1Page.locator('#count')).toHaveText('1');
59+
60+
await web1Page.close();
61+
await server.close();
62+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference types="@rsbuild/core/types" />
2+
3+
const titleEl = document.createElement('div');
4+
titleEl.id = 'title';
5+
titleEl.innerText = 'web';
6+
7+
const countEl = document.createElement('div');
8+
countEl.id = 'count';
9+
countEl.innerText = '0';
10+
11+
document.body.append(titleEl, countEl);
12+
13+
if (import.meta.webpackHot) {
14+
import.meta.webpackHot.on('count', () => {
15+
countEl.innerText = String(Number(countEl.innerText) + 1);
16+
});
17+
18+
import.meta.webpackHot.accept();
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference types="@rsbuild/core/types" />
2+
3+
const titleEl = document.createElement('div');
4+
titleEl.id = 'title';
5+
titleEl.innerText = 'web1';
6+
7+
const countEl = document.createElement('div');
8+
countEl.id = 'count';
9+
countEl.innerText = '0';
10+
11+
document.body.append(titleEl, countEl);
12+
13+
if (import.meta.webpackHot) {
14+
import.meta.webpackHot.on('count', () => {
15+
countEl.innerText = String(Number(countEl.innerText) + 1);
16+
});
17+
18+
import.meta.webpackHot.accept();
19+
}

packages/core/src/server/devServer.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type ExtractSocketMessageData<T extends ServerMessage['type']> =
5555
? Extract<ServerMessage, { type: T }>['data']
5656
: undefined;
5757

58-
export type SockWrite = <T extends ServerMessage['type']>(
58+
export type HotSend = <T extends ServerMessage['type']>(
5959
type: T,
6060
data?: ExtractSocketMessageData<T>,
6161
) => void;
@@ -87,7 +87,7 @@ export type RsbuildDevServer = RsbuildServerBase & {
8787
* - `static-changed`: Alias of `full-reload` for backward compatibility.
8888
* - `custom`: Send custom messages via `custom` type with optional data to the browser and handle them via HMR events.
8989
*/
90-
sockWrite: SockWrite;
90+
sockWrite: HotSend;
9191
};
9292

9393
export async function createDevServer<
@@ -275,6 +275,16 @@ export async function createDevServer<
275275
);
276276

277277
const environmentAPI: EnvironmentAPI = {};
278+
const createHotSend =
279+
(token?: string): HotSend =>
280+
(type, data) =>
281+
state.buildManager?.socketServer.sockWrite(
282+
{
283+
type,
284+
data,
285+
} as ServerMessage,
286+
token,
287+
);
278288

279289
const getErrorMsg = (method: string) =>
280290
`${color.dim('[rsbuild:server]')} Can not call ` +
@@ -284,6 +294,9 @@ export async function createDevServer<
284294
context.environmentList.forEach((environment, index) => {
285295
environmentAPI[environment.name] = {
286296
context: environment,
297+
hot: {
298+
send: createHotSend(environment.webSocketToken),
299+
},
287300
getStats: async () => {
288301
if (!state.buildManager) {
289302
throw new Error(getErrorMsg('getStats'));
@@ -326,11 +339,7 @@ export async function createDevServer<
326339
middlewares,
327340
});
328341

329-
const sockWrite: SockWrite = (type, data) =>
330-
state.buildManager?.socketServer.sockWrite({
331-
type,
332-
data,
333-
} as ServerMessage);
342+
const sockWrite = createHotSend();
334343

335344
const devServer: RsbuildDevServer = {
336345
port,

packages/core/src/types/config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type {
2020
} from 'http-proxy-middleware';
2121
import type { RspackChain } from 'rspack-chain';
2222
import type { FileDescriptor } from 'rspack-manifest-plugin';
23-
import type { RsbuildDevServer } from '../server/devServer';
23+
import type { HotSend, RsbuildDevServer } from '../server/devServer';
2424
import type { RsbuildPreviewServer } from '../server/previewServer';
2525
import type { Logger } from '../logger';
2626
import type {
@@ -1810,6 +1810,13 @@ export type EnvironmentAPI = Record<
18101810
*/
18111811
getTransformedHtml: (entryName: string) => Promise<string>;
18121812

1813+
/**
1814+
* Send HMR message to the current environment only.
1815+
*/
1816+
hot: {
1817+
send: HotSend;
1818+
};
1819+
18131820
/**
18141821
* Provides some context information about the current environment
18151822
*/

website/docs/en/api/javascript-api/environment-api.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ type EnvironmentAPI = {
228228
getStats: () => Promise<Stats>;
229229
loadBundle: <T = unknown>(entryName: string) => Promise<T>;
230230
getTransformedHtml: (entryName: string) => Promise<string>;
231+
hot: {
232+
send: SockWrite;
233+
};
231234
};
232235
};
233236
```
@@ -304,3 +307,28 @@ const html = await environments.web.getTransformedHtml('main');
304307
```
305308

306309
This method returns the complete HTML string, including all resources and content injected through HTML plugins.
310+
311+
### hot.send
312+
313+
Send an HMR message to the client of the current environment only.
314+
315+
This works the same as [server.sockWrite](/api/javascript-api/server-api#sockwrite), but it only affects the matched environment.
316+
317+
- **Type:**
318+
319+
```ts
320+
type SockWrite = {
321+
(type: 'full-reload', data?: { path?: string }): void;
322+
(type: 'static-changed'): void;
323+
(type: 'custom', data: { event: string; data?: any }): void;
324+
};
325+
```
326+
327+
- **Example:**
328+
329+
```ts
330+
environments.web.hot.send('custom', {
331+
event: 'count',
332+
data: { value: 1 },
333+
});
334+
```

website/docs/zh/api/javascript-api/environment-api.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ type EnvironmentAPI = {
229229
getStats: () => Promise<Stats>;
230230
loadBundle: <T = unknown>(entryName: string) => Promise<T>;
231231
getTransformedHtml: (entryName: string) => Promise<string>;
232+
hot: {
233+
send: SockWrite;
234+
};
232235
};
233236
};
234237
```
@@ -305,3 +308,28 @@ const html = await environments.web.getTransformedHtml('main');
305308
```
306309

307310
该方法会返回完整的 HTML 字符串,包含了所有通过 HTML 插件注入的资源和内容。
311+
312+
### hot.send
313+
314+
向当前 environment 对应的客户端发送 HMR 消息。
315+
316+
它和 [server.sockWrite](/api/javascript-api/server-api#sockwrite) 的行为一致,区别是只会影响当前匹配的 environment。
317+
318+
- **类型:**
319+
320+
```ts
321+
type SockWrite = {
322+
(type: 'full-reload', data?: { path?: string }): void;
323+
(type: 'static-changed'): void;
324+
(type: 'custom', data: { event: string; data?: any }): void;
325+
};
326+
```
327+
328+
- **示例:**
329+
330+
```ts
331+
environments.web.hot.send('custom', {
332+
event: 'count',
333+
data: { value: 1 },
334+
});
335+
```

0 commit comments

Comments
 (0)