Extract React components and props usage from code.
react-scannerstatically analyzes the given code (TypeScript supported) and extracts React components and props usage.
First, 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.
For example, let's say we have the following
index.jsfile:
import React from "react"; import ReactDOM from "react-dom"; import { BasisProvider, defaultTheme, Container, Text, Link as BasisLink, } from "basis";function App() { return ( Want to know how your design system components are being used? Try{" "} react-scanner ); }
ReactDOM.render(, document.getElementById("root"));
Running
react-scanneron it will create the following JSON report:
{
"BasisProvider": {
"instances": [
{
"importInfo": {
"imported": "BasisProvider",
"local": "BasisProvider",
"moduleName": "basis"
},
"props": {
"theme": "(Identifier)"
},
"propsSpread": false,
"location": {
"file": "/path/to/index.js",
"start": {
"line": 13,
"column": 5
}
}
}
]
},
"Container": {
"instances": [
{
"importInfo": {
"imported": "Container",
"local": "Container",
"moduleName": "basis"
},
"props": {
"margin": "4",
"hasBreakpointWidth": null
},
"propsSpread": false,
"location": {
"file": "/path/to/index.js",
"start": {
"line": 14,
"column": 7
}
}
}
]
},
"Text": {
"instances": [
{
"importInfo": {
"imported": "Text",
"local": "Text",
"moduleName": "basis"
},
"props": {
"textStyle": "subtitle2"
},
"propsSpread": false,
"location": {
"file": "/path/to/index.js",
"start": {
"line": 15,
"column": 9
}
}
},
{
"importInfo": {
"imported": "Text",
"local": "Text",
"moduleName": "basis"
},
"props": {
"margin": "4 0 0 0"
},
"propsSpread": false,
"location": {
"file": "/path/to/index.js",
"start": {
"line": 18,
"column": 9
}
}
}
]
},
"Link": {
"instances": [
{
"importInfo": {
"imported": "Link",
"local": "BasisLink",
"moduleName": "basis"
},
"props": {
"href": "https://github.com/moroshko/react-scanner",
"newTab": null
},
"propsSpread": false,
"location": {
"file": "/path/to/index.js",
"start": {
"line": 20,
"column": 11
}
}
}
]
}
}
This raw JSON report is used then to generate something that is useful to you. For example, you might want to know:
count-componentsprocessor)
count-components-and-propsprocessor)
Once you have the result you are interested in, you can write it to a file or simply log it to the console.
npm install --save-dev react-scanner
npx react-scanner -c /path/to/react-scanner.config.js
Everything that
react-scannerdoes is controlled by a config file.
The config file can be located anywhere and it must export an object like this:
module.exports = { crawlFrom: "./src", includeSubComponents: true, importedFrom: "basis", };
Running
react-scannerwith this config would output something like this to the console:
{ "Text": { "instances": 17, "props": { "margin": 6, "color": 4, "textStyle": 1 } }, "Button": { "instances": 10, "props": { "width": 10, "variant": 5, "type": 3 } }, "Footer": { "instances": 1, "props": {} } }
Here are all the available config options:
| Option | Type | Description | | ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
crawlFrom| string | The path of the directory to start crawling from.
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.
excludecan be a a function that accepts a directory name and should return
trueif the directory should be excluded from crawling. | |
globs| array | Only files matching these globs will be scanned. See here for glob syntax.
["**/!(*.test\|*.spec)[email protected](js\|ts)?(x)"]| |
components| object | Components to report. Omit to report all components. | |
includeSubComponents| boolean | Whether to report subcomponents or not.
false,
Footerwill be reported, but
Footer.Contentwill not.
true,
Footer.Contentwill be reported, as well as
Footer.Content.Legal, etc.
false| |
importedFrom| string or regex | Before reporting a component, we'll check if it's imported from a module name matching
importedFromand, only if there is a match, the component will be reported.
getComponentName| function | This function is called to determine the component name to be used in report based on the
importdeclaration.
({ imported, local, moduleName }) => imported || local| |
processors| array | See Processors.
["count-components-and-props"]|
Scanning the files results in a JSON report. Add processors to tell
react-scannerwhat to do with this report.
react-scannercomes with some ready to use processors.
To use a built-in processor, simply specify its name as a string, e.g.:
processors: ["count-components"]
You can also use a tuple form to pass options to a built-in processor, e.g.:
processors: [ ["count-components", { outputTo: "/path/to/my-report.json" }] ]
All the built-in processors support the following options:
| Option | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | |
outputTo| string | Where to output the result.
Here are the built-in processors that
react-scannercomes with:
count-components
Example output:
{ "Text": 10, "Button": 5, "Link": 3 }
count-components-and-props
Example output:
{ "Text": { "instances": 17, "props": { "margin": 6, "color": 4, "textStyle": 1 } }, "Button": { "instances": 10, "props": { "width": 10, "variant": 4, "type": 2 } }, "Footer": { "instances": 1, "props": {} } }
raw-report
Example output:
{ "Text": { "instances": [ { "props": { "textStyle": "subtitle2" }, "propsSpread": false, "location": { "file": "/path/to/file", "start": { "line": 9, "column": 9 } } }, { "props": { "margin": "4 0 0 0" }, "propsSpread": false, "location": { "file": "/path/to/file", "start": { "line": 12, "column": 9 } } } ] }, "Link": { "instances": [ { "props": { "href": "https://github.com/moroshko/react-scanner", "newTab": null }, "propsSpread": false, "location": { "file": "/path/to/file", "start": { "line": 14, "column": 11 } } } ] }, "Container": { "instances": [ { "props": { "margin": "4", "hasBreakpointWidth": null }, "propsSpread": false, "location": { "file": "/path/to/file", "start": { "line": 8, "column": 7 } } } ] } }
We saw above that built-in processors come in the form of a string or a tuple.
Custom processors are functions, and can be asynchronous!
If 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.
Here is an example of taking the output of the built-in
count-components-and-propsprocessor and sending it to your storage solution.
processors: [ "count-components-and-props", ({ prevResult }) => { return axios.post("/my/storage/solution", prevResult); } ]
Processor functions receive an object with the following keys in it:
| Key | Type | Description | | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
report| object | The raw JSON report. | |
prevResults| array | Previous processors results. | |
prevResult| any | The last item in
prevResults. Just for convenience. | |
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
componentNameand
componentin it. Check the implementation of
count-components-and-propsfor a usage example. | |
sortObjectKeysByValue| function | Helper function that sorts object keys by some function of the value. Check the implementation of
count-components-and-propsfor a usage example. | |
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. |
MIT