Dep Tree - a tool for rendering and linting your project's dependency tree in the terminal
Tags: javascript, typescript, opensource, go
Published: 2023-02-21 17:45:55

## Dep Tree

https://github.com/gabotechs/dep-tree

Coding is a team sport, and team members should agree on conventions and strategies for taking projects across the line.

For example, team members may agree on a specific code style, and configure linters for enforcing it, that way no one can’t get their code merged without first be compliant with the styling rules.

Team members could also choose to agree on strategies for managing dependencies, something like “files in folder X” should never depend on “anything under folder Y”. A clean dependency management is crucial for a project’s maintainability and scalability in the long term, but there is not that many tools for enforcing dependency higiene as there is for code style higiene.

Dep Tree aims to solve this, not only by allowing developers to visualize in an interactive way their project’s dependency tree, but also by providing a tool for enforcing some user-defined dependency rules. Something like a linter, but for dependencies.

## Dependency tree visualization

The first step for knowing how messy a project is, is by graphically visualizing it. Dep Tree allows rendering an interactive dependency tree in the terminal, which is super useful for understanding what pieces of a project depend on what, and what other things are correctly decoupled.

Take this project of mine as an example: https://github.com/gabotechs/react-gcode-viewer

That project is just a very simple React component library, and as usually, it has an entrypoint in `src/index.ts`. A very important thing to note, is that there is always an entrypoint, either the `index` file used for exposing a library, or the executable file with the program’s main function.

Running `dep-tree render src/index.ts` under that project’s root folder will render something like this in the terminal:

```
index.ts                                                                        
│                                                                               
└▷GCodeViewer/GCodeViewer.tsx                                                   
  │                                                                             
  ├▷GCodeViewer/GCodeModel.tsx                                                  
  └▷│GCodeViewer/gcode/reader/UrlReader.ts                                      
    ││                                                                          
    ├│▷GCodeViewer/GCodeLines.tsx                                               
    ├│▷│GCodeViewer/SceneElements/Camera.tsx                                    
    ├│▷│GCodeViewer/SceneElements/Floor.tsx                                     
    ├│▷│GCodeViewer/SceneElements/OrbitControls.tsx                             
    ├│▷│GCodeViewer/gcode/GCode.ts                                              
    │└▷││GCodeViewer/gcode/reader/base.ts                                       
    │  ││                                                                       
    │  │├─▷GCodeViewer/gcode/parser.ts                                          
    │  ││  │                                                                    
    └──┴┴──┴▷GCodeViewer/gcode/types.ts
```
> 💡 Dep Tree does not check third-party dependencies, it will only take into account the source code of the project, ignoring any dependency to a external library.

This is just copy-pasted from a terminal, but the truth is that users can navigate through the entries using the keyboard, and select specific entries for rendering their isolated dependency trees.

![Demo Gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2gzi39g0kz1sl4wfv2x.gif)

## Dependency rules check

Once users are already aware of how messy their project’s dependencies are, they may choose to fix it and enforce some dependency rules. In the same way that developers configure linters for checking that new code matches the expected style, they can define rules to ensure that new code does not introduce dependencies between parts of the application that are meant to be decoupled.

In Dep Tree, this rules are defined in a `.dep-tree.yml` file in the root of the project with the following structure:

```yaml
entrypoints:
  - src/index.ts
allow:
  "this-folder-can-only-depend-on.../**":
    - "this/*.ts"
    - "and-this/*.ts"
deny:
  "this-one-cannot-depend-on.../**":
    - "not-this/*.ts"
    - "neither-this/*.ts"
```

Some real life examples:

### ⚛ React `pages` and `components`

Imagine a React application, with a `pages` folder and a `components` folder. Probably, files under `pages` will use the reusable components defined in `components` for building whole pages, but `components` are just isolated testable components that should not depend on page-specific details:

```yaml
entrypoints:
  - src/app.tsx
deny:
  "src/components/**/*.tsx":
    - "src/pages/**"
```

running `dep-tree check` with this `.dep-tree.yml` file would fail if any `component` tries to depend on something that lives under the `pages` folder.

### 🔧 The classic `utils` folder

Who hasn’t dealt with this? a project that starts being a small thing, with some innocent helper functions that are placed under a `utils` folder. Then the project keeps growing, every new addition has its own “util” function or class, and because of the vague and generic meaning of the word “util”, everything can be considered a “util”. Some months later half of the project ends up living in a `utils` folder.

```yaml
entrypoints:
  - src/main.ts
allow:
  "src/utils/**":
    - "src/utils/**"
```

This config will limit files under `src/utils` to only import other files under `src/utils`. That way a “util” can’t depend on parts of the application with business logic important for a specific functionality, developers are forced to colocate things that are designed for functionality X under the folder designed for functionality X, and the `utils` folder is left just for some generic and basic helper functions.

### 🔄 Dependency inversion

Last letter of the SOLID principles and one of the most powerful tools in software development, but lets directly dive-in in a real world example:

An application that is in charge of managing users, that uses MySQL as a database, will probably have some MySQL client code under `src/mysql-client`, and some user management code under `src/users`. As the application is using MySQL for persisting user information, then the code under `src/users` will depend on `src/mysql-client` for storing the users.

The `src/users` high level module is depending on the `src/mysql-client` low level module, which has some serious impact in testability in isolation of infrastructure-specific details, or in other words, not needing a whole MySQL database running just for testing `src/users`.

Dependency inversion can help here by breaking the dependency between both modules, putting an interface in between, and making `src/mysql-client` depend on that interface. But explaining how dependency inversion work is out of the scope here, so let's see directly how Dep Tree can help here:

```yaml
entrypoints:
  - src/main.ts
deny:
  "src/users/**":
    - "src/mysql-client/**"
```

This will enforce the `src/users` module to not depend on MySQL-specific details.

Even better, if all the infrastructure-specific code is gathered under a `infra` package, the dependency banning can go even further:

```yaml
entrypoints:
  - src/main.ts
deny:
  "src/users/**":
    - "infra/**"
```

## Summary

Dep Tree aims to be another automated tool for helping in the maintainability of medium/large code bases. It helps to understand the dependency flow of an application and provides tools for validating it against the rules defined in a configuration file.

If you find it helpful, feel free to stop by the source repository https://github.com/gabotechs/dep-tree and give it a try, feedback is much appreciated!