Interface that custom transforms must implement to work with babel-plugin-react-transform.
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;
};
}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;
};
}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;
};
}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;
};
}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;
};
}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;
};
}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 versionMethods 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;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
}
};
}