I’ve been using JavaScript and TypeScript in my personal projects for a long time now. Some of my more interesting projects end up accumulating countless new files as they evolve. The number of files increases becomes so unwieldy that I find myself struggling to locate exported functions. Does this sound familiar?
In this tutorial, we’ll show you how to restructure your JavaScript and TypeScript app according to the fractal tree concept using Destiny.
Destiny is an open-source project available on npm that scans folders for file dependencies and places the files in a fractal structure, where they are “destined” to be.
Consider a square. Now put two smaller squares on top of it, such that all three make a triangle in the center. Apply the same process to both smaller squares (and again, and again, depending on the number of files), and you have a fractal tree.
Destiny takes the same approach to structuring files. If a file has a dependency, it is moved to a folder with the same name as the file. It follows the principle that states, “Repeat the unit until all dependencies are structured, one below another.”
Fractal allows you to:
When multiple people are working on a single project, it’s sometimes difficult to get on the same page in terms of the file structure. Often, a programmer will simply search for all the dependencies in a project to understand the file structure format. The goal of Destiny is to create a common understanding of the file structure so development teams can work more efficiently together.
Below is the fractal representation of a file structure. index.js
is the main file and header.js
and footer.js
are the dependencies of that main file. Ideally, the dependencies are placed inside a folder named index
, which is the same as the main file name.
Destiny scans the selected folder and places files according to their dependencies. It follows the steps outlined below to restructure the dependencies.
To better understand how Destiny works, I created a project that demonstrates the file structure before and after using Destiny.
├── footer.js
├── header.js
├── index.js
├── loginButton.js
├── nav.js
└── search.js
As you can see, this file structure is a fiasco. No one could possibly understand how the dependencies exist in the project without going through all these files individually.
Now let’s take a look at our project’s dependency graph.
There are multiple dependencies organized in a multilevel hierarchy for our index.js
file. header.js
and footer.js
are dependencies of index.js
, and header.js
has dependencies of its own.
Now let’s see how Destiny transforms the file structure.
After creating a fractal structure using Destiny, our project will look as follows. The yellow box represents a folder with its name written inside.
This is how the files should be structured in a fractal manner.
Now let’s see what it looks like after applying Destiny to our project. You can simply traverse the file structure and understand the relationships and dependencies between the different components. For example, the index folder contains both a header and a footer. Furthermore, the header consists of a nav component that contains both a login button and a search bar. Easy, right?
├── index
│ ├── footer.js
│ ├── header
│ │ ├── nav
│ │ │ ├── loginButton.js
│ │ │ └── search.js
│ │ └── nav.js
│ └── header.js
└── index.js
It’s very easy to install and use Destiny in your project. If you use npm, write the following command to install Destiny globally.
npm i -g destiny
If you use yarn, use the following command.
yarn global add destiny
To use Destiny in a folder:
destiny <path to source files> # example destiny ./src
If you’re using Destiny in a React project, install Destiny and use the following command to scan all files and folders in your src
folder.
npx destiny "src/**/*.*"
The structure has not yet been applied with this command. To do so, you need a write
flag to apply the changes. You can either use - w
or --write
to apply the file structure changes.
npx destiny -w "src/**/*.*"
What if there is a certain file that is required for both header.js
and footer.js
but not index.js
?
Destiny counters this problem very easily. A new folder called shared
is created under the index
folder, generating the /index/shared
path. Here, the shared dependencies are located next to the highest dependent file.
In the above graph, header.js
and footer.js
have a dependency named _button.js
(placed inside curved brackets). This button.js
file must be moved to a new folder under the index folder.
After using Destiny, this is how the file structure should look:
As the graph shows, the fractal structure has been applied by Destiny and files have been placed accordingly.
This is what our folder tree looks like now. Below is the structure of a shared dependency project.
├── index
│ ├── footer.js
│ ├── header.js
│ └── shared
│ └── button.js
└── index.js
There are many other file structure formats out there, including:
The MVC pattern is a classic example of structuring your application. The user interacts with a view and manipulates data in the model via a controller. To close the loop, the model updates the view with the new data, which the user sees again. It’s a popular pattern among popular programming languages such as JavaScript, Python, PHP, Java, and C#.
In essence, when you organize by functionality, you organize files according to the functional areas of your application. For example, all functionalities related to handling emails go into an email
folder.
You can scale this approach further by grouping more specific functionalities within your higher-level functional folders. For example, let’s say you’re hosting a feature for sending and displaying emails. You can further divide the email
folder into a send
and receive
folder.
When you organize files by type, you get a very similar approach to MVC. A type can be a service, controller, domain, or any other type of file, such as a database model. However, as your application grows, you’re cound to accumulate services. Therefore, when organizing by type, you’ll end up organizing by feature within your type folders to keep your file structure clean.
Destiny was created by developer Ben Awad to manage a file structure for React applications. In the beginning, the tool was called butter-CLI before being changed to Destiny.
In most cases, when a developer starts a project, the project is tidy and well-structured — all files and folders are in their correct place and dependencies are managed properly. But as time passes, it becomes a much bigger effort to keep your file structure clean and tidy. You might move functionalities between components and create many shared folders. Often, you’ll end up with a whirlpool of files and their shared dependencies.
Destiny is designed to help you manage files on a large scale and make the file structure easy to understand for other developers working on your project. As of this writing, Destiny has a solid following with more than 2.7k stars on GitHub and 16 contributors.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.