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 like a lump of clay, it's constantly evolving when as I add new features or change existing ones. Moreso, using default exports not only makes it harder, but it also adds a layer of complexity, especially with already existing code!
Allow me to explain my reasoning!
The first major problem with default exports is naming. You have to think about naming the import every time you include it. Additionally, since it is possible to export anonymous values while creating a new function, you don’t need to think of a catchy name.
Let's take a look at this simple JavaScript file:
// 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 that adds more cognitive load. It’s hard to tell from the jump what the function is supposed to do.
That forces you to name 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 lead to other team members picking up different names when working on new features or refactoring existing code. And consistency and convention are key for high-performing teams! You can suggest a different name in the code review, but that’s adding that complexity that could be easily avoided in the first place by using named exports.
JavaScript's import allows you to import everything from the module using import * as X
syntax. And default export is just available under default
.
// 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.
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!
Frameworks like Next.js or Remix force you to use default exports to define components with folder-based routing. I'm not a big fan of forcing default exports in any case.
TypeScript, at this point, still lacks 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.
The syntax allows you to use export
keyword before each function, type or value.
export function makeCookies() {
// baking 🍪 cookies logic
}
It's also possible to export multiple things at the same time:
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.
When you really need to use a different name, named exports allow named export aliases.
// cookies.js
export function makeCookies() {
// baking 🍪 cookies logic
}
// app.js
import { makeCookies as makeCookiesWithStyle } from "./cookie.js";
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 no real 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.
Also Published here.
If you have some default exports refactoring horror stories, don't hesitate to share them on Twitter!
List of resources I used when researching this blog post:
Subscribe to my newsletter: https://buttondown.email/cichocinski