DEV Community

Kristian Dupont
Kristian Dupont

Posted on • Originally published at kristiandupont.Medium on

About index.js Files..

Index.js files are a “cute” feature that Ryan Dahl came up with when he designed Node.js. While he officially regrets them, I think they have evolved to be a useful tool for structuring code. In this post, I will try to articulate the rules that I had made up for myself about them.


Photo by Maksym Kaharlytskyi on Unsplash

Basically_,_ index files serve one of two purposes: as encapsulation or as a namespace. The corollary of this is that index files act as guard rails for imports — I avoid importing something from a subfolder of a folder containing an index file.

Encapsulation

If an index file is used for encapsulation, it should contain a default export for the primary entity (class, component, function, etc.) in he folder. It may also contain a number of names exports for related, “secondary” items. This is useful when you want to hide the implementation details of a module and expose only a single interface. For example, consider the following folder structure:

src
└── TopBar
    ├── index.js
    ├── TopBar.js
    ├── DropDown.js
    └── SearchBox.js
Enter fullscreen mode Exit fullscreen mode

In this case, the TopBar folder contains the implementation for a top bar component, including a few sub-components. The index file re-exports the component in TopBar.js:

// index.js
export { default } from './TopBar';
Enter fullscreen mode Exit fullscreen mode

To use the component, you would import the default export from the TopBar folder:

import TopBar from './TopBar';
Enter fullscreen mode Exit fullscreen mode

Namespace

If an index file is used as a namespace, it should contain a bunch of named exports for everything in the folder. I used to do this a lot but I have actually stopped doing it in favor of just having the folder name in the import. However, it can still be useful to group related entities under a single identifier if they feel particularly strongly related.

For example, consider the following folder structure:

src
└── shared-components
    ├── LoadingButton.jsx
    ├── Spinner.jsx
    └── ModalDialog
        ├── ModalDialog.jsx
        ├── index.js
        └── Overlay.jsx
    ├── Chevron.jsx
    └── index.js
Enter fullscreen mode Exit fullscreen mode

In this case, the shared-components folder contains four components. The index.js file in the

export { default as LoadingButton } from './LoadingButton';
export { default as Spinner } from './Spinner';
export { default as ModalDialog } from './ModalDialog';
export { default as Chevron } from './Chevron';
Enter fullscreen mode Exit fullscreen mode

This makes shared-components work like a sort of package that you can import the components from. The win over importing the components from their respective sub-directories is small but might be to your preference.

Importing from Sub-folders

If a folder has an index file, I consider that a guard rail which should prevent me from importing anything from sub-folders. Now, files that are in the folder or a sub-folder themselves are allowed to import stuff, you just shouldn’t cross the boundary, so to speak. This is something I want to create a linter rule for because it could be enforced automatically, but for now I keep track of it manually.

Emergent Design

Both use cases are well suited for emergent design.

In the case of encapsulation, you can start by writing something in a single file. When that file becomes too large or complex, you replace it with a folder of the same name (sans extension), and from the perspective of the rest of the code, everything remains the same.

When used for namespaces, this is a nice first step for grouping related code before moving it into a separate package, either in your mono-repo using workspaces or all the way to NPM, depending on how general-purpose it becomes.

..default exports??

Some people argue that the concept of default exports was a mistake and I have some sympathy for that. At this point I’ve used them so much that it’s basically muscle memory and I’ve found that VSCode has sufficient refactoring capabilities for handling them, to the point where the downsides are negligible.

Top comments (0)