Skip to content

TheHypnoo/react-compiler-cli

react-compiler-cli

See what the React Compiler actually compiles — in milliseconds. A fast, Rust-powered auditor for projects adopting React Compiler. Zero config. Monorepo-aware. AI-friendly output. Ships with an MCP server so Claude, Cursor, and any other agent can audit your codebase directly.

npx react-compiler-cli ai .
compiler: enabled=true mode=Infer source=vite.config.ts
stats: 301f 397c | enabled=397
issues: 2
  src/App.tsx:7   Counter       [enabled] useMemo=1 useCallback=1
  src/App.tsx:17  MemoizedCard  [enabled] React.memo

One command. Every component. Nothing else to set up.


Why this exists

React Compiler is rolling out across real codebases, but “is the compiler actually handling this file?” is a surprisingly hard question. Between Vite, Next.js, Expo, Babel presets, "use no memo" directives, component-level opt-outs, and monorepos with mixed configs, the answer is often “it depends, and we don’t know.”

The only official answer today is react-compiler-marker — a Node CLI that runs Babel over every file. It’s accurate but slow, and its output is tuned for humans, not agents.

react-compiler-cli is built for the world where you (or an AI) audit compiler coverage every time you touch the code.

  • Fast by construction. Rust + oxc (the fastest JS/TS parser in any language) + rayon parallelism + mimalloc global allocator + a parallel file walker. Static coverage of a 462-file Vite app runs in ~22 ms on the author's M1 Pro — orders of magnitude below a Babel-based run on the same tree. Run bench/run.sh for numbers on your own hardware.
  • Zero config. Detects Vite, Next.js, Expo, Babel, SWC, package.json dependencies. Resolves compiler mode (infer / annotation / syntax / all) automatically.
  • Monorepo-native. Walks pnpm, npm/yarn/bun, Lerna, and Nx layouts (legacy workspace.json and modern project.json). Each workspace is audited under its own compiler config — no false negatives when only one app has the compiler on.
  • Token-efficient for agents. Ships a dedicated ai subcommand that emits ~1/10th the JSON of a full report, with only the actionable bits.
  • MCP server included. Any MCP-capable assistant can call scan, ai, doctor, or cleanup tools directly. Your agent audits your repo without ever piping JSON through its context window.
  • Cleanup mode. Once the compiler is on, manual useMemo / useCallback is redundant. react-compiler-cli cleanup --apply rewrites them back to plain expressions, safely — only where the unwrap is trivially valid.
  • Ground-truth --deep mode. Optionally shells out to the real babel-plugin-react-compiler to confirm static results and surface actual bailouts with reasons and spans.

Install

npm i -g react-compiler-cli
# or no install at all:
npx react-compiler-cli ai .

Prebuilt binaries ship for darwin-arm64, darwin-x64, linux-x64, linux-arm64, and win32-x64 via npm optionalDependencies — npm installs only the one matching your machine.

Prefer Rust directly?

cargo install --git https://github.com/TheHypnoo/react-compiler-cli

The four commands

scan — full report

react-compiler-cli scan . --format json

Emits the complete structured report: every file, every component, its status, manual memoization counts, directives, and workspace. JSON, text, or Markdown. Add --summary to drop per-file detail and --issues-only to keep only actionable components.

ai — compact digest for agents

react-compiler-cli ai .

One-screen Markdown (or --json) tuned for LLM input. Typical output is 10× smaller than the full report but preserves the 100 most actionable issues.

compiler: enabled=true mode=Infer source=package.json
stats: 1f 4c | enabled=4
issues: 2
  src/App.tsx:7  Counter       [enabled] useMemo=1 useCallback=1
  src/App.tsx:17 MemoizedCard  [enabled] React.memo

doctor — environment check

react-compiler-cli doctor

Verifies React Compiler is configured, lists workspaces, checks node_modules/babel-plugin-react-compiler (for --deep), and tells you what to fix.

cleanup — safe hand-memo removal

react-compiler-cli cleanup .          # preview
react-compiler-cli cleanup . --apply  # rewrite in place

Finds useMemo(() => X, …) and useCallback(fn, …) inside compiler-enabled components and suggests replacing the call with its argument. Only emits hunks where the unwrap is provably safe (single-expression bodies or a single return); anything with multiple statements, conditional hook order, or opt-out directives is left alone.


MCP server (AI agents)

react-compiler-cli installs a second binary, react-compiler-mcp, exposing scan / ai / doctor / cleanup as MCP tools.

Point Claude Desktop, Cursor, or any MCP client at it:

{
  "mcpServers": {
    "react-compiler": {
      "command": "npx",
      "args": ["-y", "react-compiler-mcp"]
    }
  }
}

Now your agent can ask “which components in apps/storefront would benefit from removing manual memoization?” and get an answer grounded in your actual code, not a guess.


What it detects

Config babel.config.*, .babelrc*, vite.config.*, next.config.*, package.json dependencies, Expo experiments.reactCompiler, Babel preset react-compiler
Modes infer, annotation, syntax, all, plus explicit disabled
Directives "use memo", "use no memo", "use forget", "use no forget", and gated forms like "use memo if(condition)"
Components Function declarations, arrow consts, React.memo(…), memo(…), forwardRef(…) wrappers — disambiguated via PascalCase + JSX return heuristic
Hooks useMemo, useCallback, useTransition counts per component; React.memo wrapping flag
Workspaces pnpm-workspace.yaml, package.json#workspaces (array and { packages } object forms)
Per-component status enabled · opted-out-file · opted-out-component · opted-in-component · disabled-project · bailed-out (with --deep)

--deep mode

Static analysis is accurate for coverage — whether the compiler processes a file — but can’t tell you whether a given component actually bails out at compile time. Add --deep and the CLI will invoke babel-plugin-react-compiler via Node to confirm and annotate each component:

react-compiler-cli scan . --deep --format json
{
  "name": "UserMenu",
  "status": "bailed-out",
  "deep": {
    "success": false,
    "reason": "CannotForgetCallExpression: Cannot infer dependencies…",
    "loc": { "start": { "line": 34, "col": 2 }, "end": { "line": 41, "col": 3 } }
  }
}

--deep requires babel-plugin-react-compiler to be installed in the project you’re auditing (not in the CLI). react-compiler-cli doctor tells you whether it’s available.


Example output

Monorepo audit (ai subcommand)

compiler: enabled=false (root)
workspaces: 7 (1 with compiler on)
  ✓ @acme/storefront     [vite.config.ts mode=Infer]  301f 397c enabled=397
  · @acme/dashboard      [disabled]                   260f 334c
  · @acme/landing        [disabled]                     5f   6c
  · @acme/backend-core   [disabled]                    10f  10c
stats: 576f 747c | enabled=397 disabled-project=350
issues: 29
  packages/storefront/src/.../Cart.tsx:42   CartList   [enabled] useMemo=1 useCallback=2
  ...

cleanup

2 suggestion(s) across 1 file(s)

src/App.tsx
  L8  UseMemo      Counter  → value * 2
  L9  UseCallback  Counter  → () => console.log(value)

How it works

project
  │
  ├─ config::detect        ← reads Babel/Vite/Next/Expo/package.json
  ├─ workspace::discover   ← resolves monorepo layout
  ├─ walker::collect       ← ignore(1) + globs → file list
  │
  ├─ analyzer (parallel via rayon)
  │   ├─ oxc_parser         ← AST
  │   ├─ directives         ← file + function-level pragmas
  │   ├─ components         ← Pascal + JSX return + wrappers
  │   ├─ memoization        ← useMemo/useCallback/React.memo counts
  │   └─ classify           ← final status per component
  │
  ├─ [optional] deep::enrich ← babel-plugin-react-compiler bridge
  ├─ report::build          ← merge everything into a Report
  └─ render (json|text|md|ai)

Every file is parsed exactly once, on a worker thread, with the same AST walker that powers Oxlint. No regex fallbacks on source code; no re-reading.


Benchmarks

Reproducible numbers on Apple M1 Pro, macOS 26.2, scanning a private Vite + React Router project (apps/storefront) with 462 JS/TS source files and the React Compiler enabled via Babel. Measured with hyperfine — 2 warmup runs + 15 timed runs. Reproduce on your own tree with bench/run.sh.

Command Work done Mean ± σ
react-compiler-cli scan --format json --summary Static coverage 22.3 ms ± 1.3 ms (15 runs)
react-compiler-cli scan --format json Static coverage + full per-file report 22.2 ms ± 1.3 ms (10 runs)
react-compiler-cli scan --deep --format json --summary Static coverage + real babel-plugin-react-compiler per file 5.55 s ± 0.10 s (3 runs)
react-compiler-marker --format json (reference) Babel-based compilation report 9.32 s ± 0.14 s (3 runs)

Three fair takeaways:

  • Default (static) vs. the reference tool ≈ 420× faster, because the work is different — we don't run Babel; we infer coverage from the AST. Use this for the every-save / every-PR case.
  • --deep vs. the reference tool ≈ 1.7× faster, apples-to-apples — both run babel-plugin-react-compiler on every component. The win here comes from running Babel in a pool of workers from a single Node process instead of cold-booting for each file.
  • Optimized vs. the previous release (thin-LTO, system allocator, sequential walker): 1.08× faster on --summary, 1.18× faster on full scan, and the binary is ~15% smaller. See the release notes for the allocator / LTO / walker changes.

Numbers will move with repo size and plugin version; what won't move is the shape — static is instant, --deep is bounded by Babel itself.


Exit codes

Code Meaning
0 success
1 --fail-on-issues requested and at least one opt-out / bail-out was found
2 execution error (bad path, IO failure, …)

Useful in CI: fail a PR when new manual opt-outs land without justification.


FAQ

Is this a replacement for react-compiler-marker? For coverage reports, yes — faster and with monorepo + MCP out of the box. For absolute ground truth on bailouts, use --deep, which runs the same Babel plugin under the hood.

Do I need Rust installed? No. npm ships prebuilt binaries for every common platform via optionalDependencies. Rust is only needed if you’re contributing or installing from source via cargo install.

Does it modify my code? Only cleanup --apply does, and only for trivially safe hunks. Everything else is read-only.

Does it send anything over the network? No. --deep shells out to a local Node process; nothing leaves the machine.


Contributing

git clone https://github.com/TheHypnoo/react-compiler-cli
cd react-compiler-cli
cargo test
cargo run -- ai tests/fixtures/vite-enabled

Fixture projects under tests/fixtures/ cover the main config flavors (Vite, Babel, Next, Expo, annotation mode, monorepo). Add a fixture when reporting a detection bug — it’s the fastest path to a fix.

License

Licensed under either of MIT or Apache-2.0 at your option.

© TheHypnoo

About

Fast Rust CLI that reports which React files and components are processed by React Compiler. Zero config. Monorepo-aware. AI-friendly. Ships with MCP server.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Security policy

Stars

Watchers

Forks

Packages