Refactor Code with a Codemod

#Abstract Syntax Trees
#Javascript
#Codemod

Switch out imports and component props of a React Component without wasting time. Let a Codemod script handle it.

Codemod is the practice of parsing through code using ASTs and transforming code automagically. This gives developers some sweet superpowers to solve problems efficiently in each file. These scripts benefit developers, PMs, and their organizations:

  • Save countless hours that would've been spent on a manual refactor
  • Reduces human error when implementing refactors
  • Teams + organizations can make code changes confidently with Codemods
  • Organizations can automate the tedious and redirect developer effort to improve their products.

Helpful examples:

  • Change the import from a package
  • Remove a deprecated component prop
  • Add new property in every instance of a React Component

Below are a few examples of changing an import name, a component name, and adding a component property:

ComponentTransform.js
export default function transformer(file, { jscodeshift: j }, options) {
  const source = j(file.source);
  
  /** Transform #1: Change an Input Location **/
  // Tranform React Imports for a Button
  const reactImports = source
    .find(j.ImportDeclaration) // Find all nodes that match a type of `ImportDeclaration`
    .filter(path => path.node.source.value === '@old-library') // Filter imports by source equal to the target
	
  reactImports.forEach((
    reactImport, // Iterate over react imports
  ) =>
    // Replace the existing node with a new one
    j(reactImport).replaceWith(
      // Build a new import declaration node based on the existing one
      j.importDeclaration(
        reactImport.node.specifiers, // copy over the existing import specificers
        j.stringLiteral('@new-library'), // Replace the source with our new source
      ),
    ),
  );
  
  /** Transform #2: Update a specific import name*/
   // Build an import specifier
  const newImport = j.importSpecifier(j.identifier('Button'));
  source
    .find(j.ImportDeclaration)
    // Find if the import source is the newly transformed import
    .filter(path => path.node.source.value === '@new-library')
    // Grab the import with the name we want to change
    .find(j.ImportSpecifier, {
  		imported: {
          name: 'DeprecatedButton'
        }
  	})
    .replaceWith(newImport); // Insert our new import specifier
  
  /** Transform #3: Add Component Props */
  source
    .find(j.JSXElement,{
  			openingElement: {
            	name: {
                	name: 'DeprecatedButton'
                }
            }
  	})
    .forEach(element => {
      const newComponentTag = j.jsxIdentifier('Button'); 
    
      const newComponent = j.jsxElement(
        // New opening element now relabeled + props transformed
        j.jsxOpeningElement(newComponentTag, [
          ...element.node.openingElement.attributes,
          // build and insert our new prop
          j.jsxAttribute(j.jsxIdentifier('disabled'), j.stringLiteral('true')),
        ]),
        // New closing element now relabeled
        j.jsxClosingElement(newComponentTag, [
        	...element.node.closingElement,
        ]),
        element.node.children,
      );

      // Replace our original component with our modified one
      j(element).replaceWith(newComponent);
    });
  
  return source.toSource();
}