How to structure the application

Generic structure

The root directory follows a pretty standard structure for frontend apps.

front
├── dist/                # Output of the build, can be directly deployed
├── node_modules/
├── src/                 # Source code (mainly .js files, in ES7+JSX)
├── static
│   ├── styles/          # We use inline-style, so only a reset.css and the fonts declaration
│   ├── images/
│   ├── fonts/
│   └── ...static libs   # Because sadly, not every libs are available on npm yet...
├── tests                # /!\ Actually not the tests, but the tests launchers, configurations, utils (global)
│   ├── karma/
│   ├── mocha/
│   └── utils/
├── index.html
└── ...webpack files

The main difference with the conventional structure is that we don't write our tests in the root directory but directly next to the components/modules we want to test (following the Jest convention). This allows easier refactoring, by enabling us to move a component around (or even move it to an external repository) by simply moving its folder. Our tests folder at the project root is only here to host tests launchers, configurations and global testing methods.

The structure inside src

Let's now focus on what is in our src! In any case, don't worry too early about the structure : our experience has provent that no matter the initial choices concerning the structure, it can often be changed later and without too much hassle. As said earlier, all files related to a specific component are stored inside this component's folder, as a simple atomic structure. In this part, we'll focus first on what we have put inside of the component atom, then, we'll see how we glued everything together.

The component Atom

To build a component, we try to follow a fractal pattern : the component's subcomponents have their __docs__ and __tests__ too. This makes the structure grow downwards as we add features to a component.

Having atomic components folders enables easier refactorings : when moving a folder around, it reduces the number of imports we have to track down and edit accordingly.

Note : The __docs__ are folders containing live documentation for our components, meant to be discovered and displayed in our project's component library by a tool we developped called react-doks. Check it out ! We have found it really useful.

...
├── MyComponent
    ├── __docs__/              # The "living" documentation of our component
    ├── __tests__/             # The tests of the component
    ├── duck.js                # If the component needs to use the Redux store, it has its own reducer 
    ├── index.js               # We use index.js to export the component
    ├── SimpleSubComponent.js  # A simple subcomponent
    └── BiggerSubComponent     # As a subcomponent grows, we create its own folder
        ├── index.js             # Export BiggerSubComponent
        └── ...                  # It follows same structure as its parent

What does the content of src look like ?

Apart of the components and modules that naturally climbed to the top because they are used in a lot of places in the application, we will also find directly in the src folder:

  • services (by which we mean the functions that will call remote webservices)
  • the theme (by being global, the root is the best place)
  • utils
  • constants
  • flow types

One of the very early choices was to create global folders like components, constants, types, utils that will be used often by pages. It prevents code duplication for simple tasks.

src
├── __docs__/     # generic docs and docs helpers (mocks, wrappers...)
├── __tests__/    # global tests and test helpers (mocks, assertion helpers...)
├── components/   # global app components, often "dumb" and quite small, used multiple times in the app
├── constants/    # speaks for itself
├── pages/        # Major part of our app
├── services/     # files exporting functions to interact with remote APIs
├── types/        # Flow type definitions
├── utils/        # global app functions like input validator, redirectors, url helpers...
├── App.js        # our main react component, basically just wrapping content and mounting a notification module
├── index.js      # simple index, importing routes/reducers, create store and mount redux provider
├── reducers.js   # exports the list of reducers combined (or a function to filter our reducers, used in docs)
├── routes.js     # exports react-router routes definitions
├── store.js      # exports redux store creation function, and imports all reducers
└── themes.js     # our global theme file, providing style constants (like colors)

The basic purpose of the pages directory is to regroup and organize all react components linked to the router.

The pages folder structure

After many iterations, we recently decided that we should re-structure our pages folder. There is multiple reasons to our choice, such as the simplicity of maintenance, or the ease to integrate a new developer on the project.
To illustrate our new structure, let's take a simple example.
The app is a simple marketplace with a page listing articles, a page with article details, a page for the cart, a user profile page and a login page

pages
├── Article
│   ├── List
│   │   ├── __docs__/
│   │   ├── __tests__/
│   │   ├── duck.js
│   │   └── index.js
│   └── Details
│       ├── __docs__/
│       ├── __tests__/
│       ├── duck.js
│       └── index.js
├── User
│   ├── Login
│   │   ├── __docs__/
│   │   ├── __tests__/
│   │   └── index.js
│   └── Profile
│       ├── __docs__/
│       ├── __tests__/
│       ├── duck.js
│       └── index.js
└── Cart
    ├── __docs__/
    ├── __tests__/
    ├── duck.js
    └── index.js

Each folder at the root of pages/ is an independent section of the app.
Basically, in this example we have 5 main components

  • Article/List
  • Article/Details
  • Cart
  • User/Login
  • User/Profile

You may note that this makes the pages folder structure roughly equivalent to the routing schema linking to each of the pages. This is the way we found out was easier to reason about, and kept us from eternally hesitating about the location in which a new page should be implemented.

Making refactorings easier, one component at a time

When your app grows, you may face problems like we did, such as the mess in the pages directory. For example, at the beginning, we used to create components at the root of the pages folder. This is not a good practice, as it can quickly become annoying to look for components. That is how we found out the rule explicited above : when moving a component upwards in the tree, put it in the src/components directory instead of putting it at the root of src/pages.

The golden rule to ensure our folders are organized nicely : don't import any component that's not located directly under the current file or above the current file, in the directory structure. The only exception to this rule is, of course, importing a component located under src/components from a component located under src/pages.

So, what's the problem ? It's a bit hard to maintain and risky. When working inside a component's folder, you often assume a file is only used in the current folder, and not imported anywhere else. By making some major changes to a sub-component, you may break another part of your app without noticing, because the component is not at the right place. The corollary of this rule is : the higher you're in the directory tree, the more careful you must be when moving things around. See the end of this article to avoid this issue.

If you need to import a component that is located somewhere you shouldn't be peeking, the solution is simple : move it up, ideally inside the common ancestor folder of the folder it is located in, and the one you're currently working in.

You may wonder how we found out this rule. The answer is simple : refactoring (the hard way, cleaning the mess we created when we were less experimented). The typical cases of refactorings (be it moving code across files or moving files inside our folder structure) that benefit from these rules are "vertical" and "lateral" moves (in regards of the tree structure of the application folders).

  • Moving upwards : when a subcomponent needs to be shared with other components, it moves upwards, and becomes a first-class citizen ;
  • Moving "laterally" : when a page's subcomponent has been lifted so much that we feel like it should be at the root of the pages folder, we move it to src/components, to make it obvious that it is meant to be used by the whole app.

One last thing we used to make refactorings easier is to use aliases, which you may read about in the next article.

Questions we often asked ourselves

Where do I create a sub-component?

That depends on:

  1. The size of the sub-component
    • If containing multiple small components, create it directly in its own folder
  2. Will it be re-used by other components ?
    • If it will, create it in the highest common tree ancestor of the components using it
  3. Do you feel like it will probably be used everywhere in the application ?
    • This one can be tricky to know in advance: if you're creating a global component such as a HelpButton component, it's smarter to create it directly at the top level (in src/components)

What if I have too many sub-components?

We sometimes created a sub-folder called components/ where we move the sub-components to limit the amount of files in a single folder, but this was not an automatic reflex :

Here is an example:

...
├── myComponent
    ├── ...
    └── components
        ├── SimpleSubComponent.js  # A simple sub component
        └── BiggerSubComponent  # As a sub component grows, we create its own folder
            ├── index.js  # Export BiggerSubComponent
            └── ...  # It follows same structure as its parent

TL;DR

Make it clear, keep it simple.

Your application structure has to be easily readable and understandable.

results matching ""

    No results matching ""