Skip to content

Instantly share code, notes, and snippets.

@heloa-net
Forked from ryanflorence/folder-structure.md
Created March 30, 2017 21:37
Show Gist options
  • Save heloa-net/09913a92a472de27baac1efe5690ac3e to your computer and use it in GitHub Desktop.
Save heloa-net/09913a92a472de27baac1efe5690ac3e to your computer and use it in GitHub Desktop.

React Migration

Folder Structure

Motivations

  • Clear feature ownership
  • Module Usage Predictibility (refactoring, maintainence, etc)
  • CI runs only the tests that matter (future)
  • Code Splitting (future)

How it works

The file structure maps directly to the route hierarchy, which maps directly to the UI hierarchy.

It's inverted from the model that we've used in other systems. If we consider all folders being either a "generic" or a "feature" folder, we only have one "feature" folder but many "generic" folders.

Examples of "feature" folders:

  • Surveys
  • Admin
  • Users
  • Author

Examples of "generic" folders:

  • components
  • helpers
  • stores
  • actions

Given this route config:

var routes = (
  <Route name="App">
    <Route name="Admin">
      <Route name="Users"/>
      <Route name="Reports"/>
    </Route>
    <Route name="Course">
      <Route name="Assignments"/>
    </Route>
  </Route>
);

We would now set up our directories like this:

app
└── screens
    └── App
        └── screens
            ├── Admin
            │   └── screens
            │       ├── Reports
            │       └── Users
            └── Course
                └── screens
                    └── Assignments

Next, each of these screens has an index.js file, which is the file that handles the entry into the screen, also known as a "Route Handler" in react router. Its very much like a Route in Ember. We'll also have some top-level application bootstrapping stuff at the root, like config/routes.js.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── screens
│       │   ├── Admin
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       └── index.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       └── index.js
│       │       └── index.js
│       └── index.js
└── index.js

With this structure, each screen has its own directory to hold its modules.

In otherwords, we've introduced "scope" into our application file structure.

Each will probably have a components directory.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       ├── screens
│       │   ├── Admin
│       │   │   ├── components
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   ├── components
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       ├── components
│       │   │   │       └── index.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── components
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       ├── components
│       │       │       └── index.js
│       │       └── index.js
│       └── index.js
└── index.js

These components are used only in the current screens. So what about when you've got some shared components between screens?

Shared Modules

Every screen also has a "shared" generic directory. If its children share any components with each other or the parent, we put the shared code in "shared". Here is our growing app with some new shared, and not shared modules.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       ├── screens
│       │   ├── Admin
│       │   │   ├── components
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   ├── components
│       │   │   │   │   ├── stores
│       │   │   │   │   │   └── ReportsStore.js
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       ├── components
│       │   │   │       └── index.js
│       │   │   ├── shared
│       │   │   │   └── stores
│       │   │   │       ├── AccountStore.js
│       │   │   │       └── UserStore.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── components
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       ├── components
│       │       │       └── index.js
│       │       └── index.js
│       ├── shared
│       │   └── components
│       │       ├── Avatar.js
│       │       └── Icon.js
│       └── index.js
├── shared
│   └── util
│       └── createStore.js
└── index.js

Note Admin/shared; Reports and Users can both access the shared stores. Additionally, every screen in the app can use Avatar.js and Icon.js.

We put shared components in the nearest shared directory possible and move it toward the root as needed.

Shared module resolution

The way modules in common-js are resolved is pretty straightforward: its all relative from the current file.

There is one piece of "magic" in the way modules resolve. When you do a non-relative require like require('moment') the resolver will first try to find it in node_modules/moment. If its not there, it will look in ../node_modules/moment, and on up the tree until it finds it.

We've made it so that shared resolves the same way. This way you don't have to require('../../../../../../../../../../shared/Avatar') you can simply do require('components/Avatar') no matter where you are.

Tests

Tests live next to the modules they test. Tests for shared/createStore.js live in shared/__tests__/createStore.test.js.

Now our app has a bunch of __tests__ directories:

app
├── __tests__
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       │   ├── __tests__
│       │   │   └── AppView.test.js
│       │   └── AppView.js

... etc.

├── shared
│   └── util
│       ├── __tests__
│       │   └── createStore.test.js
│       └── createStore.js
└── index.js

Dev Workflow

Testing

Flux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment