Mar 2, 2023

Automating Refactoring with Codemod

Software development is an ever-evolving process, and as more features are added to a product, the codebase can quickly become overwhelming. As code accumulates, so does the effort required to maintain it, making even simple refactoring tasks a daunting challenge. Fortunately, there is a solution: codemod.

Codemod is a tool that scans a codebase for coding patterns that you want to change and automates the process of transforming them. Although it is a common practice in large tech companies, it is still an underutilized tool in general, either due to lack of awareness or because the need to operate on an Abstract Syntax Tree (AST) can be intimidating for many developers. In this article, we’ll explore how to use codemod, its benefits, and limitations, so you can start using it to automate refactoring and make your codebase more manageable.

How Codemod is Used

Using codemod is a fairly straightforward process, assuming a codemod has already been created:

  1. First, refer to the instructions provided by the creator and run a command in your project directory. For example, instruction to run codemod to migrate React Query to v4 can be found here. The command, usually executed using npx, downloads or executes the codemod code on your codebase and perform the necessary transformation.
  2. Once the codemod has completed running, verify the results by manually inspecting a few examples, running static-analysis tools like TypeScript or ESLint, or perform code formatting using tools like prettier. This part of the process is no different from typical workflow when making code changes.
  3. If unexpected results are found, such as certain coding patterns not being transformed or transformation causing bugs, discuss the issue with the codemod creator to determine if they are expected or if they are bugs. In the meantime, all changes made by the codemod can be reverted, as no changes have been committed/merged!
  4. If everything looks good and passes all CI checks, proceed to merge the code follow your typical workflow.

To better understand codemod, it’s helpful to compare it with another JavaScript tool, transpilation (usually using Babel):

  1. Transpilation is run every time your code is built, with the source code remains the same but the runtime code that ships to user being different. In contrast, with codemod, the source code is updated once. The result of codemod can be easily verified by any developer who can read the code, while the output of transpilation is usually minified and typically only verified by those who maintain the build tool and configuration.
  2. Transpilation must be exhaustive to handle the current and future use cases, as it remains as a part of build pipeline until it is removed. Meanwhile, codemod only needs to cover the current coding patterns, as it only changes the code once when it is run.

The Benefits and Limitations of Codemod

Codemod offers few benefits, including:

  • It saves efforts in refactoring large codebase, as it replaces the time required for manual refactoring with the time required to create the codemod, which is constant regardless of the size of codebase.
  • It makes refactoring reproducible. Codemod enforces consistency in refactoring, avoiding human error.
  • It shifts the burden of introducing breaking changes from the consumer to the creator. In companies where different teams create and use tools, codemod as part of toolkit forces tool creators to take responsibility for introducing breaking changes rather than requiring all tool users to absorb the impact of the change. This is similar to Google’s practice of having the team removing an old system responsible for deprecating its usages across other systems.

However, codemod also has some limitations:

  • It takes time to create, especially when doing it for the first time because new concepts, such as Abstract Syntax Tree (AST) must be learned in the process.
  • It cannot handle complex refactoring. Most codemods scan the code patterns on a per-file basis and perform transformation accordingly. Cross-file refactoring is usually not supported, and complicated migration such as migrating Angular to React are impossible with codemod alone.

See It in Action

To see a codemod in action, let’s use the previous article on TailwindCSS in Module Federation as an example, where we will transform the usage of twin.macro to a new tw utility function call.

  1. open this Stackblitz link, which will open a new window. You should see a Button component by default, which is similar to the snippet below. The parts that we are interested in and would like to transform are highlighted.

    tsx
    import * as React from 'react';
    import tw from 'twin.macro';
    interface ButtonProps {
    variant?: 'primary' | 'secondary';
    isSmall?: boolean;
    }
    const Button = React.forwardRef<
    HTMLButtonElement,
    ButtonProps & React.ComponentPropsWithoutRef<'button'>
    >(function Button({ variant, isSmall, className, ...props }, forwardedRef) {
    return (
    <button
    type="button"
    {...props}
    css={[
    // The common button styles
    tw`px-8 py-2 rounded transform duration-75`,
    // Use the variant grouping feature to add variants to multiple classes
    tw`hocus:(scale-105 text-yellow-400)`,
    // Use props to conditionally style your components
    variant === 'primary' && tw`bg-black text-white border-black`,
    // Combine regular css with tailwind classes within backticks
    variant === 'secondary' && tw`border-2 border-yellow-600`,
    // Conditional props can be added
    isSmall ? tw`text-sm` : tw`text-lg`,
    tw`text-white`,
    className,
    ]}
    ref={forwardedRef}
    />
    );
    });
    export default Button;
    tsx
    import * as React from 'react';
    import tw from 'twin.macro';
    interface ButtonProps {
    variant?: 'primary' | 'secondary';
    isSmall?: boolean;
    }
    const Button = React.forwardRef<
    HTMLButtonElement,
    ButtonProps & React.ComponentPropsWithoutRef<'button'>
    >(function Button({ variant, isSmall, className, ...props }, forwardedRef) {
    return (
    <button
    type="button"
    {...props}
    css={[
    // The common button styles
    tw`px-8 py-2 rounded transform duration-75`,
    // Use the variant grouping feature to add variants to multiple classes
    tw`hocus:(scale-105 text-yellow-400)`,
    // Use props to conditionally style your components
    variant === 'primary' && tw`bg-black text-white border-black`,
    // Combine regular css with tailwind classes within backticks
    variant === 'secondary' && tw`border-2 border-yellow-600`,
    // Conditional props can be added
    isSmall ? tw`text-sm` : tw`text-lg`,
    tw`text-white`,
    className,
    ]}
    ref={forwardedRef}
    />
    );
    });
    export default Button;
  2. Open a new terminal tab by clicking the ”+” button

    The + button is near the bottom of the editor

    In there, run the command

    bash
    npm run codemod
    bash
    npm run codemod

The button component will be transformed to:

tsx
import * as React from 'react';
import { tw } from '~/lib/tw';
interface ButtonProps {
variant?: 'primary' | 'secondary';
isSmall?: boolean;
}
const Button = React.forwardRef<
HTMLButtonElement,
ButtonProps & React.ComponentPropsWithoutRef<'button'>
>(function Button({ variant, isSmall, className, ...props }, forwardedRef) {
return (
<button
type="button"
{...props}
className={tw(
`px-8 py-2 rounded transform duration-75`,
`hocus:(scale-105 text-yellow-400)`,
// Use props to conditionally style your components
variant === 'primary' && `bg-black text-white border-black`,
// Combine regular css with tailwind classes within backticks
variant === 'secondary' && `border-2 border-yellow-600`,
// Conditional props can be added
isSmall ? `text-sm` : `text-lg`,
`text-white`,
className
)}
ref={forwardedRef}
/>
);
});
export default Button;
tsx
import * as React from 'react';
import { tw } from '~/lib/tw';
interface ButtonProps {
variant?: 'primary' | 'secondary';
isSmall?: boolean;
}
const Button = React.forwardRef<
HTMLButtonElement,
ButtonProps & React.ComponentPropsWithoutRef<'button'>
>(function Button({ variant, isSmall, className, ...props }, forwardedRef) {
return (
<button
type="button"
{...props}
className={tw(
`px-8 py-2 rounded transform duration-75`,
`hocus:(scale-105 text-yellow-400)`,
// Use props to conditionally style your components
variant === 'primary' && `bg-black text-white border-black`,
// Combine regular css with tailwind classes within backticks
variant === 'secondary' && `border-2 border-yellow-600`,
// Conditional props can be added
isSmall ? `text-sm` : `text-lg`,
`text-white`,
className
)}
ref={forwardedRef}
/>
);
});
export default Button;

How to Create a Codemod Yourself

If you would like to create a codemod yourself to automate refactoring, following are some resources that can help you get started:

  • jscodeshift is a popular library created by Facebook for creating codemod for JavaScript and TypeScript. The example shown above uses it.
  • The official jscodeshift documentation can be a bit dense and missing a lot details, but it is maintained by the library maintainer, so you should take a look as well.
  • CodeshiftCommunity is a site maintained by community about codemod, which includes tutorial and some handy tips.
  • My own notes on jscodeshift are a personal reference I created while learning to use the library. I found that the official documentation assumed a lot of knowledge and didn’t always provide clear examples, so I created my own guide to fill in some of the gaps.
  • The AST Explorer is a powerful tool for exploring the abstract syntax tree (AST) of your code and understanding how to navigate and manipulate it. This can be helpful when you’re trying to figure out how to write code that scans or transforms specific parts of your codebase.
  • If you’re looking for inspiration or examples of how to create codemods, there are many open-source codemods available on GitHub. Search for “codemod” and the programming language you’re interested in (e.g. “codemod javascript”) to find examples that might be helpful.

With these resources and some persistence, you can learn to create your own codemods and start automating repetitive coding tasks in your own projects.

Thanks for reading!

Love what you're reading? Sign up for my newsletter and stay up-to-date with my latest contents and projects.

    I won't send you spam or use it for other purposes.

    Unsubscribe at any time.