Skip to content

Commit 710f312

Browse files
piotrskiclaude
andcommitted
fix: use static import in connect module to prevent race condition
The connect module used a dynamic `await import('react-devtools-core')` which yielded control before `initialize()` could install the devtools hook. This created a race where react-dom would load first, find no hook, and proceed without connecting to devtools. Switch to a static import so `initialize()` runs synchronously at module evaluation time, before any dependent modules (like main.tsx) execute. This also avoids top-level await which is incompatible with Vite's esbuild dep optimizer (es2020 target). Fixes #14 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0caec54 commit 710f312

1 file changed

Lines changed: 22 additions & 13 deletions

File tree

packages/agent-react-devtools/src/connect.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
*
66
* This must be imported before React loads. It:
77
* 1. Removes the Vite plugin-react hook stub
8-
* 2. Initializes react-devtools-core
8+
* 2. Initializes react-devtools-core (installs the real __REACT_DEVTOOLS_GLOBAL_HOOK__)
99
* 3. Connects via WebSocket to the agent-react-devtools daemon
1010
*
11+
* Steps 1–2 run synchronously at module evaluation time via a static import
12+
* of react-devtools-core so the hook is installed before React loads.
13+
*
1114
* Export `ready` — a promise that resolves once the WebSocket opens
1215
* (or after a 2s timeout / error, so the app is never blocked).
1316
*/
1417

18+
import { initialize, connectToDevTools } from 'react-devtools-core';
19+
1520
function getMeta(name: string): string | null {
1621
if (typeof document === 'undefined') return null;
1722
const meta = document.querySelector(`meta[name="${name}"]`);
@@ -41,23 +46,27 @@ const isProd =
4146
(typeof process !== 'undefined' &&
4247
process.env?.NODE_ENV === 'production');
4348

49+
// Install the devtools hook synchronously before React loads.
50+
// This MUST happen at module evaluation time — if deferred to an async
51+
// callback, react-dom may initialize first and miss the hook entirely.
52+
if (!isSSR && !isProd) {
53+
// Remove Vite's plugin-react hook stub so react-devtools-core can install the full hook
54+
try {
55+
delete (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
56+
} catch {
57+
// Property may be non-configurable (browser extension) — ignore
58+
}
59+
60+
initialize();
61+
}
62+
4463
export const ready: Promise<void> = isSSR || isProd ? noop() : connect();
4564

46-
async function connect(): Promise<void> {
65+
function connect(): Promise<void> {
4766
try {
4867
const port = getPort();
4968
const host = getHost();
5069

51-
// Remove Vite's plugin-react hook stub so react-devtools-core can install the full hook
52-
try {
53-
delete (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
54-
} catch {
55-
// Property may be non-configurable (browser extension) — ignore
56-
}
57-
58-
const { initialize, connectToDevTools } = await import('react-devtools-core');
59-
initialize();
60-
6170
return new Promise<void>((resolve) => {
6271
try {
6372
const ws = new WebSocket(`ws://${host}:${port}`);
@@ -70,6 +79,6 @@ async function connect(): Promise<void> {
7079
}
7180
});
7281
} catch {
73-
// react-devtools-core not installed or other error — silently skip
82+
return Promise.resolve();
7483
}
7584
}

0 commit comments

Comments
 (0)