or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

component-detection.mdindex.mdplugin-configuration.mdtransform-interface.md
tile.json

transform-interface.mddocs/

Transform Interface

Interface that custom transforms must implement to work with babel-plugin-react-transform.

Capabilities

Transform Function Pattern

All custom transforms must follow this specific function signature pattern.

/**
 * Transform function interface - all custom transforms must export a default function
 * that returns a wrapper function for React components
 * @param options - Configuration object with component metadata and dependencies
 * @returns Function that wraps and potentially modifies React components
 */
function customTransform(options: TransformOptions): TransformWrapper;

/**
 * Transform wrapper function that receives the React component
 * @param ReactClass - The React component constructor/class to transform
 * @param uniqueId - Unique identifier for the component within the file
 * @returns Modified or wrapped React component
 */
type TransformWrapper = (ReactClass: any, uniqueId: string) => any;

Basic Transform Example:

export default function myTransform(options) {
  return function wrap(ReactClass, uniqueId) {
    // Transform the ReactClass here
    return ReactClass;
  };
}

Transform Options

Configuration object passed to transform functions containing file and component metadata.

/**
 * Options object passed to transform functions
 */
interface TransformOptions {
  /** Absolute path to the source file being processed */
  filename: string;
  /** Map of component IDs to their metadata */
  components: { [id: string]: ComponentInfo };
  /** Array of imported dependencies specified in transform config */
  imports: any[];
  /** Array of local variables specified in transform config */
  locals: any[];
}

/**
 * Metadata for individual components found in the file
 */
interface ComponentInfo {
  /** Component display name (from class name or displayName property) */
  displayName?: string;
  /** Whether the component is defined inside a function scope */
  isInFunction?: boolean;
}

Usage Example:

export default function logAllUpdates(options) {
  return function wrap(ReactClass, uniqueId) {
    // Access component metadata
    const componentInfo = options.components[uniqueId];
    const displayName = componentInfo.displayName || '<Unknown>';
    
    // Access injected dependencies
    const React = options.imports[0]; // First import is React
    const module = options.locals[0];  // First local is module
    
    // Transform component
    const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate;
    ReactClass.prototype.componentDidUpdate = function componentDidUpdate() {
      console.info(`${displayName} updated:`, this.props, this.state);
      
      if (originalComponentDidUpdate) {
        originalComponentDidUpdate.apply(this, arguments);
      }
    };
    
    return ReactClass;
  };
}

Component Metadata Access

How to access component information within transforms.

/**
 * Component information available to transforms
 */
interface ComponentInfo {
  /** Component display name from class name or displayName property */
  displayName?: string;
  /** True if component is defined inside a function (affects hot reloading) */
  isInFunction?: boolean;
}

Accessing Component Metadata:

export default function debugTransform(options) {
  return function wrap(ReactClass, uniqueId) {
    const component = options.components[uniqueId];
    
    if (component.displayName) {
      console.log(`Transforming component: ${component.displayName}`);
    }
    
    if (component.isInFunction) {
      console.log('Component is defined inside a function');
    }
    
    return ReactClass;
  };
}

Dependency Injection

How transforms receive imported modules and local variables.

/**
 * Accessing injected dependencies in transforms
 */
interface DependencyAccess {
  /** Imported modules in order specified in transform config imports array */
  imports: any[];
  /** Local variables in order specified in transform config locals array */
  locals: any[];
}

Example with Dependencies:

// Transform config in .babelrc
{
  "transform": "my-transform",
  "imports": ["react", "prop-types"],
  "locals": ["module", "process"]
}

// Transform implementation
export default function myTransform(options) {
  // Access dependencies by index
  const React = options.imports[0];      // react
  const PropTypes = options.imports[1];  // prop-types
  const module = options.locals[0];      // module
  const process = options.locals[1];     // process
  
  return function wrap(ReactClass, uniqueId) {
    // Use dependencies in transformation
    if (process.env.NODE_ENV === 'development') {
      // Add development-only behavior
    }
    
    return ReactClass;
  };
}

Transform Examples

Hot Module Replacement Transform

Example transform for hot reloading React components.

export default function hmrTransform(options) {
  const React = options.imports[0];
  const module = options.locals[0];
  
  return function wrap(ReactClass, uniqueId) {
    if (!module.hot) {
      return ReactClass;
    }
    
    const displayName = options.components[uniqueId].displayName || 'Component';
    
    // Create hot-reloadable wrapper
    class HotWrapper extends React.Component {
      render() {
        return React.createElement(ReactClass, this.props);
      }
    }
    
    HotWrapper.displayName = `Hot(${displayName})`;
    
    // Register for hot updates
    module.hot.accept();
    
    return HotWrapper;
  };
}

Error Boundary Transform

Example transform for wrapping components with error boundaries.

export default function errorTransform(options) {
  const React = options.imports[0];
  const ErrorDisplay = options.imports[1];
  
  return function wrap(ReactClass, uniqueId) {
    const displayName = options.components[uniqueId].displayName || 'Component';
    
    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false, error: null };
      }
      
      componentDidCatch(error, errorInfo) {
        this.setState({ hasError: true, error });
        console.error(`Error in ${displayName}:`, error);
      }
      
      render() {
        if (this.state.hasError) {
          return React.createElement(ErrorDisplay, { error: this.state.error });
        }
        
        return React.createElement(ReactClass, this.props);
      }
    }
    
    ErrorBoundary.displayName = `ErrorBoundary(${displayName})`;
    
    return ErrorBoundary;
  };
}

AST Visitor Implementation

How the plugin traverses and modifies the AST to apply transforms.

/**
 * Internal visitor object used to traverse AST and find React components
 */
const componentVisitor = {
  /**
   * Class visitor method - processes class declarations and expressions
   * @param path - AST path for the class node
   */
  Class(path: ASTPath): void;
  
  /**
   * CallExpression visitor method - processes function calls like React.createClass
   * @param path - AST path for the call expression node  
   */
  CallExpression(path: ASTPath): void;
};

/**
 * Unique visited key to prevent processing components multiple times
 */
const VISITED_KEY: string; // Generated as 'react-transform-' + Date.now()

Visitor Behavior:

// Class visitor logic:
// 1. Skip if already visited or doesn't match superClass patterns or no render method
// 2. Mark node as visited
// 3. Extract component name and generate unique ID
// 4. Detect if component is in function scope
// 5. Convert class declaration to expression if needed
// 6. Wrap with transformation calls
// 7. Handle export default declarations

// CallExpression visitor logic:  
// 1. Skip if already visited or doesn't match factory patterns or no render method
// 2. Mark node as visited
// 3. Extract displayName from component object
// 4. Generate unique component ID
// 5. Detect if component is in function scope
// 6. Replace call with wrapped version

Code Generation Methods

Methods used to generate the wrapper code and component metadata.

/**
 * Initializes the components declaration object at the top of the file
 * @param componentsDeclarationId - Identifier for the components object
 * @param components - Array of detected component information
 * @returns VariableDeclaration AST node
 */
initComponentsDeclaration(componentsDeclarationId: any, components: ComponentInfo[]): any;

/**
 * Initializes transform imports and configurations  
 * @param componentsDeclarationId - Identifier for the components object
 * @returns Array of VariableDeclaration AST nodes for transforms
 */
initTransformers(componentsDeclarationId: any): any[];

/**
 * Creates the wrapper function that applies transforms to components
 * @param wrapperFunctionId - Identifier for the wrapper function
 * @returns FunctionDeclaration AST node for the wrapper
 */
initWrapperFunction(wrapperFunctionId: any): any;

Transform Validation

Transforms should handle edge cases and validate inputs appropriately.

export default function robustTransform(options) {
  // Validate options
  if (!options || !options.components) {
    throw new Error('Transform requires valid options with components');
  }
  
  return function wrap(ReactClass, uniqueId) {
    // Validate component
    if (!ReactClass) {
      console.warn('Transform received null/undefined component');
      return ReactClass;
    }
    
    // Check if component metadata exists
    const componentInfo = options.components[uniqueId];
    if (!componentInfo) {
      console.warn(`No metadata found for component ${uniqueId}`);
      return ReactClass;
    }
    
    // Apply transformation safely
    try {
      // Transform logic here
      return ReactClass;
    } catch (error) {
      console.error('Transform failed:', error);
      return ReactClass; // Return original on error
    }
  };
}