0
# Hot Module Replacement Client
1
2
WebSocket-based client for receiving and applying hot updates during development. The HMR client provides real-time code updates without losing application state, enabling fast development iteration cycles.
3
4
## Capabilities
5
6
### HMRClient Class
7
8
Main class for establishing and managing HMR WebSocket connections with the Metro development server.
9
10
```javascript { .api }
11
/**
12
* WebSocket-based Hot Module Replacement client
13
* Extends EventEmitter for event-driven update handling
14
*/
15
class HMRClient extends EventEmitter {
16
/**
17
* Creates a new HMR client connection
18
* @param url - WebSocket URL for the Metro HMR server
19
*/
20
constructor(url: string);
21
22
/**
23
* Closes the WebSocket connection
24
*/
25
close(): void;
26
27
/**
28
* Sends a message to the HMR server
29
* @param message - String message to send
30
*/
31
send(message: string): void;
32
33
/**
34
* Enables HMR and applies any pending updates
35
*/
36
enable(): void;
37
38
/**
39
* Disables HMR (updates will be queued but not applied)
40
*/
41
disable(): void;
42
43
/**
44
* Returns whether HMR is currently enabled
45
* @returns True if HMR is enabled
46
*/
47
isEnabled(): boolean;
48
49
/**
50
* Returns whether there are updates waiting to be applied
51
* @returns True if there are pending updates
52
*/
53
hasPendingUpdates(): boolean;
54
}
55
56
type SocketState = 'opening' | 'open' | 'closed';
57
```
58
59
**Usage Examples:**
60
61
```javascript
62
const HMRClient = require("metro-runtime/modules/HMRClient");
63
64
// Basic HMR client setup
65
const client = new HMRClient("ws://localhost:8081/hot");
66
67
// Enable HMR when ready
68
client.enable();
69
70
// Set up event listeners
71
client.on("open", () => {
72
console.log("HMR connection established");
73
});
74
75
client.on("update", (update) => {
76
console.log("Received hot update:", update.revisionId);
77
console.log("Modified modules:", update.modified.length);
78
});
79
80
client.on("error", (error) => {
81
console.error("HMR error:", error);
82
});
83
84
// Disable HMR temporarily
85
const pauseHMR = () => {
86
client.disable();
87
console.log("HMR paused, updates will be queued");
88
};
89
90
// Re-enable and apply queued updates
91
const resumeHMR = () => {
92
client.enable();
93
console.log("HMR resumed, applying queued updates");
94
};
95
```
96
97
### Event System
98
99
The HMRClient emits various events for different stages of the update process:
100
101
```javascript { .api }
102
// Connection events
103
client.on('open', () => void);
104
client.on('close', (closeEvent) => void);
105
client.on('connection-error', (error) => void);
106
107
// HMR lifecycle events
108
client.on('bundle-registered', () => void);
109
client.on('update-start', (data: {isInitialUpdate: boolean}) => void);
110
client.on('update', (update: HmrUpdate) => void);
111
client.on('update-done', () => void);
112
client.on('error', (error: FormattedError) => void);
113
```
114
115
**Event Usage Examples:**
116
117
```javascript
118
// Complete HMR event handling
119
const setupHMRClient = (url) => {
120
const client = new HMRClient(url);
121
122
// Connection lifecycle
123
client.on("open", () => {
124
console.log("π₯ HMR connected");
125
client.send(JSON.stringify({
126
type: "register-entrypoints",
127
entryPoints: ["index.js"]
128
}));
129
});
130
131
client.on("close", (event) => {
132
console.log("β HMR disconnected:", event.reason);
133
});
134
135
client.on("connection-error", (error) => {
136
console.error("π¨ HMR connection failed:", error);
137
});
138
139
// Update lifecycle
140
client.on("bundle-registered", () => {
141
console.log("π¦ Bundle registered with HMR server");
142
});
143
144
client.on("update-start", ({ isInitialUpdate }) => {
145
if (isInitialUpdate) {
146
console.log("π¬ Initial HMR update");
147
} else {
148
console.log("β‘ Hot update starting");
149
}
150
});
151
152
client.on("update", (update) => {
153
const { added, modified, deleted, revisionId } = update;
154
console.log(`π Update ${revisionId}:`, {
155
added: added.length,
156
modified: modified.length,
157
deleted: deleted.length
158
});
159
});
160
161
client.on("update-done", () => {
162
console.log("β Hot update completed");
163
});
164
165
client.on("error", (error) => {
166
console.error("π₯ HMR error:", error.message);
167
if (error.errors) {
168
error.errors.forEach(err => console.error(" -", err.description));
169
}
170
});
171
172
return client;
173
};
174
```
175
176
## HMR Message Types
177
178
The client handles various message types from the Metro server:
179
180
### Update Messages
181
182
```javascript { .api }
183
type HmrUpdate = {
184
+added: $ReadOnlyArray<HmrModule>,
185
+deleted: $ReadOnlyArray<number>,
186
+isInitialUpdate: boolean,
187
+modified: $ReadOnlyArray<HmrModule>,
188
+revisionId: string,
189
};
190
191
type HmrModule = {
192
+module: [number, string], // [moduleId, moduleCode]
193
+sourceMappingURL: string,
194
+sourceURL: string,
195
};
196
```
197
198
### Server Messages
199
200
Complete message types received from the Metro HMR server:
201
202
```javascript { .api }
203
type HmrMessage =
204
| {+type: 'bundle-registered'}
205
| {+type: 'update-start', +body: {+isInitialUpdate: boolean}}
206
| {+type: 'update-done'}
207
| {+type: 'update', +body: HmrUpdate}
208
| {+type: 'error', +body: FormattedError};
209
210
type FormattedError = {
211
+type: string,
212
+message: string,
213
+errors: Array<{description: string, ...}>,
214
};
215
```
216
217
### Client Messages
218
219
Messages that can be sent to the server:
220
221
```javascript { .api }
222
type HmrClientMessage =
223
| {
224
+type: 'register-entrypoints',
225
+entryPoints: Array<string>,
226
}
227
| {
228
+type: 'log',
229
+level: 'trace' | 'info' | 'warn' | 'log' | 'group' | 'groupCollapsed' | 'groupEnd' | 'debug',
230
+data: Array<mixed>,
231
+mode: 'BRIDGE' | 'NOBRIDGE',
232
}
233
| {
234
+type: 'log-opt-in',
235
};
236
```
237
238
## Update Processing
239
240
The HMR client automatically processes updates through code injection:
241
242
```javascript
243
// Updates are processed automatically when HMR is enabled
244
// The client injects new module code using eval() or globalEvalWithSourceUrl()
245
246
// Manual update handling (advanced usage)
247
client.on("update", (update) => {
248
// Updates are automatically applied when HMR is enabled
249
// This event is for monitoring/logging purposes
250
251
update.added.forEach(module => {
252
const [moduleId, code] = module.module;
253
console.log(`Added module ${moduleId}`);
254
});
255
256
update.modified.forEach(module => {
257
const [moduleId, code] = module.module;
258
console.log(`Modified module ${moduleId}`);
259
});
260
261
update.deleted.forEach(moduleId => {
262
console.log(`Deleted module ${moduleId}`);
263
});
264
});
265
```
266
267
## Connection Management
268
269
The client handles connection states and message queuing:
270
271
```javascript
272
// Connection state management
273
const monitorConnection = (client) => {
274
const checkState = () => {
275
console.log("HMR State:", {
276
enabled: client.isEnabled(),
277
hasPending: client.hasPendingUpdates()
278
});
279
};
280
281
setInterval(checkState, 5000);
282
};
283
284
// Graceful shutdown
285
const shutdownHMR = (client) => {
286
console.log("Shutting down HMR client...");
287
client.disable();
288
client.close();
289
};
290
291
// Reconnection logic (manual implementation)
292
const createReconnectingClient = (url) => {
293
let client = new HMRClient(url);
294
295
client.on("close", () => {
296
console.log("Attempting to reconnect in 5 seconds...");
297
setTimeout(() => {
298
client = new HMRClient(url);
299
client.enable();
300
}, 5000);
301
});
302
303
return client;
304
};
305
```
306
307
## Integration with React Fast Refresh
308
309
The HMR client integrates seamlessly with React Fast Refresh for component-level updates:
310
311
```javascript
312
// React Fast Refresh integration is handled automatically
313
// The client works with Metro's React Refresh implementation
314
315
// Custom refresh boundary detection (advanced)
316
client.on("update", (update) => {
317
const hasReactComponents = update.modified.some(module => {
318
const [, code] = module.module;
319
return code.includes("$RefreshReg$") || code.includes("react");
320
});
321
322
if (hasReactComponents) {
323
console.log("π React components updated with Fast Refresh");
324
}
325
});
326
```