import { Platform } from 'react-native'; /** * 自定义 HMR Client * 用于监听 Metro 的 /hot 端点,实现多 Bundle 的热更新 */ export class HMRClient { private static instance: HMRClient; private ws: WebSocket | null = null; private host: string = 'localhost'; private port: number = 8081; private isEnabled: boolean = false; private pendingEntryPoints: string[] = []; private constructor() {} public static getInstance(): HMRClient { if (!HMRClient.instance) { HMRClient.instance = new HMRClient(); } return HMRClient.instance; } /** * 初始化并连接 WebSocket */ public setup(host: string, port: number) { if (this.isEnabled) return; this.host = host; this.port = port; this.isEnabled = true; this.connect(); } private connect() { if (!this.isEnabled) return; const url = `ws://${this.host}:${this.port}/hot`; console.log('[HMRClient] Connecting to:', url); this.ws = new WebSocket(url); this.ws.onopen = () => { console.log('[HMRClient] Connected'); this.flushEntryPoints(); }; this.ws.onmessage = (event) => { try { const data = JSON.parse(event.data as string); this.processMessage(data); } catch (e) { console.error('[HMRClient] Failed to parse message:', e); } }; this.ws.onerror = (e) => { console.log('[HMRClient] Connection error:', (e as any).message); }; this.ws.onclose = () => { console.log('[HMRClient] Disconnected'); this.ws = null; // 尝试重连 if (this.isEnabled) { setTimeout(() => this.connect(), 2000); } }; } /** * 注册 Bundle 入口 * 当 Bundle 加载时调用,告诉 Metro 我们关心这个 Bundle 的更新 */ public registerBundle(bundleUrl: string) { if (!__DEV__) return; // 避免重复注册 if (!this.pendingEntryPoints.includes(bundleUrl)) { this.pendingEntryPoints.push(bundleUrl); this.flushEntryPoints(); } } private flushEntryPoints() { if (!this.ws || this.ws.readyState !== WebSocket.OPEN || this.pendingEntryPoints.length === 0) { return; } const message = { type: 'register-entrypoints', entryPoints: this.pendingEntryPoints, }; this.ws.send(JSON.stringify(message)); // 注意:不要清空 pendingEntryPoints,因为断线重连后需要重新注册 } private processMessage(message: any) { if (message.type === 'update') { console.log('[HMRClient] Received update'); this.applyUpdate(message.body); } else if (message.type === 'update-start') { console.log('[HMRClient] Update start'); } else if (message.type === 'update-done') { console.log('[HMRClient] Update done'); } else if (message.type === 'error') { console.error('[HMRClient] Metro error:', message.body); } } private applyUpdate(body: any) { const { modified, added, deleted } = body; if (modified && modified.length > 0) { modified.forEach((mod: any) => { const [moduleId, moduleCode] = mod.module; // console.log(`[HMRClient] Applying update for module: ${moduleId}`); try { // 执行新的模块代码 // 模块代码通常包含 __d(...) 调用 // 我们使用 Function 构造函数或 eval 来执行它 // 如果 moduleCode 已经是字符串形式的代码 // 注意:Metro HMR 发送的代码可能需要处理 // 在 React Native 环境中,__d 是全局函数 // 直接执行代码应该会更新模块定义 // 使用 Function 更加安全,但需要确保作用域正确 // 这里简单使用 indirect eval (0, eval)(moduleCode); // 强制刷新引用了该模块的组件? // 如果 React Fast Refresh 启用,它应该会自动处理 // 但我们需要确保 Metro 的 runtime 能够感知到模块变化 // HACK: 触发一个全局事件或者直接调用 RN 的刷新机制 // 但由于我们是自定义加载,可能需要手动触发重新渲染 } catch (error) { console.error(`[HMRClient] Failed to apply update for module ${moduleId}:`, error); } }); } } public disable() { this.isEnabled = false; if (this.ws) { this.ws.close(); this.ws = null; } } }