npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fluentui/react-compiler-analyzer

v0.0.3

Published

CLI for assessing React Compiler coverage, directives, and migration risks in TypeScript source files.

Readme

@fluentui/react-compiler-analyzer

Understand how React Compiler handles your TypeScript components and hooks before changing your build. Inspect compiler coverage, review existing memoization, and check that compiler directives still do what you intended.

| Command | Use it to | | --------- | ------------------------------------------------------------------------------------------------------------ | | analyze | Inspect coverage and manual-memo migration candidates; optionally detect runtime risks or insert directives. | | lint | Validate 'use memo' and 'use no memo' directives, locally or in CI. | | init | Create a repository configuration, with an optional Agent Skill installation. |

This is a command-line tool, not a React runtime dependency. There is no public JavaScript API. Compiler acceptance is not a guarantee of runtime safety or a prediction of performance gains. Outcomes come from Babel's React Compiler plugin, not the native Rust/OXC/SWC compiler. Parsing the same syntax does not establish equal eligibility, bailouts, emitted caches, or runtime exports.

Quick start

Requires Node.js 22.14.0 or newer within 22.x, or Node.js 24.x. Install the published package as a development dependency:

npm install --save-dev @fluentui/react-compiler-analyzer

To run the CLI from this repository, see local development.

Create a config without installing agent tooling, then inspect your source:

npx react-compiler-analyzer init --yes --no-skill
npx react-compiler-analyzer analyze ./src --mode infer --verbose

For directive lint, select the mode your build actually uses:

npx react-compiler-analyzer lint ./src --mode annotation

Neither scan command modifies source unless you explicitly pass analyze --annotate or lint --fix. Review the diff and run your application's tests after either operation.

Reference and guides

Upgrading

See the version migration guide for release-specific changes, compatibility considerations, and upgrade instructions.

Configuration

analyze and lint read stable defaults from an optional rca.config.json in the current working directory. Use --config <path> to select a different file. RCA does not search parent directories. When no implicit config exists, the built-in defaults apply.

Use init for the minimal first-time config:

{
  "$schema": "./node_modules/@fluentui/react-compiler-analyzer/rca.config.schema.json",
  "mode": "infer",
  "format": "cli",
  "exclude": [
    "**/node_modules/**",
    "**/*.d.ts",
    "**/__tests__/**",
    "**/testing/**",
    "**/__mocks__/**",
    "**/*.spec.*",
    "**/*.test.*",
    "**/*.stories.*",
    "**/*.cy.*",
    "**/*.e2e.*",
    "**/e2e/**"
  ]
}

init offers infer and annotation, with infer selected by default because analyze is the primary workflow. It also persists the analyzer's current default excludes, including dependency directories, declarations, Cypress, and E2E files and directories. It does not offer all. Re-running it preserves custom excludes and other valid advanced settings already present in the file. An invalid existing config requires confirmation, or --yes --force in a non-interactive environment.

{
  "$schema": "./node_modules/@fluentui/react-compiler-analyzer/rca.config.schema.json",
  "mode": "infer",
  "concurrency": 10,
  "exclude": ["**/__tests__/**", "**/*.test.*"],
  "parserPlugins": ["decorators-legacy"],
  "analyze": {
    "quote": "single",
    "risks": {
      "detectGetStateReads": true,
      "storeAccessorPattern": "Store$",
      "selectorHookProperties": ["use"]
    }
  }
}

The shipped rca.config.schema.json provides editor validation and is also used for runtime validation. Unknown keys, invalid nested values, and malformed storeAccessorPattern regexes fail at config load before scanning. Explicit CLI options override config values. JSON uses camelCase (strictPaths, parserPlugins), while CLI flags retain kebab-case (--strict-paths, --parser-plugin).

Scan paths remain required positional arguments. Source-writing operations are intentionally not persistent configuration: use --annotate and --fix explicitly on each run.

| Config key | Type | Default or meaning | | -------------------------------------- | ---------- | ---------------------------------------------------------------------------------- | | $schema | string | Optional schema reference for editors. | | mode | string | infer; also accepts annotation, all. | | format | string | cli; also accepts md, html, json. | | verbose | boolean | false. | | concurrency | integer | 10; minimum 1. | | exclude | string[] | Replaces the default directory-scan exclusions. | | strictPaths | boolean | false. | | parserPlugins | string[] | []; extra Babel parser plugins. | | analyze.quote | string | single; also accepts double. | | analyze.risks.detectGetStateReads | boolean | false; detect imperative .getState() snapshots. | | analyze.risks.storeAccessorPattern | string | Unset; regex matching non-hook store-accessor names. | | analyze.risks.selectorHookProperties | string[] | []; property names identifying hidden selector hooks. | | analyze.risks.resolveWrappers | boolean | false; follow first-party wrapper calls and re-exports. | | analyze.risks.pathAliases.baseUrl | string | Required with pathAliases; relative to the config file. | | analyze.risks.pathAliases.paths | object | Required with baseUrl; alias patterns map to nonempty arrays of target patterns. |

Optional agent integration

The npm package ships the canonical skill at skills/react-compiler-analyzer. By default, init copies it to the consuming repository's .agents/skills/react-compiler-analyzer directory. Copilot and other Agent Skills-compatible tools can then discover it from the repository rather than from node_modules.

The skill is not required to use the CLI. The persisted output default remains human-friendly cli; automation can select --format json explicitly.

The repository copy is never silently overwritten. Identical content is a no-op; a different copy requires interactive confirmation or --force. Use --no-skill to initialize only the config:

react-compiler-analyzer init --no-skill

After adding or updating the skill, reload active agent sessions (for GitHub Copilot CLI, /skills reload).

Commands

init

react-compiler-analyzer init [options]

| Flag | Type | Default | Description | | ------------- | --------- | ------- | ------------------------------------------------------------------------- | | --config | string | (1) | Config file to create or update | | --yes, -y | boolean | false | Accept recommended defaults without prompting | | --force | boolean | false | Replace an invalid config or a differing installed skill | | --skill | boolean | true | Copy the packaged skill to the repository; use --no-skill to disable it |

(1) Defaults to ./rca.config.json.

Without --yes, init requires an interactive terminal. The config is replaced atomically, and cancelling the final confirmation leaves both config and skill unchanged.

lint

react-compiler-analyzer lint <paths..> [options]

Scans one or more files or directories for both 'use no memo' and 'use memo' directives. --mode controls what compilation strategy is assumed.

Status categories

| Status | Meaning | Exit code | | ------------- | ---------------------------------------------------------- | --------- | | redundant | Directive has no effect | 1 | | active | Directive is valid (compilable or intentionally opted out) | 0 | | broken | Opt-in compilation or an opt-out probe fails | 1 | | conflicting | Both 'use no memo' and 'use memo' on same function | 1 | | skipped | Has a justification comment, or is a file-level opt-out | 0 |

Files that cannot be parsed or compiled are reported separately and fail the lint gate, even if no directive status could be derived. --fix does not modify these files.

Options and fixes

| Flag | Type | Default | Description | | ------------------------ | --------- | ------- | ------------------------------------------------------------------------------------------------- | | --fix | boolean | false | Remove redundant opt-outs, justify active opt-outs, and resolve conflicts. | | --revalidate-justified | boolean | false | Recheck both kinds of justified function directive against Babel; never fix justified directives. |

All shared options apply. --fix removes redundant 'use no memo' directives, adds // justified: <reason> to active ones, and resolves conflicts by keeping 'use memo'. It does not repair broken opt-ins, remove file-level opt-outs, or change directives whose probes fail. JSON output describes the post-fix statuses and failures.

By default, directives with a // justified: comment are skipped. Use --revalidate-justified to recheck both directive kinds in the selected compiler mode. Redundant, broken, or conflicting results fail lint, but justified directives and their comments remain untouched even with --fix. File-level directives remain preserved. Results describe Babel acceptance, not runtime safety or removability in another build. See directive probing for implementation details.

| Directive and scenario | infer | annotation | | ----------------------------------------------- | ------------- | ------------- | | 'use no memo' on a compilable named component | active | redundant | | 'use no memo' on a non-compilable function | redundant | redundant | | File-level 'use no memo' | skipped | skipped | | Opt-out probe fails | broken | broken | | Both directives on one function | conflicting | conflicting | | 'use memo' on a compilable named component | active | active | | 'use memo' on a non-compilable function | broken | broken |

react-compiler-analyzer lint ./src --mode annotation
react-compiler-analyzer lint packages/button/src packages/menu/src --mode infer
react-compiler-analyzer lint ./src/Widget.tsx --verbose
react-compiler-analyzer lint ./src --fix
react-compiler-analyzer lint ./src --revalidate-justified

An empty risk report does not prove an opt-out is safe to remove. Review suppressed: true findings with the same risk config used when adding the opt-out. See the opt-out review workflow.

analyze

react-compiler-analyzer analyze <paths..> [options]

Reports accepted, skipped, and errored functions. Accepted functions are separated by emitted memo caches, no emitted cache, or unavailable statistics. Acceptance alone is not an optimization signal. The default report shows package and aggregate summaries; --verbose adds per-function results, candidate review details, risk findings, parse failures, and full code-framed diagnostics.

Completed scans exit 0 even with attributed per-function compiler errors or advisory risks. Parse failures produce a report but exit 1. Unattributed compiler errors and non-parse transform errors abort with code 1 before a JSON document or annotation writes are produced.

| Flag | Type | Default | Description | | ------------ | --------- | -------- | -------------------------------------------------------------------------------- | | --annotate | string | Not set | Write directives using a mode below. | | --quote | string | single | Quote style for written directives: single or double. | | --dry-run | boolean | false | Preview --annotate insertions without modifying source; requires --annotate. |

All shared options apply.

Use analyze --annotate <mode> --dry-run to list proposed insertion targets and counts without modifying source. Ordinary analyze is already read-only. Dry-run is not a persistent config setting.

| Annotation mode | Writes 'use memo' on | Writes 'use no memo' on | | --------------- | --------------------------------------------------------------------------- | ----------------------------------------- | | manual-memo | Accepted functions with detected useMemo, useCallback, or React.memo. | Nothing. | | all | Every accepted function. | Nothing. | | all-safe | Accepted functions without configured risks. | Accepted functions with configured risks. | | bailout-only | Nothing. | Accepted functions with configured risks. |

Annotations are inserted at the start of the function body. Existing directives are left unchanged; repeating the operation does not insert duplicates. Without risk rules, all-safe behaves like all, and bailout-only writes nothing. Neither is a runtime-safety proof.

Discover new candidates with --mode infer or --mode all; annotation considers only functions already carrying 'use memo'. The scan mode does not change your build configuration. For choosing annotations for your build mode, see the migration workflow.

Candidates are accepted functions with detected manual memoization and no opt-out. Detection includes nested memo/forwardRef wrappers and anonymous inner components. Custom comparators need separate review; the analyzer does not remove hooks or wrappers. Human-review candidate labels are not a performance ranking or part of the JSON API. See report interpretation.

react-compiler-analyzer analyze ./src --verbose
react-compiler-analyzer analyze packages/button/src packages/menu/src --mode infer
react-compiler-analyzer analyze ./src --mode infer --annotate manual-memo
react-compiler-analyzer analyze ./src --mode infer --annotate all-safe --quote double

Runtime-risk rules

Rules are disabled by default, advisory, and never change the exit code. Configure only patterns that match your application under analyze.risks.

| Rule | Pattern | Severity | | ------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------ | | nonreactive-store-read | Imperative .getState() snapshots or configured non-hook accessor reads. | high for .getState(), medium for configured accessors. | | hidden-selector-hook | Hidden hooks behind configured property chains, such as store.use.field(). | high. |

Optional chaining is supported. resolveWrappers follows first-party helpers and re-exports; pathAliases supplies config-relative aliases. Resolution is syntactic and stops at package boundaries and dynamic/inferred dispatch. Inspect stderr resolution diagnostics rather than assuming no findings means every import was resolved.

Reports distinguish compiled risks, risks suppressed by 'use no memo', and risks in functions not compiled. See runtime-risk detection for examples and limitations.

Shared options

| Argument / Flag | Type | Default | Description | | ----------------- | ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- | | <paths..> | string[] | — | Required. One or more files or directories to scan for .ts/.tsx files. Excludes are not applied to explicitly passed files | | --mode | string | infer | Compilation strategy: infer, annotation, or all. | | --format | string | "cli" | Output format: cli, md, html, or json (machine-readable) | | --verbose | boolean | false | Print detailed compiler events and full code-framed diagnostics | | --concurrency | number | 10 | Max parallel file processing | | --exclude | string[] | (1) | Glob patterns to exclude | | --strict-paths | boolean | false | Fail instead of warning when a given path does not exist | | --parser-plugin | string[] | [] | Extra Babel parser plugins, e.g. decorators-legacy. Match your build's parser config — see Not Analyzed | | --config | string | — | Use this RCA config instead of an optional ./rca.config.json |

(1) Default excludes: **/node_modules/**, **/*.d.ts, **/__tests__/**, **/testing/**, **/__mocks__/**, **/*.spec.*, **/*.test.*, **/*.stories.*, **/*.cy.*, **/*.e2e.*, **/e2e/**

--help displays command help, and --version displays the CLI version. --config is global and can precede the command. Table defaults are built-in values; config values override them, and explicit CLI options take precedence over the config.

Parser settings

JSX is enabled by default only for .tsx. The analyzer also accepts .ts angle-bracket assertions and TypeScript auto-accessors. Parsing a file does not guarantee React Compiler acceptance. See how it works for pipeline details.

Decorators require explicit configuration. If your build's loader enables extra syntax, pass the matching parser plugins:

react-compiler-analyzer analyze ./src --parser-plugin decorators-legacy

Unknown names are rejected. Babel ignores unrecognized parser plugins silently, so a typo would otherwise produce a run that looks successful and analyzed nothing extra.

Verbose human reports identify the resolved Babel and Babel React Compiler versions once. Compiler identity is not added to JSON output.

Missing paths

A path that does not exist is skipped with a warning rather than aborting the run, so a sparse checkout does not need a pre-filter step. The run only fails if no given path exists. A path that exists but is not a .ts/.tsx file is always fatal — that is a typo, not a missing checkout. Pass --strict-paths to fail on the first missing path instead.

Path resolution

<paths..> accepts any mix of directories and files, resolved independently per path:

| Path kind | Behavior | | ----------------- | --------------------------------------------------------------------------------------------------------- | | Directory | Recursively scanned for .ts/.tsx files. --exclude globs are applied. | | Explicit file | Analyzed as-is. --exclude globs are bypassed — naming a file selects it even if a glob excludes it. |

Files discovered across all paths are de-duplicated by absolute path, so overlapping arguments are safe — e.g. passing a directory together with a file inside it analyzes that file once, not twice. This also means a file explicitly named alongside its containing directory is never double-processed (or double-annotated/fixed).

# Directory (excludes apply) + two explicit files (excludes bypassed), all analyzed once
react-compiler-analyzer analyze \
  src/components/Foo/ \
  src/components/Bar.styles.ts \
  src/components/Baz.test.tsx   # explicitly named — analyzed despite the default *.test.* exclude

--format

Controls how reports are rendered:

| Value | Output | | ------ | ------------------------------------------------------------------------------------------------------- | | cli | Default. Terminal-friendly plain text — aligned columns, plain headings, no markdown/HTML noise. | | md | GitHub-flavored markdown — pipe tables, ## headings, and <details> blocks suitable for PR comments. | | html | Self-contained styled HTML document — embedded CSS, real tables, and a collapsible scan log. | | json | Machine-readable document on stdout; every diagnostic goes to stderr. |

Pass --format md when capturing output for a markdown destination (e.g. posting to a PR), or --format html for a shareable report you can open in a browser:

react-compiler-analyzer analyze ./library/src --format md > coverage-report.md
react-compiler-analyzer analyze ./library/src --format html > coverage-report.html

--format json

Emits a versioned document so results can be diffed, tracked, or fed to a dashboard without scraping text. When a report is produced, stdout carries only the document — the scan log and per-file diagnostics are redirected to stderr — so it pipes straight into a parser. A parse error is still represented in the document but exits 1; a fatal compiler/transform error emits no JSON:

summary.memoCacheEmitted is the direct machine-readable equivalent of the human Compiler accepted (memo cache emitted) metric. It counts only canonical compiled function rows whose reported memoStats.memoSlots is greater than zero.

| Collection | Fields and meaning | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | functions[] | file, package (string or null), line, column, function, status (compiled, skipped, error), and compilerEvent. Optional reason, errorLine, and errorColumn identify the terminal diagnostic separately from the function declaration. | | functions[].memoStats | Optional memoSlots, memoBlocks, memoValues, prunedMemoBlocks, and prunedMemoValues counters; an unreported counter is null, not 0. | | functions[].manualMemo | Optional useMemo, useCallback, and reactMemo counts, plus reactMemoHasComparator boolean. | | findings[] | file, package, line, column, function, ruleId, severity (high or medium), symbol, message, and compiled. Optional suppressed is true for risks held back by an opt-out and omitted otherwise. | | unparseable[] | file and error for each file the parser rejected. | | annotate | Present after annotation: mode, filesModified, functionsAnnotated, and functionsBailedOut. |

With --annotate <mode> --dry-run, actual annotation counters remain zero. The annotate object additionally contains dryRun: true and planned, whose filesModified, functionsAnnotated, and functionsBailedOut counters describe proposed changes. No planned metadata is emitted for ordinary analysis or actual writes. Dry-run JSON contains counts only; use human output for the list of proposed insertion targets.

The envelope is schemaVersion, tool, command, mode, summary, functions, findings, unparseable, and optional annotate. Summary counters are functions, compiled, memoCacheEmitted, skipped, errors, findings, findingsOnCompiled, findingsSuppressed, and unparseableFiles; findingsOnCompiled excludes suppressed findings.

react-compiler-analyzer analyze ./src --format json \
  | jq '.findings[] | select(.compiled and (.suppressed != true)) | {file, line, rule: .ruleId, severity}'
{
  "schemaVersion": 2,
  "tool": "react-compiler-analyzer",
  "command": "analyze",
  "mode": "infer",
  "summary": {
    "functions": 1,
    "compiled": 1,
    "memoCacheEmitted": 1,
    "skipped": 0,
    "errors": 0,
    "findings": 1,
    "findingsOnCompiled": 1,
    "findingsSuppressed": 0,
    "unparseableFiles": 0
  },
  "functions": [
    {
      "file": "src/Widget.tsx",
      "package": "app",
      "line": 6,
      "column": 7,
      "function": "Widget",
      "status": "compiled",
      "compilerEvent": "CompileSuccess",
      "memoStats": {
        "memoSlots": 2,
        "memoBlocks": null,
        "memoValues": 1,
        "prunedMemoBlocks": null,
        "prunedMemoValues": null
      }
    }
  ],
  "findings": [
    {
      "file": "src/Widget.tsx",
      "package": "app",
      "line": 7,
      "column": 13,
      "function": "Widget",
      "ruleId": "nonreactive-store-read",
      "severity": "medium",
      "symbol": "getAppStore",
      "message": "...",
      "compiled": true
    }
  ],
  "unparseable": []
}
  • Findings use compiled to distinguish risks on compiler-accepted functions from latent risks on failed/skipped functions. suppressed: true marks risks held back by 'use no memo'; the key is omitted for other findings.
  • Memo counters preserve compiler absence as null; a reported zero remains 0.
  • unparseable lists files the parser rejected outright. They contribute nothing to the other counts, so a shrinking functions total is never silently caused by a parse failure; a nonempty array also sets exit code 1.
  • Paths are workspace-relative and POSIX-separated.
  • Output is deterministically ordered, so equivalent runs produce byte-identical documents.
  • lint --format json emits the directive equivalent (command: "lint", a directives array, summary.unparseableFiles, and an unparseable array) and keeps its usual exit code. lint --format json --fix reports the post-fix directive statuses and failures.
  • Full code-framed compiler diagnostics remain available in human reports with --verbose; JSON stays compact and includes the terminal reason and location only.
  • --annotate still writes directives to disk under --format json when processing completes without a fatal compiler/transform error; the outcome is reported in an annotate key ({ mode, filesModified, functionsAnnotated, functionsBailedOut }).
  • rca.analyze.schema.json validates the compact analyze document.

For lint, the envelope is schemaVersion, tool, command: "lint", mode, summary, directives, and unparseable. Each directive row has file, package (possibly null), line, column, directive (use-memo or use-no-memo), status, compilerEvent, function, and optional reason. Summary counters are directives, active, redundant, broken, conflicting, skipped, and unparseableFiles. Unparseable entries contain file and error. Explicitly revalidated justified directives additionally include justified: true and revalidated: true.

Terminal colors honor NO_COLOR and FORCE_COLOR; Markdown is plain and HTML embeds its styles. HTML package navigation remains available even for packages without detailed sections. For compiler probes and rendering internals, see how it works.

Exit codes

| Command | Code 0 | Code 1 | | --------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | init | Successful initialization. | Invalid input, non-interactive use without --yes, or an operation failure. | | lint | No failing directives or file failures remain. | Redundant, broken, or conflicting directives; parse/compiler failures; invalid input. | | analyze | Completed scan without parse failures; attributed function errors and advisory risks may still be present. | Parse failures, unattributed compiler errors, non-parse transform errors, or invalid input. |

Nx integration

// project.json
{
  "targets": {
    "lint-compiler": {
      "command": "react-compiler-analyzer lint ./library/src --mode annotation"
    },
    "analyze-compiler": {
      "command": "react-compiler-analyzer analyze ./library/src --mode infer"
    },
    "analyze-compiler-multi": {
      "command": "react-compiler-analyzer analyze packages/pkg-a/src packages/pkg-b/src --mode infer"
    }
  }
}

Known upstream issues

Defects found during rollout that live in the React Compiler toolchain rather than in this analyzer. Each write-up carries a minimal reproduction and the exact versions it was verified against.

| Issue | Affects | Write-up | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | A useState initializer is outlined to module scope while still referencing the enclosing function's parameter, emitting an unbound identifier. Compiles as CompileSuccess; fails with ReferenceError at mount. | [email protected] | docs/upstream-react-compiler-outlined-closure.md | | The loader appends its own parser plugins after the user's, so jsx cannot be disabled and a .ts angle-bracket cast (<Foo>bar) fails the build. | [email protected] | docs/upstream-react-compiler-webpack-parser-plugins.md |

Further reading

Local development

From the Fluent UI repository root, build the workspace CLI before invoking its binary directly:

yarn nx run react-compiler-analyzer:build
yarn run -T react-compiler-analyzer analyze ./packages/react-components/react-button/library/src --verbose

The root dependency resolves to the workspace package rather than the published package. Generated Nx analyzer targets build it automatically.