React Component & Prop Usage Scanner
react-scanner is a static analysis tool designed to extract React component and prop usage from source code, supporting both JavaScript and TypeScript. It operates by crawling a specified directory, identifying relevant files, and then parsing them to build a detailed JSON report of component instances and their associated prop values. The current stable version is 1.2.0, with a history of regular updates indicating active maintenance and feature development. Key differentiators include its static analysis approach, eliminating the need for runtime instrumentation, robust TypeScript support, and a flexible architecture that allows for custom processors to transform the raw JSON output into actionable insights, such as component usage counts or prop value distributions. It is primarily used for understanding the adoption and utilization patterns of design system components within a codebase.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'type')
cause This error often occurs in custom `getPropValue` or `getComponentName` functions when attempting to access properties of a `value` or `local` object that might be `null` or `undefined` for certain AST nodes.fixAdd nullish coalescing or optional chaining (`?.`) when accessing properties within your custom configuration functions, e.g., `value && value.type` or `value?.type`. -
Error: Configuration is not valid. 'rootDir' is required.
cause The `rootDir` property is missing or empty in the `react-scanner` configuration object. Since version 0.3.0, configurations are strictly validated.fixEnsure your configuration object explicitly includes a `rootDir` property pointing to the base directory for scanning, e.g., `{ rootDir: './src' }`.
Warnings
- breaking The `getComponentName` configuration option was introduced in version 0.5.0. If you were relying on previous implicit component name extraction, you must explicitly configure `getComponentName: ({ local }) => local` to maintain the old behavior.
- gotcha When no files are found to scan based on the provided configuration, `react-scanner` will now exit with an exit code of 1. This change, introduced in version 0.6.0, can cause CI/CD pipelines to fail if they expect a 0 exit code even for empty scan results.
Install
-
npm install react-scanner -
yarn add react-scanner -
pnpm add react-scanner
Imports
- scan
import { scan } from 'react-scanner';import scan from 'react-scanner';
- countComponents
import { countComponents } from 'react-scanner';import { countComponents } from 'react-scanner/processors'; - Config
import { Config } from 'react-scanner';import type { Config } from 'react-scanner';
Quickstart
import { mkdir, writeFile, rm } from 'node:fs/promises';
import { join } from 'node:path';
import scan from 'react-scanner';
async function runScanner() {
const tmpDir = join(process.cwd(), 'tmp-scanner-test');
await mkdir(tmpDir, { recursive: true });
const jsxContent = `
import React from 'react';
import { Button, Card } from './components';
function App() {
return (
<div>
<Button label="Click Me" onClick={() => console.log('clicked')} />
<Card title="Hello" description="This is a test card" />
<Button label="Submit" variant="primary" />
</div>
);
}
export default App;
`;
await writeFile(join(tmpDir, 'App.jsx'), jsxContent);
const componentsContent = `
import React from 'react';
export const Button = ({ label, onClick, variant }) => <button>{label}</button>;
export const Card = ({ title, description }) => <div><h3>{title}</h3><p>{description}</p></div>;
`;
await writeFile(join(tmpDir, 'components.js'), componentsContent);
const config = {
rootDir: tmpDir,
excludedPaths: [],
processors: [],
getComponentName: ({ local }) => local,
getPropValue: ({ value }) => value && value.type === 'JSXExpressionContainer' ? '(Identifier)' : value
};
try {
console.log('Scanning React components...');
const report = await scan(config);
console.log('Generated Report:\n', JSON.stringify(report, null, 2));
} catch (error) {
console.error('Error during scanning:', error);
} finally {
await rm(tmpDir, { recursive: true, force: true });
console.log(`Cleaned up temporary directory: ${tmpDir}`);
}
}
runScanner();