Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Folder Structure

Motivations

  • Clear feature ownership
  • Module usage predictibility (refactoring, maintainence, you know what's shared, what's not, prevents accidental regressions, avoids huge directories of not-actually-reusable modules, 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 other words, 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 screen, not even the child 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 CommonJS are resolved is pretty straightforward in practice: 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 with webpack modulesDirectories. 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/util/createStore.js live in shared/util/__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

Why "Screens"?

The other option is "views", which has become a lot like "controller". What does it even mean? Screen seems pretty intuitive to me to mean "a specific screen in the app" and not something that is shared. It has the added benefit that there's no such thing as an "MSC" yet, so the word "screen" causes people to ask "what's a screen?" instead of assuming they know what a "view" is supposed to be.

@iamdustan

This comment has been minimized.

Show comment
Hide comment
@iamdustan

iamdustan Jan 20, 2015

Are you doing this with isomorphic rendering as well? How are you overriding node’s require to resolve components in the same way?

iamdustan commented Jan 20, 2015

Are you doing this with isomorphic rendering as well? How are you overriding node’s require to resolve components in the same way?

@kentcdodds

This comment has been minimized.

Show comment
Hide comment
@kentcdodds

kentcdodds Jan 20, 2015

Awesome Ryan. I love how you stimulate node_modules behavior using Webpack. I would love to see how this is accomplished and use it in my app.

Also, as a side note, you could enforce this structure using Webpack and ensure that nobody is requiring anything out of scope (throw an error otherwise). Sadly, I am unable to do this with angular. +1 for react's true modularity. Unless we migrate our app to react, we're stuck attempting to "enforce" this manually.

Anyway, this is great stuff Ryan.

kentcdodds commented Jan 20, 2015

Awesome Ryan. I love how you stimulate node_modules behavior using Webpack. I would love to see how this is accomplished and use it in my app.

Also, as a side note, you could enforce this structure using Webpack and ensure that nobody is requiring anything out of scope (throw an error otherwise). Sadly, I am unable to do this with angular. +1 for react's true modularity. Unless we migrate our app to react, we're stuck attempting to "enforce" this manually.

Anyway, this is great stuff Ryan.

@ryanflorence

This comment has been minimized.

Show comment
Hide comment
@ryanflorence

ryanflorence Jan 20, 2015

@kentcdodds

{
  modulesDirectories: ['shared', 'node_modules']
}

@iamdustan I haven't yet, but I would just use webpack --target node and have webpack build the server-side bundle also, which will give us the same module resolution.

Owner

ryanflorence commented Jan 20, 2015

@kentcdodds

{
  modulesDirectories: ['shared', 'node_modules']
}

@iamdustan I haven't yet, but I would just use webpack --target node and have webpack build the server-side bundle also, which will give us the same module resolution.

@nmn

This comment has been minimized.

Show comment
Hide comment
@nmn

nmn Jan 20, 2015

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

Does 'enhanced-require' solve this in node.js? Or is there some other way?

nmn commented Jan 20, 2015

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

Does 'enhanced-require' solve this in node.js? Or is there some other way?

@edygar

This comment has been minimized.

Show comment
Hide comment
@edygar

edygar Jan 21, 2015

This could turn out to be a very good Yeoman Generator, don't you think?

edygar commented Jan 21, 2015

This could turn out to be a very good Yeoman Generator, don't you think?

@ryanflorence

This comment has been minimized.

Show comment
Hide comment
@ryanflorence

ryanflorence Feb 6, 2015

Been using this for a while now in two projects, so far everybody seems to like it.

Owner

ryanflorence commented Feb 6, 2015

Been using this for a while now in two projects, so far everybody seems to like it.

@natew

This comment has been minimized.

Show comment
Hide comment
@natew

natew Feb 6, 2015

I've built something to do this, and am using it in a couple projects. You can see it here: https://github.com/reapp/reapp-routes

With usage here & here:
https://github.com/reapp/hacker-news-app/blob/master/app/routes.js
https://github.com/reapp/kitchen-sink/blob/master/app/routes.js

Edit just to say: I think it's a great pattern, and with webpack dynamic require you can see how ultra-simple routing becomes.

natew commented Feb 6, 2015

I've built something to do this, and am using it in a couple projects. You can see it here: https://github.com/reapp/reapp-routes

With usage here & here:
https://github.com/reapp/hacker-news-app/blob/master/app/routes.js
https://github.com/reapp/kitchen-sink/blob/master/app/routes.js

Edit just to say: I think it's a great pattern, and with webpack dynamic require you can see how ultra-simple routing becomes.

@dfkaye

This comment has been minimized.

Show comment
Hide comment
@dfkaye

dfkaye Feb 6, 2015

+1 for "avoids huge directories of not-actually-reusable modules"

that would be npm (hmmmmmmm, that's gonna leave a mark on twitter, I think....)

srsly, what issues have you run into with this? doesn't matching routes to files mean you have to alias the route in the URL (i.e., app/a/b/c as filepath should not map to app/a/b/c in a URL)?

dfkaye commented Feb 6, 2015

+1 for "avoids huge directories of not-actually-reusable modules"

that would be npm (hmmmmmmm, that's gonna leave a mark on twitter, I think....)

srsly, what issues have you run into with this? doesn't matching routes to files mean you have to alias the route in the URL (i.e., app/a/b/c as filepath should not map to app/a/b/c in a URL)?

@ryanflorence

This comment has been minimized.

Show comment
Hide comment
@ryanflorence

ryanflorence Feb 6, 2015

its all about the nesting of views, routes can be whatever you want with React Router

Owner

ryanflorence commented Feb 6, 2015

its all about the nesting of views, routes can be whatever you want with React Router

@Carlsson87

This comment has been minimized.

Show comment
Hide comment
@Carlsson87

Carlsson87 Feb 6, 2015

This is lovely. I guess it's time to learn about webpack..

Carlsson87 commented Feb 6, 2015

This is lovely. I guess it's time to learn about webpack..

@edygar

This comment has been minimized.

Show comment
Hide comment
@edygar

edygar Feb 14, 2015

Out of curiosity, how do you organize the styles? Do you separate them in another folder structure?

edygar commented Feb 14, 2015

Out of curiosity, how do you organize the styles? Do you separate them in another folder structure?

@asbjornenge

This comment has been minimized.

Show comment
Hide comment
@asbjornenge

asbjornenge Feb 24, 2015

YES, thanks for this!

asbjornenge commented Feb 24, 2015

YES, thanks for this!

@jfromell

This comment has been minimized.

Show comment
Hide comment
@jfromell

jfromell Mar 25, 2015

The thing that puts me off about this, otherwise awesome idea, is that in config/routes.js you have to import the route handlers. Seeing that screens are nested the import statements can be infinitely long.
eg. import Assignments from '../screens/App/screens/Course/screens/Assignments/index';
Is there a way to combat this?

jfromell commented Mar 25, 2015

The thing that puts me off about this, otherwise awesome idea, is that in config/routes.js you have to import the route handlers. Seeing that screens are nested the import statements can be infinitely long.
eg. import Assignments from '../screens/App/screens/Course/screens/Assignments/index';
Is there a way to combat this?

@kjs3

This comment has been minimized.

Show comment
Hide comment
@kjs3

kjs3 Mar 26, 2015

That was my only issue but honestly my deepest route is only two slashes deep. Not that this couldn't be an issue for other apps but it was just academic for me. I love the rest so I'm going with it for now.

I am introducing sub-directories in components though. Just to have some more organization within a single screen's components.

kjs3 commented Mar 26, 2015

That was my only issue but honestly my deepest route is only two slashes deep. Not that this couldn't be an issue for other apps but it was just academic for me. I love the rest so I'm going with it for now.

I am introducing sub-directories in components though. Just to have some more organization within a single screen's components.

@docgecko

This comment has been minimized.

Show comment
Hide comment
@docgecko

docgecko May 28, 2015

This is an awesome approach Ryan, thanks for sharing.

I was wondering if you (or anyone else who may have already implemented this) could clarify the code in the index.js files. I am implementing this structure and was going to take an approach as follows import Route Handlers up the tree, i.e. I would import the index.js files from 1 level below to the next level above, and so on for as many levels as you have.

Is this the kind of approach you are suggesting we could take: only having the Route for each screen in its own index.js file that can be imported to the main routes.js file?

App Level Route Handler

app/screens/App/index.js
import AdminRoutes from './screens/Admin/index';
import CourseRoutes from './screens/Course/index';
import AppDefaultRoute from './shared/components/AppDefaultRoute';

export default (
  <Route name={App}>
    {AdminRoutes}
    {CourseRoutes}

    {AppDefaultRoute}
  </Route>
);

Admin Level Route Handler

app/screens/App/screens/Admin/index.js
import ReportsRoutes from './screens/Reports/index';
import UsersRoutes from './screens/Users/index';
import AdminDefaultRoute from './shared/components/AdminDefaultRoute';

export default (
  <Route name='admin' path='/admin}>
    {ReportsRoutes}
    {UsersRoutes}

    {AdminDefaultRoute}
  </Route>
);

Reports Level Route Handler

app/screens/App/screens/Admin/screens/Reports/index.js

And then under Reports, you may have selection of reports. You would create the Route Handler of a report as follows, which will import the component for the handler:

import ReportA from './components/ReportA';

export default (
  <Route name='report-a' path='/admin/report-a' handler={ReportA}/>
);

docgecko commented May 28, 2015

This is an awesome approach Ryan, thanks for sharing.

I was wondering if you (or anyone else who may have already implemented this) could clarify the code in the index.js files. I am implementing this structure and was going to take an approach as follows import Route Handlers up the tree, i.e. I would import the index.js files from 1 level below to the next level above, and so on for as many levels as you have.

Is this the kind of approach you are suggesting we could take: only having the Route for each screen in its own index.js file that can be imported to the main routes.js file?

App Level Route Handler

app/screens/App/index.js
import AdminRoutes from './screens/Admin/index';
import CourseRoutes from './screens/Course/index';
import AppDefaultRoute from './shared/components/AppDefaultRoute';

export default (
  <Route name={App}>
    {AdminRoutes}
    {CourseRoutes}

    {AppDefaultRoute}
  </Route>
);

Admin Level Route Handler

app/screens/App/screens/Admin/index.js
import ReportsRoutes from './screens/Reports/index';
import UsersRoutes from './screens/Users/index';
import AdminDefaultRoute from './shared/components/AdminDefaultRoute';

export default (
  <Route name='admin' path='/admin}>
    {ReportsRoutes}
    {UsersRoutes}

    {AdminDefaultRoute}
  </Route>
);

Reports Level Route Handler

app/screens/App/screens/Admin/screens/Reports/index.js

And then under Reports, you may have selection of reports. You would create the Route Handler of a report as follows, which will import the component for the handler:

import ReportA from './components/ReportA';

export default (
  <Route name='report-a' path='/admin/report-a' handler={ReportA}/>
);
@briandipalma

This comment has been minimized.

Show comment
Hide comment
@briandipalma

briandipalma Nov 18, 2015

I don't think index needs to be specified in the module ID as the npm resolve algorithm should add that automatically if you import a directory.

briandipalma commented Nov 18, 2015

I don't think index needs to be specified in the module ID as the npm resolve algorithm should add that automatically if you import a directory.

@hunterc

This comment has been minimized.

Show comment
Hide comment
@hunterc

hunterc Nov 30, 2015

@ryanflorence how do resolve webpack aliases in your test cases?

hunterc commented Nov 30, 2015

@ryanflorence how do resolve webpack aliases in your test cases?

@crazy4groovy

This comment has been minimized.

Show comment
Hide comment
@crazy4groovy

crazy4groovy Dec 2, 2015

This way you don't have to require('../../../../../../../../../../shared/Avatar') you can simply do require('components/Avatar') no matter where you are.

Do you mean

This way you don't have to require('../../../../../../../../../../shared/components/Avatar') you can simply do require('components/Avatar') no matter where you are.
?

crazy4groovy commented Dec 2, 2015

This way you don't have to require('../../../../../../../../../../shared/Avatar') you can simply do require('components/Avatar') no matter where you are.

Do you mean

This way you don't have to require('../../../../../../../../../../shared/components/Avatar') you can simply do require('components/Avatar') no matter where you are.
?

@osenvosem

This comment has been minimized.

Show comment
Hide comment
@osenvosem

osenvosem Dec 6, 2015

Can anyone show an example of an app with this structure?

osenvosem commented Dec 6, 2015

Can anyone show an example of an app with this structure?

@zimt28

This comment has been minimized.

Show comment
Hide comment
@zimt28

zimt28 Dec 8, 2015

I'd love an example app as well. Do the index.js files actually contain the screen components as well, or just the route handlers?

zimt28 commented Dec 8, 2015

I'd love an example app as well. Do the index.js files actually contain the screen components as well, or just the route handlers?

@petrometro

This comment has been minimized.

Show comment
Hide comment
@petrometro

petrometro Dec 16, 2015

How exactly would you run the tests in this setup ?
While webpack is fine with the shared module resolution, babel doesn't know how to find the shared modules.

> mocha --compilers js:babel-core/register $(find src/app -name *.test.jsx)

module.js:339
    throw err;
    ^

Error: Cannot find module 'actions/ActionCreators'

// the folder is shared/actions/ActionCreators

petrometro commented Dec 16, 2015

How exactly would you run the tests in this setup ?
While webpack is fine with the shared module resolution, babel doesn't know how to find the shared modules.

> mocha --compilers js:babel-core/register $(find src/app -name *.test.jsx)

module.js:339
    throw err;
    ^

Error: Cannot find module 'actions/ActionCreators'

// the folder is shared/actions/ActionCreators
@dlmoody

This comment has been minimized.

Show comment
Hide comment
@dlmoody

dlmoody Dec 30, 2015

what if the routes in the app change? Wouldn't you then have to move the folders around?

dlmoody commented Dec 30, 2015

what if the routes in the app change? Wouldn't you then have to move the folders around?

@adiachenko

This comment has been minimized.

Show comment
Hide comment
@adiachenko

adiachenko Jan 1, 2016

You can run tests against this setup using Karma. First you create entry file for your test bundle using require.context API (this is a webpack thing, do not try to use it in a regular node script):

const context = require.context('./src', true, /__tests__\/\S+\.js$/)
context.keys().forEach(context)

Here we grep all .js files under __tests__ directories and require them into our bundle. Then you setup Karma on top of whatever testing framework you like and supply it webpack as preprocessor.

Look at the example here or here for more details. There is also some info in karma-webpack repo

adiachenko commented Jan 1, 2016

You can run tests against this setup using Karma. First you create entry file for your test bundle using require.context API (this is a webpack thing, do not try to use it in a regular node script):

const context = require.context('./src', true, /__tests__\/\S+\.js$/)
context.keys().forEach(context)

Here we grep all .js files under __tests__ directories and require them into our bundle. Then you setup Karma on top of whatever testing framework you like and supply it webpack as preprocessor.

Look at the example here or here for more details. There is also some info in karma-webpack repo

@adiachenko

This comment has been minimized.

Show comment
Hide comment
@adiachenko

adiachenko Jan 1, 2016

@JonasFromell Use CommonJS require statements for maintaining routes manifest. Same goes to list of Redux reducers if you use it. Example:

 <Route path="login" onEnter={redirectIfAuthenticated} component={require('../screens/App/screens/Login').default} />

adiachenko commented Jan 1, 2016

@JonasFromell Use CommonJS require statements for maintaining routes manifest. Same goes to list of Redux reducers if you use it. Example:

 <Route path="login" onEnter={redirectIfAuthenticated} component={require('../screens/App/screens/Login').default} />
@adiachenko

This comment has been minimized.

Show comment
Hide comment
@adiachenko

adiachenko Jan 20, 2016

After working with the suggested folder structure for a few weeks I arrived at the following convention regarding the contents of index.js files.

Case 1. Sync

routes.js

// ...

<Route path="feed" component={require('../screens/App/screens/Feed').default} />

// ...

Feed/index.js

export {default} from './components/Feed'

Case 2. Async (code splitting)

routes.js

// ...

<Route path="post" onEnter={requireAuth} getComponents={require('../screens/App/screens/Post').default} />

// ...

Feed/index.js

// ...

export default (location, callback) => {
  // ...
  require.ensure([], (require) => {
    // ...
    callback(null, require('./components/Post').default)
  }

}

adiachenko commented Jan 20, 2016

After working with the suggested folder structure for a few weeks I arrived at the following convention regarding the contents of index.js files.

Case 1. Sync

routes.js

// ...

<Route path="feed" component={require('../screens/App/screens/Feed').default} />

// ...

Feed/index.js

export {default} from './components/Feed'

Case 2. Async (code splitting)

routes.js

// ...

<Route path="post" onEnter={requireAuth} getComponents={require('../screens/App/screens/Post').default} />

// ...

Feed/index.js

// ...

export default (location, callback) => {
  // ...
  require.ensure([], (require) => {
    // ...
    callback(null, require('./components/Post').default)
  }

}
@Jian-Huan

This comment has been minimized.

Show comment
Hide comment
@Jian-Huan

Jian-Huan Feb 2, 2016

What is the convention for capitalizing directory and file names with this pattern?

Jian-Huan commented Feb 2, 2016

What is the convention for capitalizing directory and file names with this pattern?

@ConAntonakos

This comment has been minimized.

Show comment
Hide comment
@ConAntonakos

ConAntonakos Feb 4, 2016

How are you all handling CSS/styling modularization?

ConAntonakos commented Feb 4, 2016

How are you all handling CSS/styling modularization?

@dnutels

This comment has been minimized.

Show comment
Hide comment
@dnutels

dnutels Feb 6, 2016

Yes, I join @ConAntonakos in that question. It seems that perhaps an additional folder for each component is in order? Or are you doing _index.scss?

Also of interest - what happens if you have a screen and a component of the same name, say Bill? Should there be a descriptive addition: BillScreen and Bill or such some?

dnutels commented Feb 6, 2016

Yes, I join @ConAntonakos in that question. It seems that perhaps an additional folder for each component is in order? Or are you doing _index.scss?

Also of interest - what happens if you have a screen and a component of the same name, say Bill? Should there be a descriptive addition: BillScreen and Bill or such some?

@josegl

This comment has been minimized.

Show comment
Hide comment
@josegl

josegl Feb 8, 2016

Where the reducers in this organization structure would go?

I think that action creators and reducers must go in app/shared/reducers/ and app/shared/actions/ because both, reducers and action creators are both pure function without side effects that don't depend on anything.
What do you think?

josegl commented Feb 8, 2016

Where the reducers in this organization structure would go?

I think that action creators and reducers must go in app/shared/reducers/ and app/shared/actions/ because both, reducers and action creators are both pure function without side effects that don't depend on anything.
What do you think?

@sheltonial

This comment has been minimized.

Show comment
Hide comment
@sheltonial

sheltonial Feb 11, 2016

@ryanflorence we have been using your folder structure in a project now for a couple of months and overall we are pretty happy with it, particularly how shared components are discovered.

One pain point we are having which I wanted to share. In your example you are including your "stores" in the folder hierarchy. We have done the same using Redux and reduces, though when Redux combines the reduces into the store then these properties are available from everywhere. In this instance keeping the reducers in the folder hierarchy could become misleading.

Do you have any ideas around this? One option is we keep reducers in a flat structure and outside your folder hierarchy.

Perhaps @gaearon would have some ideas around this too.

sheltonial commented Feb 11, 2016

@ryanflorence we have been using your folder structure in a project now for a couple of months and overall we are pretty happy with it, particularly how shared components are discovered.

One pain point we are having which I wanted to share. In your example you are including your "stores" in the folder hierarchy. We have done the same using Redux and reduces, though when Redux combines the reduces into the store then these properties are available from everywhere. In this instance keeping the reducers in the folder hierarchy could become misleading.

Do you have any ideas around this? One option is we keep reducers in a flat structure and outside your folder hierarchy.

Perhaps @gaearon would have some ideas around this too.

@justinko

This comment has been minimized.

Show comment
Hide comment
@justinko

justinko Feb 14, 2016

@sheltonial

One option is we keep reducers in a flat structure and outside your folder hierarchy.

This is what we're doing and it makes sense.

justinko commented Feb 14, 2016

@sheltonial

One option is we keep reducers in a flat structure and outside your folder hierarchy.

This is what we're doing and it makes sense.

@prashaantt

This comment has been minimized.

Show comment
Hide comment
@prashaantt

prashaantt Feb 21, 2016

I'm curious what's the best practice for what goes inside the index.js of every screen in case of Redux. Is it just the Redux connect() container with the main component being connected residing inside components? Or is it the container and the connected components in the same index.js file?

prashaantt commented Feb 21, 2016

I'm curious what's the best practice for what goes inside the index.js of every screen in case of Redux. Is it just the Redux connect() container with the main component being connected residing inside components? Or is it the container and the connected components in the same index.js file?

@smrq

This comment has been minimized.

Show comment
Hide comment
@smrq

smrq Mar 1, 2016

Note that if you're not using Webpack (e.g. if you're using the React Native packager) you can still get the behavior of require('shared/foo/bar') without using Webpack modulesDirectories by wrapping your shared folders in a node_modules folder:

app
├── config
├── screens
├── node_modules
│   └── shared
│       └── .....
└── index.js

This makes them resolve correctly with plain Node.js semantics. As long as these nested node_modules folders are never siblings to a package.json, there is no ambiguity between them and your external modules. The extra level of nesting is lame, but it works without any Webpack-specific configuration.

smrq commented Mar 1, 2016

Note that if you're not using Webpack (e.g. if you're using the React Native packager) you can still get the behavior of require('shared/foo/bar') without using Webpack modulesDirectories by wrapping your shared folders in a node_modules folder:

app
├── config
├── screens
├── node_modules
│   └── shared
│       └── .....
└── index.js

This makes them resolve correctly with plain Node.js semantics. As long as these nested node_modules folders are never siblings to a package.json, there is no ambiguity between them and your external modules. The extra level of nesting is lame, but it works without any Webpack-specific configuration.

@diessica

This comment has been minimized.

Show comment
Hide comment
@diessica

diessica Mar 9, 2016

What about container components? Where they fit in this folder structure?

Should the container component be placed in a screens folder (so we use the container component in the router) and the pure component inside its (or even a shared) components folder?

diessica commented Mar 9, 2016

What about container components? Where they fit in this folder structure?

Should the container component be placed in a screens folder (so we use the container component in the router) and the pure component inside its (or even a shared) components folder?

@eddyystop

This comment has been minimized.

Show comment
Hide comment
@eddyystop

eddyystop Apr 18, 2016

Like @sheltonial, we were happy with this structure, particularly how shared components & utilities, plus actions & reducers can be discovered.

However, like @petrometro commented on Dec 16, 2015, the benefit of the structure rather degraded once we started writing tests. While webpack is fine with the shared module resolution, babel doesn't know how to find the action/reducer/component required by the unit under test.

We've had to go back to using ../../../foo whenever a test ran into a problem.

Can anyone suggest a better way around this?

[Placing our modules under node_modules is a non-starter. We use imports rather than requires.]

eddyystop commented Apr 18, 2016

Like @sheltonial, we were happy with this structure, particularly how shared components & utilities, plus actions & reducers can be discovered.

However, like @petrometro commented on Dec 16, 2015, the benefit of the structure rather degraded once we started writing tests. While webpack is fine with the shared module resolution, babel doesn't know how to find the action/reducer/component required by the unit under test.

We've had to go back to using ../../../foo whenever a test ran into a problem.

Can anyone suggest a better way around this?

[Placing our modules under node_modules is a non-starter. We use imports rather than requires.]

@eddyystop

This comment has been minimized.

Show comment
Hide comment
@eddyystop

eddyystop Apr 19, 2016

The structure works for testing if you Webpack + Babel your tests before running them. I guess that's no surprise. I have to run Babel anyway since my app is in ES6, and running Webpack as well doesn't increase build time much.

My package.json includes:

"scripts": {
    "pretest": "webpack --target node --config ./webpack.config.test.js",
    "test": "mocha ./tmp/test.bundle.js --require ./test/test_helper.js  --recursive"
 },

The Webpack config is straightforward. The test_helper.js referred to above is in ES5.

There is a problem with Chai 3.5.0 as it does not work in strict mode, whereas Babel wraps code in "use strict". I have the following in the Webpack config, but it doesn't solve the problem.

test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ]

There is a patch to Chai in the dev branch which allows it to run in strict mode.

/node_modules/chai/lib/chai/assertion.js line 33
from flag(this, 'ssfi', stack || arguments.callee);
to flag(this, 'ssfi', stack || Assertion); 

And the test script worked after this.

eddyystop commented Apr 19, 2016

The structure works for testing if you Webpack + Babel your tests before running them. I guess that's no surprise. I have to run Babel anyway since my app is in ES6, and running Webpack as well doesn't increase build time much.

My package.json includes:

"scripts": {
    "pretest": "webpack --target node --config ./webpack.config.test.js",
    "test": "mocha ./tmp/test.bundle.js --require ./test/test_helper.js  --recursive"
 },

The Webpack config is straightforward. The test_helper.js referred to above is in ES5.

There is a problem with Chai 3.5.0 as it does not work in strict mode, whereas Babel wraps code in "use strict". I have the following in the Webpack config, but it doesn't solve the problem.

test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ]

There is a patch to Chai in the dev branch which allows it to run in strict mode.

/node_modules/chai/lib/chai/assertion.js line 33
from flag(this, 'ssfi', stack || arguments.callee);
to flag(this, 'ssfi', stack || Assertion); 

And the test script worked after this.

@eddyystop

This comment has been minimized.

Show comment
Hide comment
@eddyystop

eddyystop May 1, 2016

Babel can also do shared module resolution using babel-plugin-resolver. Using this you may not have to create a Webpack bundle when running tests on node, thus saving yourself a bundle (sic) of time.

Caveats:
(1) The plugin is only called when you use import not require.
(2) You will still have to run Webpack for tests if Babel loaders need to process your files, e.g. you have import style from './style.css'
(3) You can run into logical problems if you have the same file names in different shared folders.

The Babel config for the sample folder structure would include something like:

{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": [
    ["resolver", { "resolveDirs": [
      "shared",
      "screens/App/shared",
      "screens/App/screens/Admin/shared",
    ]}],
    ...
  ],
  ...
}

Modules are resolved by looking into the folders in the order they are specified in the config. The plugin does not 'walk' the folder structure. Hence you should avoid using the same file names in different shareable folders.

You can alternatively use the NODE_PATH environment variable. This option requires neither Babel nor Webpack. Its an evolutionary leftover from Node's early days, but is still supported.

It works similarly to babel-plugin-resolver but using absolute paths.

eddyystop commented May 1, 2016

Babel can also do shared module resolution using babel-plugin-resolver. Using this you may not have to create a Webpack bundle when running tests on node, thus saving yourself a bundle (sic) of time.

Caveats:
(1) The plugin is only called when you use import not require.
(2) You will still have to run Webpack for tests if Babel loaders need to process your files, e.g. you have import style from './style.css'
(3) You can run into logical problems if you have the same file names in different shared folders.

The Babel config for the sample folder structure would include something like:

{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": [
    ["resolver", { "resolveDirs": [
      "shared",
      "screens/App/shared",
      "screens/App/screens/Admin/shared",
    ]}],
    ...
  ],
  ...
}

Modules are resolved by looking into the folders in the order they are specified in the config. The plugin does not 'walk' the folder structure. Hence you should avoid using the same file names in different shareable folders.

You can alternatively use the NODE_PATH environment variable. This option requires neither Babel nor Webpack. Its an evolutionary leftover from Node's early days, but is still supported.

It works similarly to babel-plugin-resolver but using absolute paths.

@kriegerd

This comment has been minimized.

Show comment
Hide comment
@kriegerd

kriegerd May 3, 2016

Does anyone knows of an example app that uses this structure?
I'm just starting with most of these libs and it's a bit hard to wrap my head around it all. Thanks!

kriegerd commented May 3, 2016

Does anyone knows of an example app that uses this structure?
I'm just starting with most of these libs and it's a bit hard to wrap my head around it all. Thanks!

@iammerrick

This comment has been minimized.

Show comment
Hide comment
@iammerrick

iammerrick May 5, 2016

We adopted this for our project and decided to leave the practice for one reason. Because of the use of shared it became difficult to answer the question "is this module being used" with a simple find and replace. Also the question of "where is this module locate" could no longer be answers. The problem is sibling directories or directories down different branches can both refer to 'components/Something' and be referencing different modules and the only way to find out which one was to manually do the hierarchal look up that webpack does. Searching for 'components/Something' may imply the module at hand is being used, but it could be a 'components/Something' else where. You could ofcourse enforce unique module names, but the only reliable/free way to enforce uniqueness is to leverage the file system itself. This circumvents that and for that reason we are leaving this structure.

iammerrick commented May 5, 2016

We adopted this for our project and decided to leave the practice for one reason. Because of the use of shared it became difficult to answer the question "is this module being used" with a simple find and replace. Also the question of "where is this module locate" could no longer be answers. The problem is sibling directories or directories down different branches can both refer to 'components/Something' and be referencing different modules and the only way to find out which one was to manually do the hierarchal look up that webpack does. Searching for 'components/Something' may imply the module at hand is being used, but it could be a 'components/Something' else where. You could ofcourse enforce unique module names, but the only reliable/free way to enforce uniqueness is to leverage the file system itself. This circumvents that and for that reason we are leaving this structure.

@loicginoux

This comment has been minimized.

Show comment
Hide comment
@loicginoux

loicginoux Aug 16, 2016

would you not split your /components folders in 2 differents folders /components and /containers to have a separation more clear between them ?

loicginoux commented Aug 16, 2016

would you not split your /components folders in 2 differents folders /components and /containers to have a separation more clear between them ?

@felquis

This comment has been minimized.

Show comment
Hide comment
@felquis

felquis Aug 16, 2016

@ryanflorence If I have the URL /plans which show all the plans, when I click in one the user goes to /plans/:id

How can I structure it to fit this folder structure?

felquis commented Aug 16, 2016

@ryanflorence If I have the URL /plans which show all the plans, when I click in one the user goes to /plans/:id

How can I structure it to fit this folder structure?

@kuon

This comment has been minimized.

Show comment
Hide comment
@kuon

kuon Aug 22, 2016

I am still struggling a bit with this problem, and I wrote a piece about it: https://the-missing-bit.com/posts/2016/08/21/react-structure/

kuon commented Aug 22, 2016

I am still struggling a bit with this problem, and I wrote a piece about it: https://the-missing-bit.com/posts/2016/08/21/react-structure/

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Aug 25, 2016

@ryanflorence where do you put not-shared assets, like pictures?

ghost commented Aug 25, 2016

@ryanflorence where do you put not-shared assets, like pictures?

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Aug 28, 2016

babel-plugin-module-resolver will work for tests instead of webpack's modulesDirectories

{
  "plugins": [
    "transform-object-rest-spread",
      ["module-resolver", {
        "root": ["./src/../shared"]
      }]
    ]
}

ghost commented Aug 28, 2016

babel-plugin-module-resolver will work for tests instead of webpack's modulesDirectories

{
  "plugins": [
    "transform-object-rest-spread",
      ["module-resolver", {
        "root": ["./src/../shared"]
      }]
    ]
}
@emildimitrov

This comment has been minimized.

Show comment
Hide comment
@emildimitrov

emildimitrov Sep 1, 2016

Any feedback on using this with react-native project? In my case i have multi-platform codebase like so
src/
common (common for all platforms)
browser-common ( all common things across browsers )
native-common (all common across ios android etc)
browser-mobile ( we have separate website for mobile )
browser-desktop ( separate for desktop )
native-ios ( ios specific )
native-android (android specific)

THanks

emildimitrov commented Sep 1, 2016

Any feedback on using this with react-native project? In my case i have multi-platform codebase like so
src/
common (common for all platforms)
browser-common ( all common things across browsers )
native-common (all common across ios android etc)
browser-mobile ( we have separate website for mobile )
browser-desktop ( separate for desktop )
native-ios ( ios specific )
native-android (android specific)

THanks

@tarun-dugar

This comment has been minimized.

Show comment
Hide comment
@tarun-dugar

tarun-dugar Dec 17, 2016

@ryanflorence this is awesome! Also, can you tell me how you would you go about integrating redux with this folder structure?

tarun-dugar commented Dec 17, 2016

@ryanflorence this is awesome! Also, can you tell me how you would you go about integrating redux with this folder structure?

@wuchangming

This comment has been minimized.

Show comment
Hide comment
@wuchangming

wuchangming commented Jan 18, 2017

@kuon, Awesome!

@cdtinney

This comment has been minimized.

Show comment
Hide comment
@cdtinney

cdtinney Feb 22, 2017

Wanted to comment to say that I am finding this structure very helpful. I was struggling to find a good way to share components.

For anyone else reading that's using Jest to test, all you need to do is add the shared folder to moduleDirectories in your Jest config in package.json in order for the shared components imports to be found when testing.

e.g.

    "moduleDirectories": [
      "node_modules", "shared"
    ]

cdtinney commented Feb 22, 2017

Wanted to comment to say that I am finding this structure very helpful. I was struggling to find a good way to share components.

For anyone else reading that's using Jest to test, all you need to do is add the shared folder to moduleDirectories in your Jest config in package.json in order for the shared components imports to be found when testing.

e.g.

    "moduleDirectories": [
      "node_modules", "shared"
    ]
@rogovdm

This comment has been minimized.

Show comment
Hide comment
@rogovdm

rogovdm May 14, 2017

Hey, /everyone :) What do you all think about this structure in 2017? What is your practical experience with it?

Would be very thankful if you share.

rogovdm commented May 14, 2017

Hey, /everyone :) What do you all think about this structure in 2017? What is your practical experience with it?

Would be very thankful if you share.

@rafael-sa

This comment has been minimized.

Show comment
Hide comment
@rafael-sa

rafael-sa Jan 8, 2018

Bump.
I have the same doubt as @rogovdm.

rafael-sa commented Jan 8, 2018

Bump.
I have the same doubt as @rogovdm.

@kentcdodds

This comment has been minimized.

Show comment
Hide comment
@kentcdodds

kentcdodds commented Jan 22, 2018

I love it.

@drzd

This comment has been minimized.

Show comment
Hide comment
@drzd

drzd Jan 28, 2018

@iammerrick What did you leave this structure in favor of?

drzd commented Jan 28, 2018

@iammerrick What did you leave this structure in favor of?

@jessevdp

This comment has been minimized.

Show comment
Hide comment
@jessevdp

jessevdp Apr 3, 2018

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

How did you get this to work?

jessevdp commented Apr 3, 2018

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

How did you get this to work?

@jeffersonRibeiro

This comment has been minimized.

Show comment
Hide comment
@jeffersonRibeiro

jeffersonRibeiro May 10, 2018

@rogovdm @rafael-sa I have the same question. Also does someone have an alternative?

jeffersonRibeiro commented May 10, 2018

@rogovdm @rafael-sa I have the same question. Also does someone have an alternative?

@rchamberlin

This comment has been minimized.

Show comment
Hide comment
@rchamberlin

rchamberlin May 17, 2018

@jeffersonRibeiro We have a module called bundle.aliases.js

module.exports = {
  '@components': path.resolve('./app', 'client', 'src', 'components'),
  '@constants': path.resolve('./app', 'client', 'src', 'constants'),
  '@screens': path.resolve('./app', 'client', 'src', 'components', 'screens'),
  '@keyboard': path.resolve('./app', 'client', 'src', 'components', 'keyboard'),
  '@node-modules': path.resolve('./app', 'client', 'node_modules'),
  '@containers': path.resolve('./app', 'client', 'src', 'components', 'containers'),
  '@actions': path.resolve('./app', 'client', 'src', '_actions'),
  '@store': path.resolve('./app', 'client', 'src', '_store'),
  '@reducers': path.resolve('./app', 'client', 'src', '_reducers'),
  '@modules': path.resolve('./app', 'client', 'src', 'modules'),
  '@selectors': path.resolve('./app', 'client', 'src', '_selectors')
}

and then in our webpack config we have:

const alias = require(path.resolve(process.cwd(), 'app', 'config', 'bundle.aliases.js'))
resolve: {
  alias
}

rchamberlin commented May 17, 2018

@jeffersonRibeiro We have a module called bundle.aliases.js

module.exports = {
  '@components': path.resolve('./app', 'client', 'src', 'components'),
  '@constants': path.resolve('./app', 'client', 'src', 'constants'),
  '@screens': path.resolve('./app', 'client', 'src', 'components', 'screens'),
  '@keyboard': path.resolve('./app', 'client', 'src', 'components', 'keyboard'),
  '@node-modules': path.resolve('./app', 'client', 'node_modules'),
  '@containers': path.resolve('./app', 'client', 'src', 'components', 'containers'),
  '@actions': path.resolve('./app', 'client', 'src', '_actions'),
  '@store': path.resolve('./app', 'client', 'src', '_store'),
  '@reducers': path.resolve('./app', 'client', 'src', '_reducers'),
  '@modules': path.resolve('./app', 'client', 'src', 'modules'),
  '@selectors': path.resolve('./app', 'client', 'src', '_selectors')
}

and then in our webpack config we have:

const alias = require(path.resolve(process.cwd(), 'app', 'config', 'bundle.aliases.js'))
resolve: {
  alias
}
@MikeBarnhardt

This comment has been minimized.

Show comment
Hide comment
@MikeBarnhardt

MikeBarnhardt Jun 19, 2018

I suggest using utils or utilities instead of util to avoid trying to pull from Node's util module when using SSR (such as Razzle).

MikeBarnhardt commented Jun 19, 2018

I suggest using utils or utilities instead of util to avoid trying to pull from Node's util module when using SSR (such as Razzle).

@JulianSuringa

This comment has been minimized.

Show comment
Hide comment
@JulianSuringa

JulianSuringa Jul 11, 2018

I think the components is isolated to each component name is not the best option for me its look like a drill components 3 or more layer for nesting is not good for readability must better to keep it simple in the directory and I believe this one https://marmelab.com/blog/2015/12/17/react-directory-structure.html.

JulianSuringa commented Jul 11, 2018

I think the components is isolated to each component name is not the best option for me its look like a drill components 3 or more layer for nesting is not good for readability must better to keep it simple in the directory and I believe this one https://marmelab.com/blog/2015/12/17/react-directory-structure.html.

@tarang9211

This comment has been minimized.

Show comment
Hide comment
@tarang9211

tarang9211 Jul 15, 2018

Hi guys, is there an example for the folder structure? I am a little confused.

tarang9211 commented Jul 15, 2018

Hi guys, is there an example for the folder structure? I am a little confused.

@gnapse

This comment has been minimized.

Show comment
Hide comment
@gnapse

gnapse Jul 26, 2018

This is great, but unfortunately it would break an editor's ability to track what you're importing and from where. vscode for instance is able to give you useful feedback because it "understands" javascript imports, and gives you auto-complete. Even more so if using TypeScript. Is this configuration something that an editor like that would pick up too?

gnapse commented Jul 26, 2018

This is great, but unfortunately it would break an editor's ability to track what you're importing and from where. vscode for instance is able to give you useful feedback because it "understands" javascript imports, and gives you auto-complete. Even more so if using TypeScript. Is this configuration something that an editor like that would pick up too?

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