Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions packages/core/src/client/hmr.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { SocketMessage } from '../server/socketServer';
import type {
ClientMessage,
ClientMessageRuntimeError,
SocketMessage,
} from '../server/socketServer';
import type { NormalizedClientConfig } from '../types';

const config: NormalizedClientConfig = RSBUILD_CLIENT_CONFIG;
Expand Down Expand Up @@ -150,6 +154,13 @@ let socket: WebSocket | null = null;
let reconnectCount = 0;
let pingIntervalId: ReturnType<typeof setInterval>;

const isSocketReady = () => socket && socket.readyState === socket.OPEN;
const socketSend = (data: ClientMessage) => {
if (isSocketReady()) {
socket!.send(JSON.stringify(data));
}
};

function onOpen() {
// Notify users that the WebSocket has successfully connected.
console.info('[rsbuild] WebSocket connected.');
Expand All @@ -160,10 +171,15 @@ function onOpen() {
// To prevent WebSocket timeouts caused by proxies (e.g., nginx, docker),
// send a periodic ping message to keep the connection alive.
pingIntervalId = setInterval(() => {
if (socket && socket.readyState === socket.OPEN) {
socket.send(JSON.stringify({ type: 'ping' }));
}
socketSend({ type: 'ping' });
}, 30000);

if (errorMessages.length) {
errorMessages.forEach((message) => {
socketSend(message);
});
errorMessages.length = 0;
}
}

function onMessage(e: MessageEvent<string>) {
Expand Down Expand Up @@ -214,7 +230,7 @@ function onClose() {
setTimeout(connect, 1000 * 1.5 ** reconnectCount);
}

function onError() {
function onSocketError() {
if (formatURL() !== formatURL(true)) {
console.error(
'[rsbuild] WebSocket connection failed. Trying direct connection fallback.',
Expand All @@ -225,6 +241,20 @@ function onError() {
}
}

const errorMessages: ClientMessageRuntimeError[] = [];

function onRuntimeError(event: ErrorEvent) {
const message: ClientMessageRuntimeError = {
type: 'runtime-error',
message: event.message,
};
if (isSocketReady()) {
socketSend(message);
} else {
errorMessages.push(message);
}
}

// Establishing a WebSocket connection with the server.
function connect(fallback = false) {
if (reconnectCount === 0) {
Expand All @@ -240,7 +270,7 @@ function connect(fallback = false) {
socket.addEventListener('message', onMessage);
// Handle errors
if (!fallback) {
socket.addEventListener('error', onError);
socket.addEventListener('error', onSocketError);
}
}

Expand All @@ -250,7 +280,7 @@ function removeListeners() {
socket.removeEventListener('open', onOpen);
socket.removeEventListener('close', onClose);
socket.removeEventListener('message', onMessage);
socket.removeEventListener('error', onError);
socket.removeEventListener('error', onSocketError);
}
}

Expand All @@ -260,4 +290,8 @@ function reloadPage() {
}
}

if (typeof window !== 'undefined') {
window.addEventListener('error', onRuntimeError);
}

connect();
28 changes: 28 additions & 0 deletions packages/core/src/server/socketServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IncomingMessage } from 'node:http';
import type { Socket } from 'node:net';
import type Ws from '../../compiled/ws/index.js';
import {
color,
getAllStatsErrors,
getAllStatsWarnings,
getStatsOptions,
Expand Down Expand Up @@ -44,13 +45,24 @@ export type SocketMessageErrors = {
data: { text: string[]; html: string };
};

export type ClientMessageRuntimeError = {
type: 'runtime-error';
message: string;
};

export type ClientMessagePing = {
type: 'ping';
};

export type SocketMessage =
| SocketMessageOk
| SocketMessageStaticChanged
| SocketMessageHash
| SocketMessageWarnings
| SocketMessageErrors;

export type ClientMessage = ClientMessagePing | ClientMessageRuntimeError;

const parseQueryString = (req: IncomingMessage) => {
const queryStr = req.url ? req.url.split('?')[1] : '';
return queryStr ? Object.fromEntries(new URLSearchParams(queryStr)) : {};
Expand Down Expand Up @@ -247,6 +259,22 @@ export class SocketServer {
socket.isAlive = true;
});

socket.on('message', (data) => {
try {
const message: ClientMessage = JSON.parse(
// eslint-disable-next-line @typescript-eslint/no-base-to-string
typeof data === 'string' ? data : data.toString(),
);

// Handle runtime errors from browser
if (message.type === 'runtime-error') {
logger.error(
`${color.cyan('[browser]')} ${color.red(message.message)}`,
);
}
} catch {}
});

let sockets = this.socketsMap.get(token);
if (!sockets) {
sockets = new Set();
Expand Down
Loading