ts-morph-react
v1.0.6
Published
A collection of transformers for ts-morph to refactor react code
Readme
ts-morph-react
A powerful collection of AST transformers for ts-morph to automate React code refactoring. Enforce consistent code patterns, modernize your codebase, and enforce best practices with declarative, composable transformers.
Features
- 🔄 Enforce Direct Exports - Convert separate export statements to direct exports on function declarations
- 🎯 Function Components - Automatically convert function declarations to arrow function components with proper typing
- 📦 Named Imports - Transform default imports to named imports for consistency
- 🎨 Code Formatting - Format code and organize imports according to your style guide (using ESLint, Prettier or Typescript Language Featues)
- ⚡ Composable - Mix and match transformers to create your refactoring pipeline
- 🛡️ Type-Safe - Built with TypeScript for a fully typed experience
- 🎭 AST-Powered - Leverage ts-morph for precise, reliable code transformations
Installation
npm install ts-morph-react
# or
pnpm add ts-morph-react
# or
yarn add ts-morph-reactQuick Start
As a Library
import { Project } from 'ts-morph'
import { transform } from 'ts-morph-react'
const project = new Project()
const sourceFile = project.addSourceFileAtPath('src/Button.tsx')
// Run transformers with your configuration
await transform(sourceFile, {
enforceDirectExports: true,
enforceFunctionComponent: true,
enforceNamedImports: true,
enforceFormat: true,
enforcePrettier: true,
enforceEslint: true,
enforceLineSeparation: true
})
// Save changes
await sourceFile.save()Transformers
enforceDirectExports
Converts separate export statement to direct exports.
❌ Before:
function Button<ButtonProps>({ label }) {
return <button>{label}</button>
}
export { Button }✅ After:
export function Button(props) {
return <button>{props.label}</button>
}enforceFunctionComponent
Converts plain function components to properly typed React.FunctionComponent components, preserving prop types.
❌ Before:
function Button(props: ButtonProps) {
return <button>{props.label}</button>
}✅ After:
const Button: React.FunctionComponent<ButtonProps> = (props) => {
return <button>{props.label}</button>
}enforceNamedImports
Transforms default imports to named imports for better tree-shaking and consistency.
❌ Before:
import * as React from 'react'
export const Button: React.FunctionComponent<ButtonProps> = ({ label }) => {
return <button>{label}</button>
}✅ After:
import { FunctionComponent } from 'react'
export const Button: FunctionComponent<ButtonProps> = ({ label }) => {
return <button>{label}</button>
}enforceEslint / enforcePrettier / enforceFormat
Formats code and organizes imports according to your style guide. Respects all standard TypeScript formatting options, eslint rules and prettier options.
Usage:
import { transform } from 'ts-morph-react'
await transform(sourceFile, {
enforceFormat: true,
enforcePrettier: true,
enforceEslint: true,
enforceLineSeparation: true,
format: {
indentSize: 2,
convertTabsToSpaces: true,
semicolons: ts.SemicolonPreference.Remove
},
eslint: {
'@stylistic/quotes': ['error', 'single']
},
prettier: {
semi: false,
singleQuote: true
}
})❌ Before:
import * as React from 'react';
import { Text } from '@/components/Text';
export const Button: React.FunctionComponent<ButtonProps> = ({
label
}) => {
return <button><Text>{label}</Text></button>;
};✅ After:
import * as React from 'react'
import { Text } from '@/components/Text'
export const Button: React.FunctionComponent<ButtonProps> = ({ label }) => {
return <button><Text>{label}</Text></button>
}API Reference
transform(sourceFile: SourceFile, config?: TransformerConfig)
Applies transformers to a source file.
interface TransformerConfig {
enforceDirectExports: boolean
enforceFunctionComponent: boolean
enforceNamedImports: boolean
enforceFormat: boolean
enforceLineSeparation: boolean
enforceEslint: boolean
enforcePrettier: boolean
format: {
baseIndentSize: number
convertTabsToSpaces: boolean
ensureNewLineAtEndOfFile: boolean
indentMultiLineObjectLiteralBeginningOnBlankLine: boolean
indentSize: number
indentStyle: IndentStyle
indentSwitchCase: boolean
insertSpaceAfterCommaDelimiter: boolean
insertSpaceAfterConstructor: boolean
insertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean
insertSpaceAfterKeywordsInControlFlowStatements: boolean
insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: boolean
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: boolean
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: boolean
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean
insertSpaceAfterSemicolonInForStatements: boolean
insertSpaceAfterTypeAssertion: boolean
insertSpaceBeforeAndAfterBinaryOperators: boolean
insertSpaceBeforeFunctionParenthesis: boolean
insertSpaceBeforeTypeAnnotation: boolean
newLineCharacter: string
placeOpenBraceOnNewLineForControlBlocks: boolean
placeOpenBraceOnNewLineForFunctions: boolean
semicolons: SemicolonPreference
tabSize: number
trimTrailingWhitespace: boolean
},
eslint: {
'@stylistic/*': ['error'],
'@typescript-eslint/*': ['error']
},
prettier: {
semi: false,
singleQuote: true,
jsxSingleQuote: true,
arrowParens: 'always',
bracketSameLine: true,
objectWrap: 'collapse',
printWidth: 120
}
}Development
# Build the library
pnpm build
# Watch mode during development
pnpm watch
# Run tests
pnpm test
# Watch mode for tests
pnpm test:watch
# Run tests with UI
pnpm test:ui
# Lint and type-check
pnpm lint
# Clean build artifacts
pnpm cleanTesting
The project uses vitest with snapshot testing to ensure transformer behavior is consistent and intentional:
# Run tests once
pnpm test
# Run in watch mode
pnpm test:watch
# Update snapshots after intentional changes
pnpm test -- -u
# Run specific test file
pnpm test enforceFormatWhen to Use ts-morph-react
✅ Good for:
- Enforcing code patterns across your codebase
- Large-scale refactoring of React components
- Automating style guide compliance
- One-time migrations (class → function components, etc.)
- Building custom code generators and linters
❌ Not ideal for:
- Real-time code formatting (use Prettier for that)
- Rename refactoring with complex scope analysis (use your IDE)
- Performance-critical transformations of very large codebases
Under the Hood
ts-morph-react is built on top of ts-morph, a fantastic library that provides a fluent API for manipulating TypeScript ASTs. If you need lower-level control, you can access the ts-morph APIs directly.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © Tobias Strebitzer
See Also
- ts-morph - The underlying AST manipulation library
- TypeScript Compiler API - For deeper TypeScript understanding
