Skip to content

Commit 88bec52

Browse files
fix(runtime): avoid HMRClient.disable startup race (#38)
Fixed HMR (Hot Module Replacement) initialization race condition by adding retry logic with delays when disabling HMR, ensuring Harness waits for HMR to be ready before proceeding.
1 parent 2227d64 commit 88bec52

4 files changed

Lines changed: 61 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@react-native-harness/runtime': prerelease
3+
---
4+
5+
Fixed HMR (Hot Module Replacement) initialization race condition by adding retry logic with delays when disabling HMR, ensuring Harness waits for HMR to be ready before proceeding.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { disableHMRWhenReady } from '../disableHMRWhenReady.js';
4+
5+
describe('initialize', () => {
6+
it('retries HMRClient.disable until setup is ready', async () => {
7+
vi.useFakeTimers();
8+
9+
const disable = vi
10+
.fn()
11+
.mockImplementationOnce(() => {
12+
throw new Error('Expected HMRClient.setup() call at startup.');
13+
})
14+
.mockImplementationOnce(() => {
15+
// ok
16+
});
17+
18+
const promise = disableHMRWhenReady(disable, 50);
19+
await vi.runAllTimersAsync();
20+
await promise;
21+
22+
expect(disable).toHaveBeenCalledTimes(2);
23+
});
24+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export function disableHMRWhenReady(
2+
disable: () => void,
3+
retriesLeft: number,
4+
retryDelay = 10
5+
) {
6+
return new Promise<void>((resolve, reject) => {
7+
function attempt(remaining: number) {
8+
try {
9+
disable();
10+
resolve();
11+
} catch (error) {
12+
if (
13+
remaining > 0 &&
14+
error instanceof Error &&
15+
error.message.includes('Expected HMRClient.setup() call at startup.')
16+
) {
17+
setTimeout(() => attempt(remaining - 1), retryDelay);
18+
return;
19+
}
20+
21+
reject(error);
22+
}
23+
}
24+
25+
attempt(retriesLeft);
26+
});
27+
}

packages/runtime/src/initialize.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getDeviceDescriptor } from './client/getDeviceDescriptor.js';
22
import { getClient } from './client/index.js';
3+
import { disableHMRWhenReady } from './disableHMRWhenReady.js';
34
import { setupJestMock } from './jest-mock.js';
45

56
// Polyfill for EventTarget
@@ -21,11 +22,10 @@ const HMRClient =
2122

2223
// Wait for HMRClient to be initialized
2324
setTimeout(() => {
24-
HMRClient.disable();
25-
26-
// Initialize the React Native Harness
27-
void getClient().then((client) =>
28-
client.rpc.reportReady(getDeviceDescriptor())
25+
void disableHMRWhenReady(() => HMRClient.disable(), 50).then(() =>
26+
getClient().then((client) =>
27+
client.rpc.reportReady(getDeviceDescriptor())
28+
)
2929
);
3030
});
3131

0 commit comments

Comments
 (0)