CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metro-runtime

Module required for evaluating Metro bundles with async loading and HMR support.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

hmr-client.mddocs/

Hot Module Replacement Client

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.

Capabilities

HMRClient Class

Main class for establishing and managing HMR WebSocket connections with the Metro development server.

/**
 * WebSocket-based Hot Module Replacement client
 * Extends EventEmitter for event-driven update handling
 */
class HMRClient extends EventEmitter {
  /**
   * Creates a new HMR client connection
   * @param url - WebSocket URL for the Metro HMR server
   */
  constructor(url: string);
  
  /**
   * Closes the WebSocket connection
   */
  close(): void;
  
  /**
   * Sends a message to the HMR server
   * @param message - String message to send
   */
  send(message: string): void;
  
  /**
   * Enables HMR and applies any pending updates
   */
  enable(): void;
  
  /**
   * Disables HMR (updates will be queued but not applied)
   */
  disable(): void;
  
  /**
   * Returns whether HMR is currently enabled
   * @returns True if HMR is enabled
   */
  isEnabled(): boolean;
  
  /**
   * Returns whether there are updates waiting to be applied
   * @returns True if there are pending updates
   */
  hasPendingUpdates(): boolean;
}

type SocketState = 'opening' | 'open' | 'closed';

Usage Examples:

const HMRClient = require("metro-runtime/modules/HMRClient");

// Basic HMR client setup
const client = new HMRClient("ws://localhost:8081/hot");

// Enable HMR when ready
client.enable();

// Set up event listeners
client.on("open", () => {
  console.log("HMR connection established");
});

client.on("update", (update) => {
  console.log("Received hot update:", update.revisionId);
  console.log("Modified modules:", update.modified.length);
});

client.on("error", (error) => {
  console.error("HMR error:", error);
});

// Disable HMR temporarily
const pauseHMR = () => {
  client.disable();
  console.log("HMR paused, updates will be queued");
};

// Re-enable and apply queued updates
const resumeHMR = () => {
  client.enable();
  console.log("HMR resumed, applying queued updates");
};

Event System

The HMRClient emits various events for different stages of the update process:

// Connection events
client.on('open', () => void);
client.on('close', (closeEvent) => void);
client.on('connection-error', (error) => void);

// HMR lifecycle events
client.on('bundle-registered', () => void);
client.on('update-start', (data: {isInitialUpdate: boolean}) => void);
client.on('update', (update: HmrUpdate) => void);
client.on('update-done', () => void);
client.on('error', (error: FormattedError) => void);

Event Usage Examples:

// Complete HMR event handling
const setupHMRClient = (url) => {
  const client = new HMRClient(url);
  
  // Connection lifecycle
  client.on("open", () => {
    console.log("🔥 HMR connected");
    client.send(JSON.stringify({
      type: "register-entrypoints",
      entryPoints: ["index.js"]
    }));
  });
  
  client.on("close", (event) => {
    console.log("❌ HMR disconnected:", event.reason);
  });
  
  client.on("connection-error", (error) => {
    console.error("🚨 HMR connection failed:", error);
  });
  
  // Update lifecycle
  client.on("bundle-registered", () => {
    console.log("📦 Bundle registered with HMR server");
  });
  
  client.on("update-start", ({ isInitialUpdate }) => {
    if (isInitialUpdate) {
      console.log("🎬 Initial HMR update");
    } else {
      console.log("⚡ Hot update starting");
    }
  });
  
  client.on("update", (update) => {
    const { added, modified, deleted, revisionId } = update;
    console.log(`🔄 Update ${revisionId}:`, {
      added: added.length,
      modified: modified.length,
      deleted: deleted.length
    });
  });
  
  client.on("update-done", () => {
    console.log("✅ Hot update completed");
  });
  
  client.on("error", (error) => {
    console.error("💥 HMR error:", error.message);
    if (error.errors) {
      error.errors.forEach(err => console.error("  -", err.description));
    }
  });
  
  return client;
};

HMR Message Types

The client handles various message types from the Metro server:

Update Messages

type HmrUpdate = {
  +added: $ReadOnlyArray<HmrModule>,
  +deleted: $ReadOnlyArray<number>,
  +isInitialUpdate: boolean,
  +modified: $ReadOnlyArray<HmrModule>,
  +revisionId: string,
};

type HmrModule = {
  +module: [number, string], // [moduleId, moduleCode]
  +sourceMappingURL: string,
  +sourceURL: string,
};

Server Messages

Complete message types received from the Metro HMR server:

type HmrMessage =
  | {+type: 'bundle-registered'}
  | {+type: 'update-start', +body: {+isInitialUpdate: boolean}}
  | {+type: 'update-done'}
  | {+type: 'update', +body: HmrUpdate}
  | {+type: 'error', +body: FormattedError};

type FormattedError = {
  +type: string,
  +message: string,
  +errors: Array<{description: string, ...}>,
};

Client Messages

Messages that can be sent to the server:

type HmrClientMessage =
  | {
      +type: 'register-entrypoints',
      +entryPoints: Array<string>,
    }
  | {
      +type: 'log',
      +level: 'trace' | 'info' | 'warn' | 'log' | 'group' | 'groupCollapsed' | 'groupEnd' | 'debug',
      +data: Array<mixed>,
      +mode: 'BRIDGE' | 'NOBRIDGE',
    }
  | {
      +type: 'log-opt-in',
    };

Update Processing

The HMR client automatically processes updates through code injection:

// Updates are processed automatically when HMR is enabled
// The client injects new module code using eval() or globalEvalWithSourceUrl()

// Manual update handling (advanced usage)
client.on("update", (update) => {
  // Updates are automatically applied when HMR is enabled
  // This event is for monitoring/logging purposes
  
  update.added.forEach(module => {
    const [moduleId, code] = module.module;
    console.log(`Added module ${moduleId}`);
  });
  
  update.modified.forEach(module => {
    const [moduleId, code] = module.module;
    console.log(`Modified module ${moduleId}`);
  });
  
  update.deleted.forEach(moduleId => {
    console.log(`Deleted module ${moduleId}`);
  });
});

Connection Management

The client handles connection states and message queuing:

// Connection state management
const monitorConnection = (client) => {
  const checkState = () => {
    console.log("HMR State:", {
      enabled: client.isEnabled(),
      hasPending: client.hasPendingUpdates()
    });
  };
  
  setInterval(checkState, 5000);
};

// Graceful shutdown
const shutdownHMR = (client) => {
  console.log("Shutting down HMR client...");
  client.disable();
  client.close();
};

// Reconnection logic (manual implementation)
const createReconnectingClient = (url) => {
  let client = new HMRClient(url);
  
  client.on("close", () => {
    console.log("Attempting to reconnect in 5 seconds...");
    setTimeout(() => {
      client = new HMRClient(url);
      client.enable();
    }, 5000);
  });
  
  return client;
};

Integration with React Fast Refresh

The HMR client integrates seamlessly with React Fast Refresh for component-level updates:

// React Fast Refresh integration is handled automatically
// The client works with Metro's React Refresh implementation

// Custom refresh boundary detection (advanced)
client.on("update", (update) => {
  const hasReactComponents = update.modified.some(module => {
    const [, code] = module.module;
    return code.includes("$RefreshReg$") || code.includes("react");
  });
  
  if (hasReactComponents) {
    console.log("🔄 React components updated with Fast Refresh");
  }
});

docs

async-loading.md

hmr-client.md

index.md

require-polyfill.md

tile.json