Visualize react components tree
Tags: react, uml, components, visualize
Published: 2024-03-05 19:06:04

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](https://github.com/mallchel/react-to-uml)
- 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`](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#importdeclaration) - is used to gather imports that look like component imports within a file - [source](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/babelPlugin.ts#L88).

[`JSXOpeningElement`](https://babeljs.io/docs/babel-types#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](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/babelPlugin.ts#L123).

[`VariableDeclaration`](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#variabledeclaration) - saves all variable declaration nodes to the set by its name (`varDeclarationsByName`) - [source](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/babelPlugin.ts#L149).

[`ExportDeclaration`](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#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:

```ts
// VariableDeclaration
// 1. export const foo = varWithJsx;
// CallExpression
// 2. export default hoc(varWithJsx);
```
[source](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/babelPlugin.ts#L159)

#### 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](https://webpack.js.org/api/compiler-hooks/#done) and iterates all modules from compilation that exist in `connectionIdsByFileName` (gathered by babelPlugin)

[resolveConnectionProxyPath](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/webpackPlugin.ts#L430) - 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:**
```ts
// components/Button/Button.js
export const Button = () => {
...
};

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

// App.js
import { Button } from './components/Button';
```

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:
```ts
[
  "components/Button/index.js",
  [
    ["Button", "components/Button/Button.js"]
  ]
]
```

**Complicated example:**
```ts
// components/Button/Button.js
export const Button = () => {
...
};

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

// App.js
import Btn from './components/Button';
```

The result is saved in `proxyPathsWithJsx.json` file:
```ts
[
  "components/Button/index.js",
  [
    ["default", "components/Button/Button.js"]
  ]
]
```

[mapModuleConnectionIds](https://github.com/mallchel/react-to-uml/blob/0339cc50a0ebab3f9e15894212ce4fc5f1e7a0b4/src/webpackPlugin.ts#L233) - iterates by all imports in each module (file) to save the following result to the `outUmlFileName` file (it should have JSON extension):
```ts
[
  "/App.js",
  [
    "/Button.js",
  ]
]
```
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
```

**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