react-perf-analyzer 0.2.0

Static analysis CLI for React performance anti-patterns
react-perf-analyzer-0.2.0 is not a library.

react-perf-analyzer

Crates.io Downloads License: MIT CI

A high-performance Rust CLI that detects React performance anti-patterns in JS/TS/JSX files using deep AST analysis.

⚡ Powered by OXC — the fastest JS/TS parser in the ecosystem.

Built with OXC (Rust-native JS/TS parser), Rayon for parallel file processing, and clap for the CLI.


Rules

Rule What it detects
no_inline_jsx_fn Inline arrow/function expressions in JSX props — creates a new function reference on every render
large_component React components exceeding a configurable logical-line threshold
unstable_props Object/array literals in JSX props — creates a new reference on every render

Installation

From crates.io (recommended)

cargo install react-perf-analyzer

Build from source

git clone https://github.com/rashvish18/react-perf-analyzer
cd react-perf-analyzer
cargo build --release

The binary is at ./target/release/react-perf-analyzer.

Install locally from source

cargo install --path .

Then call react-perf-analyzer from anywhere.


Usage

react-perf-analyzer [OPTIONS] <PATH>

Arguments

Argument Description
<PATH> File or directory to scan (recursively)

Options

Flag Default Description
--format <text|json> text Output format
--max-component-lines <N> 300 Line threshold for large_component rule

Examples

Scan a single file

react-perf-analyzer src/components/UserCard.tsx

Scan a full project

react-perf-analyzer ./src

Scan an entire monorepo

react-perf-analyzer /path/to/monorepo

node_modules, dist, and build directories are automatically skipped.

Tune the component size threshold

react-perf-analyzer ./src --max-component-lines 150

JSON output (for CI or tooling)

react-perf-analyzer ./src --format json > results.json

Global install, then scan from anywhere

cargo install --path .
react-perf-analyzer /path/to/monorepo

Sample output

src/components/UserCard.tsx:14:17  warning  unstable_props     Object literal in 'style' prop creates a new reference on every render.
src/components/UserCard.tsx:21:24  warning  no_inline_jsx_fn   Inline arrow function in 'onClick' prop. Extract to a named handler or wrap with useCallback.
src/pages/Dashboard.tsx:1:1        warning  large_component    Component 'Dashboard' is 340 lines (310 logical) — limit is 300.

✖ 3 issues found

Scanned 42 file(s), found 3 issue(s).

Rule details

no_inline_jsx_fn

Detects inline functions passed as JSX props that create a new reference on every render, breaking React.memo and shouldComponentUpdate optimizations.

Detects:

  • Arrow functions: onClick={() => doSomething()}
  • Function expressions: onClick={function() { ... }}
  • Functions inside ternaries: onClick={flag ? () => a() : () => b()}
  • Functions inside logical expressions: onClick={enabled && (() => submit())}

Ignores:

  • useCallback-wrapped functions: onClick={useCallback(() => ..., [deps])}
  • useMemo-wrapped functions: render={useMemo(() => () => ..., [deps])}

Example fix:

// ❌ Before
<Button onClick={() => handleDelete(id)} />

// ✅ After
const handleDelete = useCallback(() => deleteItem(id), [id]);
<Button onClick={handleDelete} />

large_component

Flags React components whose logical line count (total lines minus blank lines and comments) exceeds the configured threshold. Counts logical lines to avoid penalizing well-commented code.

Reports:

  • Total lines, logical lines, blank lines
  • JSX element count
  • Hook call count
  • Context-aware suggestion (extract custom hook vs. split sub-components)

Detects all component forms:

  • function MyComponent() {}
  • const MyComponent = () => {}
  • export default function() {}
  • export const MyComponent = () => {}
  • memo()-wrapped components
  • forwardRef()-wrapped components

Example fix:

// ❌ Before — one 400-line component doing everything
function Dashboard() { /* ... */ }

// ✅ After — split by concern
function useDashboardData() { /* data-fetching hooks */ }
function DashboardHeader() { /* ... */ }
function DashboardContent() { /* ... */ }
function Dashboard() {
  const data = useDashboardData();
  return <><DashboardHeader /><DashboardContent data={data} /></>;
}

unstable_props

Detects object and array literals passed directly as JSX prop values. In JavaScript, {a:1} === {a:1} is false — a new reference is created on every render, defeating React.memo.

Detects:

  • Direct object literals: style={{ color: "red" }}
  • Direct array literals: columns={["id", "name"]}
  • Literals inside ternaries: options={flag ? { type: "bar" } : defaults}
  • Literals inside logical expressions: config={enabled && { dense: true }}
  • Parenthesized literals: style={({ color: "red" })}

Ignores:

  • useMemo-wrapped values: style={useMemo(() => ({ color }), [color])}
  • React.useMemo-wrapped values
  • Stable variable references

Example fix:

// ❌ Before
<UserCard style={{ color: "red", fontSize: 14 }} />
<DataTable columns={["id", "name", "email"]} />

// ✅ After — stable references
const COLUMNS = ["id", "name", "email"];
function UserCard({ color }) {
  const cardStyle = useMemo(() => ({ color, fontSize: 14 }), [color]);
  return <UserCard style={cardStyle} />;
}
<DataTable columns={COLUMNS} />

Exit codes

Code Meaning
0 No issues found
1 One or more issues found

Useful for CI pipelines:

react-perf-analyzer ./src || echo "Performance issues detected — failing build"

Architecture

src/
├── main.rs          # Entry point — Rayon parallel file pipeline
├── cli.rs           # clap CLI definitions
├── file_loader.rs   # Recursive file discovery (walkdir)
├── parser.rs        # OXC JS/TS/JSX parser wrapper
├── analyzer.rs      # Runs all rules against a parsed AST
├── reporter.rs      # Text and JSON output formatters
├── utils.rs         # Byte offset → line/column helper
└── rules/
    ├── mod.rs               # Rule trait, Issue/Severity types
    ├── no_inline_jsx_fn.rs  # Rule 1
    ├── large_component.rs   # Rule 2
    └── unstable_props.rs    # Rule 3

Key dependencies:

Crate Purpose
oxc_parser / oxc_ast / oxc_ast_visit Rust-native JS/TS/JSX parser and AST
rayon Data-parallel file processing
walkdir Recursive directory traversal
clap CLI argument parsing
serde / serde_json JSON output

Test fixtures

# Rule 1 — inline functions (12 issues expected)
react-perf-analyzer ./test_fixtures/inline_fn_cases.tsx

# Rule 2 — large components (5 issues expected)
react-perf-analyzer ./test_fixtures/large_component_cases.tsx --max-component-lines 20

# Rule 3 — unstable props (20 issues expected)
react-perf-analyzer ./test_fixtures/unstable_props_cases.tsx

# All rules — combined bad component (15 issues expected)
react-perf-analyzer ./test_fixtures/bad_component.tsx

# Zero issues — stable, well-written component
react-perf-analyzer ./test_fixtures/good_component.tsx