or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

devtools.mddom-components.mdindex.mdnative-modules.mdutilities.mdwinter-polyfills.md
tile.json

devtools.mddocs/

DevTools System

The Expo DevTools system provides a plugin-based architecture for integrating development tools, debuggers, and monitoring systems into your Expo applications. It enables real-time communication between development tools and running applications through a standardized plugin interface.

Capabilities

DevTools Plugin Client

Core client for connecting to and communicating with DevTools plugins during development.

/**
 * DevTools plugin client for communicating with development tools
 */
class DevToolsPluginClient {
  /**
   * Add a listener for messages from the DevTools plugin
   * @param listener - Function to handle incoming messages
   * @returns Subscription object for removing the listener
   */
  addMessageListener(listener: (message: any) => void): EventSubscription;

  /**
   * Add a listener for messages that only fires once
   * @param listener - Function to handle incoming message
   */
  addMessageListenerOnce(listener: (message: any) => void): void;

  /**
   * Send a message to the connected DevTools plugin
   * @param message - Message to send to the plugin
   */
  sendMessage(message: any): void;

  /**
   * Close the connection to the DevTools plugin
   * @returns Promise that resolves when connection is closed
   */
  closeAsync(): Promise<void>;

  /**
   * Check if the client is currently connected to a plugin
   * @returns True if connected, false otherwise
   */
  isConnected(): boolean;
}

Plugin Client Factory

Factory function for creating DevTools plugin clients with configuration options.

/**
 * Creates a DevTools plugin client for the specified plugin
 * @param pluginName - Name of the DevTools plugin to connect to
 * @param options - Optional configuration for the client connection
 * @returns Promise resolving to the connected plugin client
 */
function getDevToolsPluginClientAsync(
  pluginName: string,
  options?: DevToolsPluginClientOptions
): Promise<DevToolsPluginClient>;

/**
 * React hook to get the DevToolsPluginClient instance
 * @param pluginName - Name of the plugin to connect to
 * @param options - Optional configuration for the client
 * @returns DevToolsPluginClient instance or null if not connected
 */
function useDevToolsPluginClient(
  pluginName: string,
  options?: DevToolsPluginClientOptions
): DevToolsPluginClient | null;

/**
 * Enable or disable logging for DevTools
 * @param enabled - Whether to enable logging
 */
function setEnableLogging(enabled: boolean): void;

/**
 * Configuration options for DevTools plugin clients
 */
interface DevToolsPluginClientOptions {
  /** The underlying WebSocket binaryType ('arraybuffer' or 'blob') */
  websocketBinaryType?: 'arraybuffer' | 'blob';
}

React Hook Integration

React hook for managing DevTools plugin connections within React components.

/**
 * React hook for managing a DevTools plugin client connection
 * @param pluginName - Name of the DevTools plugin to connect to
 * @param options - Optional configuration for the client connection  
 * @returns The connected plugin client or null if not connected
 */
function useDevToolsPluginClient(
  pluginName: string,
  options?: DevToolsPluginClientOptions
): DevToolsPluginClient | null;

Logging Configuration

Control logging behavior for the DevTools system.

/**
 * Enable or disable logging for the DevTools system
 * @param enable - Whether to enable detailed logging
 */
function setEnableLogging(enable: boolean): void;

Usage Examples

Basic Plugin Connection

import { getDevToolsPluginClientAsync } from 'expo/devtools';

async function connectToDebugger() {
  try {
    const client = await getDevToolsPluginClientAsync('debugger', {
      websocketBinaryType: 'arraybuffer',
    });

    // Listen for messages from the debugger plugin
    const subscription = client.addMessageListener('breakpoint', (params) => {
      console.log('Debugger breakpoint:', params);
      handleBreakpoint(params);
    });

    // Send debugging information to the plugin
    client.sendMessage('state_update', {
      timestamp: Date.now(),
      data: { userId: 123, screen: 'Profile' },
    });

    return { client, subscription };
  } catch (error) {
    console.error('Failed to connect to debugger plugin:', error);
    return null;
  }
}

React Hook Usage

import React, { useEffect } from 'react';
import { useDevToolsPluginClient } from 'expo/devtools';

function DebuggingComponent() {
  const debugClient = useDevToolsPluginClient('performance-monitor', {
    autoReconnect: true,
  });

  useEffect(() => {
    if (debugClient?.isConnected()) {
      // Set up message listener
      const subscription = debugClient.addMessageListener((message) => {
        if (message.type === 'performance_request') {
          // Respond with performance metrics
          debugClient.sendMessage({
            type: 'performance_data',
            metrics: {
              renderTime: performance.now(),
              memoryUsage: performance.memory?.usedJSHeapSize,
            },
          });
        }
      });

      return () => subscription.remove();
    }
  }, [debugClient]);

  return null; // This is a debugging component
}

Custom DevTools Plugin

import { getDevToolsPluginClientAsync, setEnableLogging } from 'expo/devtools';

class CustomDevToolsPlugin {
  private client: DevToolsPluginClient | null = null;
  private messageSubscription: EventSubscription | null = null;

  async initialize(pluginName: string) {
    // Enable detailed logging for development
    setEnableLogging(__DEV__);

    try {
      this.client = await getDevToolsPluginClientAsync(pluginName, {
        requestInterceptor: (request) => {
          // Log all outgoing requests
          console.log('DevTools request:', request);
          return request;
        },
        responseInterceptor: (response) => {
          // Log all incoming responses
          console.log('DevTools response:', response);
          return response;
        },
      });

      this.messageSubscription = this.client.addMessageListener(
        this.handleMessage.bind(this)
      );

      console.log('DevTools plugin initialized successfully');
    } catch (error) {
      console.error('Failed to initialize DevTools plugin:', error);
    }
  }

  private handleMessage(message: any) {
    switch (message.type) {
      case 'inspect_component':
        this.sendComponentInfo(message.componentId);
        break;
      case 'update_props':
        this.updateComponentProps(message.componentId, message.props);
        break;
      case 'reload_request':
        this.handleReloadRequest();
        break;
      default:
        console.warn('Unknown DevTools message type:', message.type);
    }
  }

  sendComponentInfo(componentId: string) {
    if (this.client?.isConnected()) {
      this.client.sendMessage({
        type: 'component_info',
        componentId,
        data: {
          props: getComponentProps(componentId),
          state: getComponentState(componentId),
          children: getComponentChildren(componentId),
        },
      });
    }
  }

  async cleanup() {
    if (this.messageSubscription) {
      this.messageSubscription.remove();
      this.messageSubscription = null;
    }

    if (this.client) {
      await this.client.closeAsync();
      this.client = null;
    }
  }
}

Network Debugging Plugin

import { useDevToolsPluginClient } from 'expo/devtools';

function NetworkDebugger() {
  const networkClient = useDevToolsPluginClient('network-debugger');

  // Intercept fetch requests for debugging
  const originalFetch = global.fetch;
  global.fetch = async (input, init) => {
    const startTime = Date.now();
    
    // Log request to DevTools
    networkClient?.sendMessage({
      type: 'network_request',
      method: init?.method || 'GET',
      url: input.toString(),
      headers: init?.headers,
      timestamp: startTime,
    });

    try {
      const response = await originalFetch(input, init);
      const endTime = Date.now();

      // Log response to DevTools
      networkClient?.sendMessage({
        type: 'network_response',
        url: input.toString(),
        status: response.status,
        statusText: response.statusText,
        duration: endTime - startTime,
        timestamp: endTime,
      });

      return response;
    } catch (error) {
      // Log error to DevTools
      networkClient?.sendMessage({
        type: 'network_error',
        url: input.toString(),
        error: error.message,
        timestamp: Date.now(),
      });
      throw error;
    }
  };

  return null;
}

Plugin Types and Messages

Common Message Types

The DevTools system supports various message types for different debugging scenarios:

// Performance monitoring
interface PerformanceMessage {
  type: 'performance_data';
  metrics: {
    renderTime: number;
    memoryUsage?: number;
    bundleSize?: number;
  };
}

// Component inspection
interface ComponentMessage {
  type: 'component_info';
  componentId: string;
  data: {
    props: Record<string, any>;
    state: Record<string, any>;
    children: string[];
  };
}

// Network debugging
interface NetworkMessage {
  type: 'network_request' | 'network_response' | 'network_error';
  url: string;
  method?: string;
  status?: number;
  duration?: number;
  error?: string;
}

// State management
interface StateMessage {
  type: 'state_update';
  store: string;
  action: {
    type: string;
    payload: any;
  };
  newState: any;
}

Error Handling

Connection Errors

import { getDevToolsPluginClientAsync } from 'expo/devtools';

async function connectWithErrorHandling() {
  try {
    const client = await getDevToolsPluginClientAsync('my-plugin', {
      connectionTimeout: 3000,
    });
    
    if (!client.isConnected()) {
      throw new Error('Plugin client is not connected');
    }
    
    return client;
  } catch (error) {
    if (error.message.includes('timeout')) {
      console.warn('DevTools plugin connection timed out');
    } else if (error.message.includes('not found')) {
      console.warn('DevTools plugin not available');
    } else {
      console.error('Unexpected DevTools error:', error);
    }
    return null;
  }
}

Message Handling Errors

function setupRobustMessageHandling(client: DevToolsPluginClient) {
  const subscription = client.addMessageListener((message) => {
    try {
      // Validate message structure
      if (!message || typeof message !== 'object') {
        console.warn('Invalid DevTools message received:', message);
        return;
      }

      // Handle message based on type
      handleDevToolsMessage(message);
    } catch (error) {
      console.error('Error handling DevTools message:', error);
      
      // Send error back to plugin
      client.sendMessage({
        type: 'error',
        originalMessage: message,
        error: error.message,
      });
    }
  });

  return subscription;
}

Development vs Production

Conditional Loading

import { getDevToolsPluginClientAsync } from 'expo/devtools';

// Only enable DevTools in development
const client = __DEV__ 
  ? await getDevToolsPluginClientAsync('debugger')
  : null;

if (client) {
  // Development-only debugging code
  setupDevelopmentDebugging(client);
}

Production Safety

// Safe DevTools usage that won't break production
function safeDevToolsCall(callback: (client: DevToolsPluginClient) => void) {
  if (__DEV__ && window.__EXPO_DEVTOOLS_CLIENT__) {
    try {
      callback(window.__EXPO_DEVTOOLS_CLIENT__);
    } catch (error) {
      console.warn('DevTools operation failed:', error);
    }
  }
}