This post is based on the series of posts: Modernizing a jQuery frontend with React. If you want to get a better overview of the motivation for this post we recommend you first read our initial post.

Nowadays, it is very easy to set up a small React application, or to start one from scratch. Especially if you use create-react-app. Most projects probably just need a few dependencies (for example, for state management and for internationalization) and a src folder with at least a components folder. I guess this is how most React projects start. Typically, though, as the project grows the number of dependencies, components, reducers and other shared utilities tends to increase in a, sometimes, not so controlled manner. What do you do when it is no longer clear why some dependencies are needed or how they work together? Or when you have so many components it becomes difficult to find the one you need? What do you do when you want to find a component but you don’t really remember its name?

These are just some examples of the questions we came across while rebuilding our frontend at Karify. We knew that the number of dependencies and components was eventually going to grow out of hand. That meant we needed a plan that would be scalable enough to keep up with future development. This plan involved defining conventions for our file and folder structure, code quality and also defining the overall architecture. Most importantly, all this should be easy to pick up by new developers without requiring them to have much insight into all our dependencies and code style.

At the moment of writing, we have about 1200 JavaScript files of which 350 are components with 80% of unit test coverage. Since we still believe in the architecture and conventions we created we thought it would be a good idea to share them. In the following sections, I’ll go through how we set up our project and also some of the lessons we learned.

How to organize files and folders?

We went through multiple phases until we figured out how we wanted to organize our React frontend. Initially, we thought of placing it in the same repository as our jQuery frontend. However, the imposed folder structure of our backend framework made this option undesirable. Next, we thought of moving it into a separate repository. This worked well at first, but with time we started thinking about creating other frontends, such as a React Native frontend, which motivated the need for a component library. This led us to split this new repository into two separate repositories: one for the component library and one for the new React frontend. Even though this seemed like a good idea, it resulted in a very complex review process. The relationship between changes in the two repositories became unclear. Finally, we chose to bring them together again into one repository, but this time as a monorepo.

We chose a monorepo because we wanted to create a separation between our component library and our frontend applications. The difference between our monorepo and others out there is that we don’t really need to publish the packages inside of it. For us, the packages serve only as a means for modularity and separation of concerns. It is especially useful to have different packages for every different application since we can specify different dependencies and scripts for each one of them.

We set up our monorepo using yarn workspaces with the following configuration in our root package.json:

"workspaces": [
    "app/*",
    "lib/*",
    "tool/*"
]

Now some of you might wonder why we didn’t simply use a packages folder like in other monorepos. Mainly because we wanted to create a separation between our applications and our component library. Besides that, we also knew we needed to create some tooling of our own. So, we came up with the folders you see above and here is an explanation for each one of them:

  • app: all packages in this folder refer to frontend applications like our Karify frontend, some internal frontends and also our Storybook;
  • lib: all packages in this folder expose shared utilities to our frontend applications and are as application-agnostic as possible. These packages basically form our component library. Some examples would be our typographymedia and primitive packages;
  • tool: all packages in this folder are Node.js specific and either expose tools that we created ourselves or are configuration and utilities for tools we depend on. Some examples would be webpack utilities, linter configurations, and a file system linter.

Regardless of where we place them, all our packages always have a src folder and optionally have a bin folder. The src folder of our app and lib packages might contain some of the following folders:

  • actions: contains action creator functions of which the return value can be passed to the dispatch function from redux or useReducer;
  • components: contains component folders with their respective definition, translations, unit tests, snapshots and stories (if applicable);
  • constants: holds constant values reused across different environments and also different utilities;
  • fetch: holds type definitions for the payloads from our API and the respective async actions to retrieve them;
  • helpers: holds utilities that do not fit in any of the other categories;
  • reducers: contains reducers to be used in our redux store or useReducer;
  • routes: holds route definitions to be used in react-router components or history functions;
  • selectors: contains helper functions that read or transform data from our redux state or our API payloads.

This folder structure allows us to write really modular code since it creates a clear separation of concerns between different concepts defined by our dependencies. This helps with the lookup of variables, functions, or components in the repository, independently of us knowing if they exist or not. Furthermore, it also helps keeping the contents of these folders to a minimum, which in turn makes them easier to process.

One challenge with this new folder structure is to make sure we stick to it. It is tempting to start creating different folders in different packages and organizing files differently between them. While that might not always be a bad idea we would end up with a mess if we don’t do it consistently. To help with that, we created a file system linter which I’ll describe in more detail in the next section.

#programming #typescript #react #tdd #javascript

How to Setup a Large Scale React application
13.10 GEEK