Skip to content

Commit

Permalink
feat: add dev.client.reconnect option (#3333)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan committed Sep 1, 2024
1 parent 5ae0c59 commit 9a476f8
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 139 deletions.
121 changes: 35 additions & 86 deletions packages/core/src/client/hmr.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
/**
* This has been adapted from `create-react-app`, authored by Facebook, Inc.
* see: https://github.com/facebookincubator/create-react-app/tree/master/packages/react-dev-utils
*
* Tips: this package will be bundled and running in the browser, do not import any Node.js modules.
*/
import type { ClientConfig, Rspack } from '../types';
import type { NormalizedClientConfig, Rspack } from '../types';
import { formatStatsMessages } from './format';

const compilationName = RSBUILD_COMPILATION_NAME;
const compilationId = RSBUILD_COMPILATION_NAME;
const config: NormalizedClientConfig = RSBUILD_CLIENT_CONFIG;

function formatURL({
port,
Expand All @@ -26,7 +21,7 @@ function formatURL({
url.hostname = hostname;
url.protocol = protocol;
url.pathname = pathname;
url.searchParams.append('compilationName', compilationName);
url.searchParams.append('compilationId', compilationId);
return url.toString();
}

Expand All @@ -35,18 +30,6 @@ function formatURL({
return `${protocol}${colon}//${hostname}:${port}${pathname}`;
}

function getSocketUrl(urlParts: ClientConfig) {
const { location } = self;
const { host, port, path, protocol } = urlParts;

return formatURL({
protocol: protocol || (location.protocol === 'https:' ? 'wss' : 'ws'),
hostname: host || location.hostname,
port: port || location.port,
pathname: path || '/rsbuild-hmr',
});
}

// Remember some state related to hot module replacement.
let isFirstCompilation = true;
let lastCompilationHash: string | null = null;
Expand Down Expand Up @@ -120,7 +103,6 @@ function handleErrors(errors: Rspack.StatsError[]) {
isFirstCompilation = false;
hasCompileErrors = true;

// "Massage" webpack messages.
const formatted = formatStatsMessages({
errors,
warnings: [],
Expand All @@ -134,16 +116,11 @@ function handleErrors(errors: Rspack.StatsError[]) {
if (createOverlay) {
createOverlay(formatted.errors);
}

// Do not attempt to reload now.
// We will reload on next success instead.
}

function isUpdateAvailable() {
// __webpack_hash__ is the hash of the current compilation.
// It's a global variable injected by webpack / Rspack.
return lastCompilationHash !== __webpack_hash__;
}
// __webpack_hash__ is the hash of the current compilation.
// It's a global variable injected by Rspack.
const isUpdateAvailable = () => lastCompilationHash !== __webpack_hash__;

// Attempt to update code on the fly, fall back to a hard reload.
function tryApplyUpdates() {
Expand All @@ -158,21 +135,20 @@ function tryApplyUpdates() {
return;
}

// webpack disallows updates in other states.
// Rspack disallows updates in other states.
if (import.meta.webpackHot.status() !== 'idle') {
return;
}

function handleApplyUpdates(
const handleApplyUpdates = (
err: unknown,
updatedModules: (string | number)[] | null,
) {
) => {
const forcedReload = err || !updatedModules;
if (forcedReload) {
if (err) {
console.error('[HMR] Forced reload caused by: ', err);
}

reloadPage();
return;
}
Expand All @@ -181,22 +157,17 @@ function tryApplyUpdates() {
// While we were updating, there was a new update! Do it again.
tryApplyUpdates();
}
}
};

// https://rspack.dev/api/modules#importmetawebpackhot-webpack-specific
import.meta.webpackHot.check(true).then(
(updatedModules) => {
handleApplyUpdates(null, updatedModules);
},
(err) => {
handleApplyUpdates(err, null);
},
(updatedModules) => handleApplyUpdates(null, updatedModules),
(err) => handleApplyUpdates(err, null),
);
}

const MAX_RETRIES = 100;
let connection: WebSocket | null = null;
let retryCount = 0;
let reconnectCount = 0;

function onOpen() {
// Notify users that the HMR has successfully connected.
Expand All @@ -206,7 +177,7 @@ function onOpen() {
function onMessage(e: MessageEvent<string>) {
const message = JSON.parse(e.data);

if (message.compilationName && message.compilationName !== compilationName) {
if (message.compilationId && message.compilationId !== compilationId) {
return;
}

Expand Down Expand Up @@ -237,44 +208,33 @@ function onMessage(e: MessageEvent<string>) {
}
}

function sleep(msec = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, msec);
});
}

async function onClose() {
console.info('[HMR] disconnected. Attempting to reconnect.');

removeListeners();

await sleep(1000);
retryCount++;

if (
connection &&
(connection.readyState === connection.CONNECTING ||
connection.readyState === connection.OPEN)
) {
retryCount = 0;
return;
}

// Exceeded max retry attempts, stop retry.
if (retryCount > MAX_RETRIES) {
console.info(
'[HMR] Unable to establish a connection after exceeding the maximum retry attempts.',
);
retryCount = 0;
function onClose() {
if (reconnectCount >= config.reconnect) {
if (config.reconnect > 0) {
console.info(
'[HMR] Connection failure after maximum reconnect limit exceeded.',
);
}
return;
}

reconnect();
console.info('[HMR] disconnected. Attempting to reconnect.');
removeListeners();
connection = null;
reconnectCount++;
setTimeout(connect, 1000);
}

// Establishing a WebSocket connection with the server.
function connect() {
const socketUrl = getSocketUrl(RSBUILD_CLIENT_CONFIG);
const { location } = self;
const { host, port, path, protocol } = config;
const socketUrl = formatURL({
protocol: protocol || (location.protocol === 'https:' ? 'wss' : 'ws'),
hostname: host || location.hostname,
port: port || location.port,
pathname: path || '/rsbuild-hmr',
});

connection = new WebSocket(socketUrl);
connection.addEventListener('open', onOpen);
Expand All @@ -292,17 +252,6 @@ function removeListeners() {
}
}

/**
* Close the current connection if it exists and then establishes a new
* connection.
*/
function reconnect() {
if (connection) {
connection = null;
}
connect();
}

function reloadPage() {
if (RSBUILD_DEV_LIVE_RELOAD) {
window.location.reload();
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ const getDefaultDevConfig = (): NormalizedDevConfig => ({
writeToDisk: false,
client: {
path: HMR_SOCKET_PATH,
// By default it is set to "location.port"
port: '',
// By default it is set to "location.hostname"
host: '',
overlay: true,
reconnect: 100,
},
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/server/compilerDevMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ export class CompilerDevMiddleware {
const { devConfig, serverConfig } = this;

const callbacks = {
onInvalid: (compilationName?: string) => {
onInvalid: (compilationId?: string) => {
this.socketServer.sockWrite({
type: 'invalid',
compilationName,
compilationId,
});
},
onDone: (stats: any) => {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/server/devMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type { Compiler, MultiCompiler } from '@rspack/core';
import { applyToCompiler } from '../helpers';
import type { DevMiddlewareOptions } from '../provider/createCompiler';
import type { DevConfig, NextFunction } from '../types';
import { getCompilationName } from './helper';
import { getCompilationId } from './helper';

type ServerCallbacks = {
onInvalid: (compilationName?: string) => void;
onInvalid: (compilationId?: string) => void;
onDone: (stats: any) => void;
};

Expand Down Expand Up @@ -50,10 +50,10 @@ export const setupServerHooks = (
const { compile, invalid, done } = compiler.hooks;

compile.tap('rsbuild-dev-server', () =>
hookCallbacks.onInvalid(getCompilationName(compiler)),
hookCallbacks.onInvalid(getCompilationId(compiler)),
);
invalid.tap('rsbuild-dev-server', () =>
hookCallbacks.onInvalid(getCompilationName(compiler)),
hookCallbacks.onInvalid(getCompilationId(compiler)),
);
done.tap('rsbuild-dev-server', hookCallbacks.onDone);
};
Expand All @@ -74,7 +74,7 @@ function applyHMREntry({
}

new compiler.webpack.DefinePlugin({
RSBUILD_COMPILATION_NAME: JSON.stringify(getCompilationName(compiler)),
RSBUILD_COMPILATION_NAME: JSON.stringify(getCompilationId(compiler)),
RSBUILD_CLIENT_CONFIG: JSON.stringify(clientConfig),
RSBUILD_DEV_LIVE_RELOAD: liveReload,
}).apply(compiler);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/server/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ export const getAddressUrls = ({
};

// A unique name for WebSocket communication
export const getCompilationName = (
export const getCompilationId = (
compiler: Rspack.Compiler | Rspack.Compilation,
) => `${compiler.name ?? ''}_${compiler.options.output.uniqueName ?? ''}`;

Expand Down
Loading

0 comments on commit 9a476f8

Please sign in to comment.