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?:
- for newbies on the project
- to find unwanted dependencies
- 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);
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';
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"]
]
]
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';
The result is saved in proxyPathsWithJsx.json
file:
[
"components/Button/index.js",
[
["default", "components/Button/Button.js"]
]
]
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",
]
]
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:
- Test coverage for each file and component
- Size of your files and components
- Component names into files (if you use an approach with multiple components in one file)
- Something else
- 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)