Best Practices
Learn how to write effective codemods by following best practices.
What is a Codemod? A codemod is an automated script used to refactor large codebases efficiently. It applies consistent transformations across multiple files, ensuring accuracy and reducing manual effort. Codemods are particularly useful for:
- API migrations
- Code deprecations
- Style and pattern enforcement
- Large-scale refactors
If you've never written a codemod before, check out the Your First Codemod guide.
Why Best Practices Matter
Writing effective codemods ensures:
- Reviewability: Changes are clear, focused and can be reviewed easily.
- Debuggability: Isolating transformations helps in identifying issues.
- Safety: Reducing unintended side effects prevents breaking changes.
- Maintainability: Code remains readable and easy to update.
Planning Your Codemod
Define the Transformation Goal
Before writing a codemod, clearly define:
- What code pattern needs to change?
- Why is this change necessary?
- How can the transformation be validated?
Examples:
- Goal: Convert
componentWillReceiveProps
lifecycle method togetDerivedStateFromProps
in React class components. - Goal: Migrate from
lodash
to native JavaScript methods for performance improvements. - Goal: Upgrade React v18 to v19
Understand the Existing Codebase
Analyze code patterns before implementation:
- Identify common variations in how a feature is used.
- Ensure that special cases and edge cases are handled.
- Run static analysis tools to gather metrics on affected files.
Writing Effective Codemods
Make Atomic Changes
Each codemod should focus on a single transformation. This simplifies debugging and enables safer incremental application.
✅ Example (Converting var
to const
):
function transform(fileInfo, api) { const j = api.jscodeshift; return j(fileInfo.source) .findVariableDeclarators() .filter(path => path.parent.value.kind === "var") .forEach(path => path.parent.value.kind = "const") .toSource(); };
Avoid Unnecessary Modifications
Ensure that only required transformations are applied. Avoid whitespace or formatting changes unless essential.
✅ Example (Updating function calls without affecting spacing):
.find(j.CallExpression, { callee: { name: "oldMethod" } }) .replaceWith(path => j.callExpression(j.identifier("newMethod"), path.value.arguments));
Minimize Breaking Changes
When writing codemods, preserve existing behavior and avoid unintended side effects.
✅ Example (Avoiding breakage in optional arguments):
.find(j.CallExpression, { callee: { name: "fetchData" } }) .replaceWith(path => { const [url, options] = path.value.arguments; return j.callExpression(j.identifier("fetchDataV2"), [url, options || j.objectExpression([])]); });
Use Test-Driven Development (TDD)
Write tests before implementing the codemod to validate expected output.
✅ Example (Using Jest to test a jscodeshift transform):
const defineTest = require("jscodeshift/dist/testUtils").defineTest; defineTest(__dirname, "convert-var-to-const", null, "basic");
Implementation Best Practices
Use a Robust AST-Based Tool
Leverage Abstract Syntax Tree (AST) tools to precisely manipulate code:
- jscodeshift – Best for JavaScript and TypeScript codemods.
- ast-grep – A powerful pattern-matching tool for structured code changes.
- Babel Parser – Used internally by many JavaScript transformation tools.
Write Idempotent Codemods
Codemods should be safe to run multiple times without changing already transformed code.
✅ Example (Checking for existing changes before modifying):
.find(j.CallExpression, { callee: { name: "fetchDataV2" } }) .filter(path => path.value.callee.name !== "fetchDataV2") .replaceWith(...);
Support Dry Runs & Rollbacks
- Allow previewing changes before applying them (
--dry
flag in jscodeshift). - Store backups of modified files.
- Keep changes reversible by using version control effectively.
Composability of Codemods
Codemods should be designed to compose together for complex refactors.
Chaining Multiple Transformations
Instead of writing large monolithic codemods, break them into smaller ones that can be combined.
✅ Example (Chaining two separate transforms):
jscodeshift -t transform1.js src && jscodeshift -t transform2.js src
✅ Example (Piping transformations in a single script):
const transform = (fileInfo, api) => { return transform2({ source: transform1(fileInfo, api) }, api); };
Using Middleware Approaches
- Pass AST modifications through sequentially applied transforms.
- Use separate transforms for renaming variables, updating method signatures, and adjusting imports.
Validating and Deploying Codemods
Manually Test on a Small Subset
Before applying codemods at scale:
- Test against a few selected files.
- Verify correctness with unit tests.
- Review output diffs manually.
Run Against a Large Codebase in Phases
- Apply codemods to non-critical files first.
- Gradually expand scope based on validation.
- Monitor for regressions.
Monitor and Collect Feedback
- Capture logs of changed files.
- Gather developer feedback on the changes.
- Iterate and refine codemods based on findings.
Conclusion
Key Takeaways
- Write atomic, idempotent codemods.
- Use AST-based tools for accurate transformations.
- Test codemods before deployment.
- Ensure composability for complex migrations.
By following these best practices, you can develop robust, maintainable codemods that simplify large-scale refactoring while minimizing risk.