React does not force any file structure to work. File structure is one of those themes up for debate where no choice can be the gold standard.
In general, it is a good idea to keep files that often change together close to each other. This principle is called “colocation”. (React docs)
This post is not about why you should or shouldn’t colocate. That’s up to you. We do colocate our component files. That’s our choice. So, this post is about how we do it and how we configure our tools to support it.
Our current component layout
We put each component in its own folder. This folder is named after the component using kebab-case. Casing is not relevant. I won’t debate whether is best to use kebab, snake, pascal, camel or any other casing you may love. Component folders are arranged inside other folders to define a taxonomy.
Each component folder contains several files, also named after the component. Each one with its own distinctive extension:
.jsxfor the component’s code. The component is always the
.module.cssfor the component styles. We use CSS modules and BEM conventions.
.test.jsxfor the component’s tests. Jest is our testing framework of choice.
.test.jsx.snapfor the test snapshots.
We also add an
index.js file that re-exports the component to have shorter import sentences:
import Component from "path/to/component"; instead of
import Component from "path/to/component/component.jsx";.
Example, a simple button component
SimpleButton will be grouped under
buttons and has the following layout:
simple-button.jsx defines the component. In this example a
SimpleButton that can be either enabled or disabled:
simple-button.module.css contains the CSS modules for the component. Selectors follow our own JS friendly BEM naming convention:
index.js re-exports the
SimpleButton component from
simple-button.test.jsx contains the Jest test suite for the component. Notice we import
SimpleButton from ".". This way we make sure the
index.js re-export is working as expected.
Colocation, following this layout, gives us several advantages:
- Component files are easy to find.
- Trivial imports from test to component, component to assets, etc.
- Easily change the components taxonomy. To move a component, just move its folder around.
- Easy extraction of components to their own packages. Once again, you just need to move the folder around.
Depending on which tools we are using to document our components we can have more colocated files (
.mdx for example. We use both React Styleguidist and Docz, depending on the project). We are not covering this topic today, but it also benefits from colocation.
Configuring the tools to fit this layout
Your everyday JS tools does not encourage colocation by default, or, at least, not this way. Don’t worry, making them work the way we want is just a matter of tinkering with the config.
Babel: allowing default exports
The current standard syntax for
export does not allow to re-export from another package the way we do in our
index.js. It needs more boilerplate code unless you use Babel and @babel/plugin-proposal-export-default-from. It implements the stage-1 proposal to support the
export from syntax.
Webpack: supporting CSS modules
To use CSS modules, webpack must use the
css-loader with modules enabled. We setup the loaders for the
.css imports overriding the
.module.css imports to use CSS modules. This way we can import some third party stylesheet or setup some global styles avoiding the CSS modules scoping.
Jest: collocating test and snapshots
By default Jest will look for your component test in a
__test__ folder and store snapshots in
__snapshots__. You can change this behaviour using the
testRegex and snapshotResolver config options. With
testRegex you state that your test will be named after the component file and use a
snapshotResolver tells Jest to store the snapshots in the same folder as the test, adding a
Jest: supporting CSS modules
If you use CSS modules you must tell Jest how to handle your
.css imports by configuring a moduleNameMapper.
We use identity-object-proxy. The
styles you import in your component will be an ES2015 proxy that returns the keys you use as a string:
styles.Button will be
"Button". This way the
classNames in the snapshots will be meaningful.
ESLint: applying rules only to test files
Last but not least, we use ESLint to check our code. We can apply certain linting rules only to our test files using the
overrides option. We use it to enable Jest globals and setup some eslint-plugin-jest rules.
Colocating component files brings several advantages. We strongly believe that it is the better approach and we are happy with it. You can develop your componentes following this layout too, just by making simple changes to your tooling configuration. Give it a try. You won’t go back.