Using Default Exports Makes JavaScript Harder to Read!
Tags: programming, javascript
Published: 2022-10-12 19:52:25

In this blog post, I would like to show you how using JavaScript `export default` hurts your codebase readability and refactorability. I'll also share some tips for using named exports!

I like to treat my code as a clay, it's constantly evolving when I'm adding new features or changing existing ones. But using default exports not only makes it harder, it also adds complexity when using already existing code!

Allow me to explain my reasoning!

## You can export anonymous functions and values

The first major problem with default exports is naming. You have to think about name import every time you include it. Not only that, but you're not forced to come up with a good name when creating a new function, as it's allowed to export anonymous values.

Let's take a look at this simple JavaScript file:

```javascript
// cookies.js
export default () => {
  // baking 🍪 cookies logic
};

// app.js
import makeChocolateChipCookies from "./cookie.js";
```

You have no idea what your default exported function from `cookies.js` does. Of course, you can peek into the implementation or count on documentation, but it's definitely adds way more [cognitive load](https://en.wikipedia.org/wiki/Cognitive_load). You cannot spot right on what's that function is supposed to do.

That forces you to think about naming that function in every place you want to use it. And naming things is hard. Maybe you wrote that function yourself, you know exactly what it's doing. But if you have a new team member joining, it's much harder for them to understand what this function is doing.

That can also leads to other team members picking up different name when working on new features or refactoring existing code. And consistency and convention is a key for high performing teams! You can suggest a different name in the code review, but it's adding that complexity which could be easily avoided in the first place by using named exports.

## It's awkward when importing all from module

JavaScript's import allows you to import everything from the module using `import * as X` syntax. And default export is just available under `default`.

```javascript
// app.js
import * as Cookies from "./cookie.js";

// usage - yikes
Cookies.default();
```

Of course, if you export one thing it's not that important as you most likely won't use `import * as X`. But you may want to group multiple things you import by module name for readability.

<Callout>
  While I really love grouping module functions, and I think it's really
  excellent practice for readability, it may not always be optimal for tree
  shaking and may increase your bundle size!
</Callout>

## Default exports and folder-based routing

Frameworks like Next.js or Remix force you to use default exports to define components with the folder-based routing. I'm not a big fan of forcing default exports in any case.

TypeScript is lacking at this point some kind of support for "template" style exports, where a single file can export a predefined set of optional named exports in addition to the main default export. I would love to see something like that included in the language in the future, based on the popularity of previously mentioned frameworks.

## Using named exports

### Export each function individually, or export everything at once?

Syntax allows you to use `export` keyword before each function, type or value.

```javascript
export function makeCookies() {
  // baking 🍪 cookies logic
}
```

It's also possible to have export multiple things at the same time:

```javascript
function makeCookies() {
  // baking 🍪 cookies logic
}

function eatCookies() {
  // eating 🍪 cookies logic
}

export { makeCookies, eatCookies };
```

I tend to prefer first approach as you clearly see whether the function you're reading is exported or not.

### Named export alias to the rescue

When you really need to use a different name, named exports allow **named export aliases**.

```javascript
// cookies.js
export function makeCookies() {
  // baking 🍪 cookies logic
}

// app.js
import { makeCookies as makeCookiesWithStyle } from "./cookie.js";
```

## Conclusion

Even though we have ES2015 (ES6) for quite some time already, I see a lot of default exports in all types of applications. They were introduced for CommonJS interoperability, and there is not much reason to use them in the internal code. I hope this post will help you convince your team not to use default exports when it's possible.

If you have some default exports refactoring horror stories, don't hesitate to [share them on Twitter](https://twitter.com/intent/tweet?text=https://cichocinski.dev/using-default-exports-makes-javascript-harder)!

## Resources

List of resources I used when researching this blog post:

- https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
- https://blog.neufund.org/why-we-have-banned-default-exports-and-you-should-do-the-same-d51fdc2cf2ad

---

Read more at [cichocinski.dev](https://cichocinski.dev)