[go: up one dir, main page]

DEV Community

Sebastian Leukhin
Sebastian Leukhin

Posted on

Visualize react components tree

Hi, today I would like to announce my new library that solved my old idea to get a high view of my react component structure in the project.

When it is useful?:

  1. for newbies on the project
  2. to find unwanted dependencies
  3. to figure out what is happening in the nested subtrees of components

What is it?

  • It is living here on GitHub
  • Works with babel and webpack and consists of two plugins: babelPlugin and webpackPlugin
  • Generates a UML diagram during the build of your project

How it works?

babelPlugin - parsing AST

babelPlugin runs first and uses the AST of each file in your project that is accepted by filter with options packagesPaths and acceptableExts. This plugin uses visitor pattern to traverse AST and utilize the following built-in babel plugin methods:

ImportDeclaration - is used to gather imports that look like component imports within a file - source.

JSXOpeningElement - catches the render of the component and traverses to the parent until it finds the identifier node and after that saves it to the set of identifiers that has the JSX (varsWithJsx) - source.

VariableDeclaration - saves all variable declaration nodes to the set by its name (varDeclarationsByName) - source.

ExportDeclaration - catches the exports and tries to find the original identifier that has JSX by findVarWithJsx. It uses the saved varsWithJsx set and parses VariableDeclarations and CallExpressions to unwrap possible re-assignments like:

// VariableDeclaration
// 1. export const foo = varWithJsx;
// CallExpression
// 2. export default hoc(varWithJsx);
Enter fullscreen mode Exit fullscreen mode

source

Recap:

After the babelPlugin work, we have the JSON file with the maps connectionIdsByFileName (contains fileName -> the array of import names that seem to be component ones.) and exportedIdsWithJsxByFileName (fileName -> the array of exported identifiers that contain JSX).

webpackPlugin - traverse all modules

Runs on the compiler.hooks.done and iterates all modules from compilation that exist in connectionIdsByFileName (gathered by babelPlugin)

resolveConnectionProxyPath - is called for every module to organize the traverse by imported files and to link all modules without a kind of proxy file. The proxy file is the file with the re-export.

Example:

// components/Button/Button.js
export const Button = () => {
...
};

// components/Button/index.js
export { Button } from './Button';

// App.js
import { Button } from './components/Button';
Enter fullscreen mode Exit fullscreen mode

The index.js is a kind of proxy file that we omit and should find a direct link from the App.js to the Button.js file.
Another aspect of this process is that we need to match the component name from the App.js to the component name in the Button.js file.

The result is saved in proxyPathsWithJsx.json file:

[
  "components/Button/index.js",
  [
    ["Button", "components/Button/Button.js"]
  ]
]
Enter fullscreen mode Exit fullscreen mode

Complicated example:

// components/Button/Button.js
export const Button = () => {
...
};

// components/Button/index.js
export { Button as default } from './Button';

// App.js
import Btn from './components/Button';
Enter fullscreen mode Exit fullscreen mode

The result is saved in proxyPathsWithJsx.json file:

[
  "components/Button/index.js",
  [
    ["default", "components/Button/Button.js"]
  ]
]
Enter fullscreen mode Exit fullscreen mode

mapModuleConnectionIds - iterates by all imports in each module (file) to save the following result to the outUmlFileName file (it should have JSON extension):

[
  "/App.js",
  [
    "/Button.js",
  ]
]
Enter fullscreen mode Exit fullscreen mode

The result will be the same for both examples above with different re-exported names of the Button component.

Key ingredient

That's it, right now we have enough data to render the tree of our react components. But if you try to generate a UML diagram with all this data, on the real project it will result in a huge canvas and of course, it will be hard to read the information from it and get profit. Ok, imagine how you would solve this problem with many components. The main idea of a kind of diagram is to get a high-view picture of your system. I could not find any better solution without interactiveness than to omit some excess components from the diagram during the build time. I added the repetitiveCountForRemoveFromTree param to makePlugins (by default it is 4), and I just count the repeating of each component in the diagram and remove them if the number above or equal to the repetitiveCountForRemoveFromTree.

Final stage

Thus in the output we have a UML diagram only with the most valuable component names, they show the main structure of your app. Feel free to adjust the repetitiveCountForRemoveFromTree to an appropriate value for your app to get the desirable picture in the end.
So, try to render it:

java -jar plantuml-1.2023.11.jar ./build/assets/{outFileName}.uml -tsvg -verbose -t4
Enter fullscreen mode Exit fullscreen mode

Enjoy watching your app structure!

What is next?

Once you gathered the data from your code base, just imagine what you could do in the next step. This library right now just organizes the process of traverse, so it is possible to add logic into the library and gather extra data on the way like:

  1. Test coverage for each file and component
  2. Size of your files and components
  3. Component names into files (if you use an approach with multiple components in one file)
  4. Something else
  5. Render it with the interactive like d3.js with the real-time input search filter, and highlights of the arcs when you select one of the nodes

Top comments (0)