Edit
Page
ESM exports: named vs. default? cover image

ESM exports: named vs. default?

To name, or not to name?

updated 6 months ago

modules (1)

Should you use named or default exports in JavaScript?

There’s no shortage of strongly worded articles on this topic.

The majority judge default export as “terrible.” Others maintain default should win (e.g. AirBnb style guide.)

They often blame entirely temporary things: IDE auto-import bugs, a particular bundler’s tree-shaking abilities, or the mere possibility of typos when naming an import.

Have we missed the point of exporting in the first place?

Code is Communication. ✨

We are sending a signal to importers how to use a thing.

So, what are we saying?

Broadly speaking, there are 2 ways to export things in modern JavaScript:

  • An export default boldly declares “This is THE SINGLE MOST IMPORTANT thing.” Also, “any named exports only play a supporting role.”
  • A named export says it’s “definitely A THING!” Also raises some questions, “got any other buddies there?“ Follow up, “Are they invited or required?”

Of course you can combine both, or use different approaches for different parts of your codebase. See more examples at the end of the article.

Weak Args, Man

Let’s address some of the common “temporary issues” folks run into.

  • Arg #1: Named exports ensure name consistency. source

    • No, they don’t. You’re maybe looking for a lint rule?
    • (I hate to break it to you, but wait until you learn what variables can do!)
// You can alias using both!
import { Knife as Handle } from "./knife.js"; // 🔪
import { default as Handle } from "./knife.js"; // 🔪
import Handle from "./knife.js"; // 🔪
  • Arg #2: Use import * as soManyKnives from './kinves.js' to combine named exports. (Not linked, author retracted.)

    • Neat feature. Not the point.
    • Now tell me, how do I hold your contraption again? No author intent.
  • Arg #3: Named exports have better IDE import or renaming support. source

    • Incorrect (any more). Configure/update your tools.
    • Support has existed for 3+ years in VS Code, IntelliJ, etc.
    • Still, there are some “best practices” to use with default exports to get the best IDE & refactor experience.
    • export default function UserService() {} - always prefer named functions.
    • ❌ export default function() { } - anonymous functions are not implicitly tied to their filename. If you don’t name the thing, it’s hard to ask the computer to change it.
    • Note: For historical reasons you cannot combine export default with a const expression.
    export default const Knife = () => {...blade, ...handle}
    // ^ ❌ Not Supported ❌ ^
    // Cannot export default const ....
    // ==========================
    
    // However, once declared you can export a const var as the default.
    const Knife = () => {...blade, ...handle}
    export default Knife;
    // ^ ✅ Valid
    
    // For completeness:
    export default class anyoneStillUseThese {}
    // ^ ✅ Also valid to export a class as default

Summary

There are actually many combinations of ways we could export things, each tells a different story:

Default (Exports) Named (Exports) Private Fns Pattern Meaning
One default export. “Presenting ONE function w/ Single Purpose!”
One named export. “Please don’t rename me.”
Default export + multiple ‘private’ un-exported functions “Here’s some related logic. Also, expect class-ish behavior.“
Multiple named exports, generic file name. “A grab-bag of loosely related things, no hierarchy implied.”
Single named export ALSO exported as default. “You can’t mess up importing me.”

Something to think about: What are we saying when the file name does or doesn’t match one of its exports? (For example, a utils.js with many functions.)

Conclusion

If Code is Communication, please export like you fucking mean it. 💞