• Stars
    star
    577
  • Rank 77,363 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Extract React components and props usage from code.

CI

react-scanner

react-scanner statically 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.js file:

import React from "react";
import ReactDOM from "react-dom";
import {
  BasisProvider,
  defaultTheme,
  Container,
  Text,
  Link as BasisLink,
} from "basis";

function App() {
  return (
    <BasisProvider theme={defaultTheme}>
      <Container margin="4" hasBreakpointWidth>
        <Text textStyle="subtitle2">
          Want to know how your design system components are being used?
        </Text>
        <Text margin="4 0 0 0">
          Try{" "}
          <BasisLink href="https://github.com/moroshko/react-scanner" newTab>
            react-scanner
          </BasisLink>
        </Text>
      </Container>
    </BasisProvider>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

Running react-scanner on it will create the following JSON report:

Click to see it
{
  "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:

  • How often a cetrain component is used in your design system? (see count-components processor)
  • How often a certain prop in a given component is used? (see count-components-and-props processor)
  • Looking at some prop in a given component, what's the distribution of values used? (e.g. you might consider deprecating a certain value)

Once you have the result you are interested in, you can write it to a file or simply log it to the console.

Installation

npm install --save-dev react-scanner

Usage

npx react-scanner -c /path/to/react-scanner.config.js

Config file

Everything that react-scanner does 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-scanner with 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": {}
  }
}

Running programmatically

It is also possible to run the scanner programmatically. In this case, the config options should be passed directly to the run function.

import scanner from "react-scanner";

const output = await scanner.run(config);

Config options

Here are all the available config options:

Option Type Description
rootDir string The path to the root directory of your project.
If using a config file, this defaults to the config directory.
crawlFrom string The path of the directory to start crawling from.
Absolute or relative to the config file location.
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.
For 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.
globs array Only files matching these globs will be scanned. See here for glob syntax.
Default: ["**/!(*.test|*.spec).@(js|ts)?(x)"]
components object Components to report. Omit to report all components.
includeSubComponents boolean Whether to report subcomponents or not.
When false, Footer will be reported, but Footer.Content will not.
When true, Footer.Content will be reported, as well as Footer.Content.Legal, etc.
Default: false
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.
When omitted, this check is bypassed.
getComponentName function This function is called to determine the component name to be used in the report based on the import declaration.
Default: ({ imported, local, moduleName, importType }) => imported || local
getPropValue function Customize reporting for non-trivial prop values. See Customizing prop values treatment
processors array See Processors.
Default: ["count-components-and-props"]

Processors

Scanning the files results in a JSON report. Add processors to tell react-scanner what to do with this report.

Built-in processors

react-scanner comes 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.
Absolute or relative to the root directory.
When omitted, the result is printed out to the console.

Here are the built-in processors that react-scanner comes 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
          }
        }
      }
    ]
  }
}

Custom processors

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-props processor 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 componentName and component in it. Check the implementation of count-components-and-props for a usage example.
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.
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.

Customizing prop values treatment

When 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.

getPropValue

Using the getPropValue configuration parameter makes this possible.

type IGetPropValue = {
    /** The AST node */
    node: Node,
    componentName: string,
    propName: string,
    /** Pass the node back into this method for default handling of the prop value */
    defaultGetPropValue: (node: Node) => string
}
getPropValue({ node, componentName, propName, defaultGetPropValue }: IGetPropValue): string

Example

If 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:

const escodegen = require("escodegen-wallaby");

getPropValue: ({ node, propName, componentName, defaultGetPropValue }) => {
  if (componentName === "Input" && propName === "style") {
    if (node.type === "JSXExpressionContainer") {
      return escodegen.generate(node.expression);
    } else {
      return escodegen.generate(node);
    }
  } else {
    return defaultGetPropValue(node);
  }
};

License

MIT

More Repositories

1

react-autosuggest

WAI-ARIA compliant React autosuggest component
JavaScript
5,967
star
2

rxviz

Rx Visualizer - Animated playground for Rx Observables
JavaScript
1,574
star
3

accessible-colors

Automatically find the closest accessible color combination
JavaScript
471
star
4

autosuggest-highlight

Utilities for highlighting text in autosuggest and autocomplete components
JavaScript
300
star
5

react-autowhatever

Accessible rendering layer for Autosuggest and Autocomplete components
JavaScript
163
star
6

bs-blabla

BuckleScript `[@bs.blabla]` attributes explained with examples
154
star
7

shallow-equal

Minimalistic shallow equality check for arrays/objects
TypeScript
75
star
8

ionic-firebase-auth

Ionic app with Firebase user management
JavaScript
59
star
9

sliding-puzzle

Sliding puzzle built in Elm
JavaScript
52
star
10

elo.js

Minimalistic Javascript implementation of a Elo rating system
JavaScript
51
star
11

giant-piano

Minimal Javascript pagination utility
JavaScript
47
star
12

autosuggest-trie

Minimalistic trie implementation for autosuggest and autocomplete components
JavaScript
25
star
13

chessboard

Minimalistic Javascript implementation of a chess board UI.
JavaScript
13
star
14

analyze-deps-cli

Compare dependencies in package.json to the latest available versions.
JavaScript
13
star
15

react-baseline

Add baseline overlay to your React components
JavaScript
11
star
16

react-chessboard

React chess board component
JavaScript
9
star
17

ionic-firebase

Ionic app with Firebase user management
JavaScript
4
star
18

beepy

Easily track your Blood Pressure.
JavaScript
4
star
19

rotating-circle-illusion

JavaScript
3
star
20

extract-params

Extract parameters from a string based on a pattern
JavaScript
3
star
21

analyze-deps

Compare dependencies in package.json to the latest available versions.
JavaScript
2
star
22

interpolate-params

Interpolate params in a pattern
JavaScript
2
star
23

reason-react-tutorial

2
star
24

numbers-and-flowers

Visualization of rational numbers
JavaScript
2
star
25

misha-moroshko

Personal website
TypeScript
2
star
26

myplanner

JavaScript
1
star
27

aws-mock-store

Mock for moroshko/aws-s3-store and moroshko/aws-dynamodb-store
JavaScript
1
star
28

combination

JavaScript
1
star
29

color-contrast

JavaScript
1
star
30

zones-controller

TypeScript
1
star
31

2masters.com.au

TypeScript
1
star
32

cnnviz

Convolutional Neural Networks Playground
JavaScript
1
star