{"id":13393605,"url":"https://github.com/moroshko/react-scanner","last_synced_at":"2025-05-14T06:13:38.519Z","repository":{"id":39962391,"uuid":"282364168","full_name":"moroshko/react-scanner","owner":"moroshko","description":"Extract React components and props usage from code.","archived":false,"fork":false,"pushed_at":"2025-04-07T08:14:03.000Z","size":337,"stargazers_count":627,"open_issues_count":25,"forks_count":45,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-13T11:47:22.532Z","etag":null,"topics":["abstract-syntax-tree","components","jsx","react","stats","usage"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moroshko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-07-25T03:39:12.000Z","updated_at":"2025-05-06T15:45:19.000Z","dependencies_parsed_at":"2024-06-18T15:30:03.755Z","dependency_job_id":"50e8a73c-3911-4917-a20c-6a634d2630de","html_url":"https://github.com/moroshko/react-scanner","commit_stats":{"total_commits":83,"total_committers":14,"mean_commits":5.928571428571429,"dds":0.4819277108433735,"last_synced_commit":"6b787b353fa7e817d13b37f1221ae29553020795"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moroshko%2Freact-scanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moroshko%2Freact-scanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moroshko%2Freact-scanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moroshko%2Freact-scanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moroshko","download_url":"https://codeload.github.com/moroshko/react-scanner/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254083706,"owners_count":22011902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstract-syntax-tree","components","jsx","react","stats","usage"],"created_at":"2024-07-30T17:00:56.827Z","updated_at":"2025-05-14T06:13:38.470Z","avatar_url":"https://github.com/moroshko.png","language":"JavaScript","readme":"![CI](https://github.com/moroshko/react-scanner/workflows/CI/badge.svg)\n\n# react-scanner\n\n`react-scanner` statically analyzes the given code (TypeScript supported) and extracts React components and props usage.\n\nFirst, it crawls the given directory and compiles a list of files to be scanned. Then, it scans every file and extracts rendered components and their props into a JSON report.\n\nFor example, let's say we have the following `index.js` file:\n\n```jsx\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport {\n  BasisProvider,\n  defaultTheme,\n  Container,\n  Text,\n  Link as BasisLink,\n} from \"basis\";\n\nfunction App() {\n  return (\n    \u003cBasisProvider theme={defaultTheme}\u003e\n      \u003cContainer margin=\"4\" hasBreakpointWidth\u003e\n        \u003cText textStyle=\"subtitle2\"\u003e\n          Want to know how your design system components are being used?\n        \u003c/Text\u003e\n        \u003cText margin=\"4 0 0 0\"\u003e\n          Try{\" \"}\n          \u003cBasisLink href=\"https://github.com/moroshko/react-scanner\" newTab\u003e\n            react-scanner\n          \u003c/BasisLink\u003e\n        \u003c/Text\u003e\n      \u003c/Container\u003e\n    \u003c/BasisProvider\u003e\n  );\n}\n\nReactDOM.render(\u003cApp /\u003e, document.getElementById(\"root\"));\n```\n\nRunning `react-scanner` on it will create the following JSON report:\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to see it\u003c/summary\u003e\n\n```json\n{\n  \"BasisProvider\": {\n    \"instances\": [\n      {\n        \"importInfo\": {\n          \"imported\": \"BasisProvider\",\n          \"local\": \"BasisProvider\",\n          \"moduleName\": \"basis\"\n        },\n        \"props\": {\n          \"theme\": \"(Identifier)\"\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/index.js\",\n          \"start\": {\n            \"line\": 13,\n            \"column\": 5\n          }\n        }\n      }\n    ]\n  },\n  \"Container\": {\n    \"instances\": [\n      {\n        \"importInfo\": {\n          \"imported\": \"Container\",\n          \"local\": \"Container\",\n          \"moduleName\": \"basis\"\n        },\n        \"props\": {\n          \"margin\": \"4\",\n          \"hasBreakpointWidth\": null\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/index.js\",\n          \"start\": {\n            \"line\": 14,\n            \"column\": 7\n          }\n        }\n      }\n    ]\n  },\n  \"Text\": {\n    \"instances\": [\n      {\n        \"importInfo\": {\n          \"imported\": \"Text\",\n          \"local\": \"Text\",\n          \"moduleName\": \"basis\"\n        },\n        \"props\": {\n          \"textStyle\": \"subtitle2\"\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/index.js\",\n          \"start\": {\n            \"line\": 15,\n            \"column\": 9\n          }\n        }\n      },\n      {\n        \"importInfo\": {\n          \"imported\": \"Text\",\n          \"local\": \"Text\",\n          \"moduleName\": \"basis\"\n        },\n        \"props\": {\n          \"margin\": \"4 0 0 0\"\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/index.js\",\n          \"start\": {\n            \"line\": 18,\n            \"column\": 9\n          }\n        }\n      }\n    ]\n  },\n  \"Link\": {\n    \"instances\": [\n      {\n        \"importInfo\": {\n          \"imported\": \"Link\",\n          \"local\": \"BasisLink\",\n          \"moduleName\": \"basis\"\n        },\n        \"props\": {\n          \"href\": \"https://github.com/moroshko/react-scanner\",\n          \"newTab\": null\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/index.js\",\n          \"start\": {\n            \"line\": 20,\n            \"column\": 11\n          }\n        }\n      }\n    ]\n  }\n}\n```\n\n\u003c/details\u003e\n\nThis raw JSON report is used then to generate something that is useful to you. For example, you might want to know:\n\n- How often a cetrain component is used in your design system? (see [`count-components`](#count-components) processor)\n- How often a certain prop in a given component is used? (see [`count-components-and-props`](#count-components-and-props) processor)\n- Looking at some prop in a given component, what's the distribution of values used? (e.g. you might consider deprecating a certain value)\n\nOnce you have the result you are interested in, you can write it to a file or simply log it to the console.\n\n## Installation\n\n```\nnpm install --save-dev react-scanner\n```\n\n## Usage\n\n```\nnpx react-scanner -c /path/to/react-scanner.config.js\n```\n\n### Config file\n\nEverything that `react-scanner` does is controlled by a config file.\n\nThe config file can be located anywhere and it must export an object like this:\n\n```js\nmodule.exports = {\n  crawlFrom: \"./src\",\n  includeSubComponents: true,\n  importedFrom: \"basis\",\n};\n```\n\nRunning `react-scanner` with this config would output something like this to the console:\n\n```json\n{\n  \"Text\": {\n    \"instances\": 17,\n    \"props\": {\n      \"margin\": 6,\n      \"color\": 4,\n      \"textStyle\": 1\n    }\n  },\n  \"Button\": {\n    \"instances\": 10,\n    \"props\": {\n      \"width\": 10,\n      \"variant\": 5,\n      \"type\": 3\n    }\n  },\n  \"Footer\": {\n    \"instances\": 1,\n    \"props\": {}\n  }\n}\n```\n\n### Running programmatically\n\nIt is also possible to run the scanner programmatically. In this case, the config options should be passed directly to the `run` function.\n\n```js\nimport scanner from \"react-scanner\";\n\nconst output = await scanner.run(config);\n```\n\n## Config options\n\nHere are all the available config options:\n\n| Option                 | Type              | Description                                                                                                                                                                                                                                                                                                                                          |\n| ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `rootDir`              | string            | The path to the root directory of your project. \u003cbr\u003eIf using a config file, this defaults to the config directory.                                                                                                                                                                                                                                   |\n| `crawlFrom`            | string            | The path of the directory to start crawling from.\u003cbr\u003eAbsolute or relative to the config file location.                                                                                                                                                                                                                                               |\n| `exclude`              | array or function | Each array item should be a string or a regex. When crawling, if directory name matches exactly the string item or matches the regex item, it will be excluded from crawling.\u003cbr\u003eFor more complex scenarios, `exclude` can be a a function that accepts a directory name and should return `true` if the directory should be excluded from crawling. |\n| `globs`                | array             | Only files matching these globs will be scanned. See [here](https://github.com/micromatch/picomatch#globbing-features) for glob syntax.\u003cbr\u003eDefault: `[\"**/!(*.test\\|*.spec).@(js\\|ts)?(x)\"]`                                                                                                                                                         |\n| `components`           | object            | Components to report. Omit to report all components.                                                                                                                                                                                                                                                                                                 |\n| `includeSubComponents` | boolean           | Whether to report subcomponents or not.\u003cbr\u003eWhen `false`, `Footer` will be reported, but `Footer.Content` will not.\u003cbr\u003eWhen `true`, `Footer.Content` will be reported, as well as `Footer.Content.Legal`, etc.\u003cbr\u003eDefault: `false`                                                                                                                    |\n| `importedFrom`         | string or regex   | Before reporting a component, we'll check if it's imported from a module name matching `importedFrom` and, only if there is a match, the component will be reported.\u003cbr\u003eWhen omitted, this check is bypassed.                                                                                                                                        |\n| `getComponentName`     | function          | This function is called to determine the component name to be used in the report based on the `import` declaration.\u003cbr\u003eDefault: `({ imported, local, moduleName, importType }) =\u003e imported \\|\\| local`                                                                                                                                               |\n| `getPropValue`         | function          | Customize reporting for non-trivial prop values. See [Customizing prop values treatment](#customizing-prop-values-treatment)                                                                                                                                                                                                                         |\n| `processors`           | array             | See [Processors](#processors).\u003cbr\u003eDefault: `[\"count-components-and-props\"]`                                                                                                                                                                                                                                                                          |\n\n## Processors\n\nScanning the files results in a JSON report. Add processors to tell `react-scanner` what to do with this report.\n\n### Built-in processors\n\n`react-scanner` comes with some ready to use processors.\n\nTo use a built-in processor, simply specify its name as a string, e.g.:\n\n```\nprocessors: [\"count-components\"]\n```\n\nYou can also use a tuple form to pass options to a built-in processor, e.g.:\n\n```\nprocessors: [\n  [\"count-components\", { outputTo: \"/path/to/my-report.json\" }]\n]\n```\n\nAll the built-in processors support the following options:\n\n| Option     | Type   | Description                                                                                                                           |\n| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |\n| `outputTo` | string | Where to output the result.\u003cbr\u003eAbsolute or relative to the root directory.\u003cbr\u003eWhen omitted, the result is printed out to the console. |\n\nHere are the built-in processors that `react-scanner` comes with:\n\n#### `count-components`\n\nExample output:\n\n```json\n{\n  \"Text\": 10,\n  \"Button\": 5,\n  \"Link\": 3\n}\n```\n\n#### `count-components-and-props`\n\nExample output:\n\n```json\n{\n  \"Text\": {\n    \"instances\": 17,\n    \"props\": {\n      \"margin\": 6,\n      \"color\": 4,\n      \"textStyle\": 1\n    }\n  },\n  \"Button\": {\n    \"instances\": 10,\n    \"props\": {\n      \"width\": 10,\n      \"variant\": 4,\n      \"type\": 2\n    }\n  },\n  \"Footer\": {\n    \"instances\": 1,\n    \"props\": {}\n  }\n}\n```\n\n#### `raw-report`\n\nExample output:\n\n```json\n{\n  \"Text\": {\n    \"instances\": [\n      {\n        \"props\": {\n          \"textStyle\": \"subtitle2\"\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/file\",\n          \"start\": {\n            \"line\": 9,\n            \"column\": 9\n          }\n        }\n      },\n      {\n        \"props\": {\n          \"margin\": \"4 0 0 0\"\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/file\",\n          \"start\": {\n            \"line\": 12,\n            \"column\": 9\n          }\n        }\n      }\n    ]\n  },\n  \"Link\": {\n    \"instances\": [\n      {\n        \"props\": {\n          \"href\": \"https://github.com/moroshko/react-scanner\",\n          \"newTab\": null\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/file\",\n          \"start\": {\n            \"line\": 14,\n            \"column\": 11\n          }\n        }\n      }\n    ]\n  },\n  \"Container\": {\n    \"instances\": [\n      {\n        \"props\": {\n          \"margin\": \"4\",\n          \"hasBreakpointWidth\": null\n        },\n        \"propsSpread\": false,\n        \"location\": {\n          \"file\": \"/path/to/file\",\n          \"start\": {\n            \"line\": 8,\n            \"column\": 7\n          }\n        }\n      }\n    ]\n  }\n}\n```\n\n### Custom processors\n\nWe saw above that built-in processors come in the form of a string or a tuple.\n\nCustom processors are functions, and can be asynchronous!\n\nIf the processor function returns a `Promise`, it will be awaited before the next processor kicks in. This way, you can use previous processors results in your processor function.\n\nHere is an example of taking the output of the built-in `count-components-and-props` processor and sending it to your storage solution.\n\n```\nprocessors: [\n  \"count-components-and-props\",\n  ({ prevResult }) =\u003e {\n    return axios.post(\"/my/storage/solution\", prevResult);\n  }\n]\n```\n\nProcessor functions receive an object with the following keys in it:\n\n| Key                     | Type     | Description                                                                                                                                                                                                                                                                                                    |\n| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `report`                | object   | The raw JSON report.                                                                                                                                                                                                                                                                                           |\n| `prevResults`           | array    | Previous processors results.                                                                                                                                                                                                                                                                                   |\n| `prevResult`            | any      | The last item in `prevResults`. Just for convenience.                                                                                                                                                                                                                                                          |\n| `forEachComponent`      | function | Helper function to recursively traverse the raw JSON report. The function you pass in is called for every component in the report, and it gets an object with `componentName` and `component` in it. Check the implementation of `count-components-and-props` for a usage example.                             |\n| `sortObjectKeysByValue` | function | Helper function that sorts object keys by some function of the value. Check the implementation of `count-components-and-props` for a usage example.                                                                                                                                                            |\n| `output`                | function | Helper function that outputs the given data. Its first parameter is the data you want to output. The second parameter is the destination. When the second parameter is omitted, it outputs to the console. To output to the file system, pass an absolute path or a relative path to the config file location. |\n\n## Customizing prop values treatment\n\nWhen a primitive (strings, numbers, booleans, etc...) is passed as a prop value into a component, the raw report will display this literal value. However, when expressions or variables are passed as a prop value into a component, the raw report will display the AST type. In some instances, we may want to see the actual expression that was passed in.\n\n### getPropValue\n\nUsing the `getPropValue` configuration parameter makes this possible.\n\n```typescript\ntype IGetPropValue = {\n    /** The AST node */\n    node: Node,\n    componentName: string,\n    propName: string,\n    /** Pass the node back into this method for default handling of the prop value */\n    defaultGetPropValue: (node: Node) =\u003e string\n}\ngetPropValue({ node, componentName, propName, defaultGetPropValue }: IGetPropValue): string\n```\n\n### Example\n\nIf we were building out a design system, and wanted to see all the variations of a `style` prop that we passed into an `Input` component, we could do something like this:\n\n```javascript\nconst escodegen = require(\"escodegen-wallaby\");\n\ngetPropValue: ({ node, propName, componentName, defaultGetPropValue }) =\u003e {\n  if (componentName === \"Input\" \u0026\u0026 propName === \"style\") {\n    if (node.type === \"JSXExpressionContainer\") {\n      return escodegen.generate(node.expression);\n    } else {\n      return escodegen.generate(node);\n    }\n  } else {\n    return defaultGetPropValue(node);\n  }\n};\n```\n\n## License\n\nMIT\n","funding_links":[],"categories":["JavaScript","Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoroshko%2Freact-scanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoroshko%2Freact-scanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoroshko%2Freact-scanner/lists"}