ES Modules: Using Named Exports as the Default Export

tl;dr import the module’s own exported named exports & export that as the module’s default export.

ES Modules are nice, I guess. They allow us to create named exports:

To use a named export, we can import it directly into our code:

Ok, great.

But what if we’re using multiple modules with the same exported API?

Of course this was never going to work. There are a few solutions though.

Aliasing named imports

You can assign a new name to a named export as you import it, allowing you to resolve naming collisions, or give the export a more informative name.

Cool. It’s a little verbose but I can deal.

What if we’re importing many functions of the same name from each module?

Ah, gross. This approach doesn’t scale well when importing APIs with many methods.

Namespace Imports

Thankfully we don’t need to namespace everything by hand, we can use the import * as X syntax to pull all of the named exports into a local namespace.

The Default Export as the API

Another alternative is to avoid authoring with ES Module’s named exports altogether, instead expose an API on the default export.

This does make for slightly cleaner import syntax. CommonJS modules generally look like this when you’re importing them using babel’s ES Modules/CommonJS interop.

2016/04/26 Update: The above pattern is mostly only a useful transitionary refactoring between CommonJS and ES Modules. Dr. Axel Rauschmayer reminds us it should be considered an ES6 anti-pattern because

“…you lose some ES6 module benefits such as tree-shaking and faster access to imports.”

Handling a Mixture of Export Approaches

While we’re in this transitional & exploratory phase between CommonJS and ES Modules, many codebases will need to work with modules authored using different exporting methods.

This has the downside of requiring the module’s consumer to know which export approach was used for authoring. This is very mildly irritating.

Authoring Ambidextrous ES Modules

There’s a simple, alternative method that mostly maintains backwards and forwards import compatibility with minimal impact on ES Module authoring style:

Simply export the module’s own namespace as the default export.

The consumer can then carelessly use either method of getting a namespaced import.

2017/08/11 Update:

There is a downside to the above approach: you can’t also have a default export that’s also a function. This can be solved simply by attaching the module namespace to the default function e.g. using Object.assign:

Now the default export is callable (albeit only when consumed via import, not require):

Summary

It’s possible that importing a module into itself isn’t valid according to the ES Modules spec, though it does currently work using babel. I don’t know why this wouldn’t be valid. edit: it’s fine. I was definitely impressed when this worked despite the circular reference. It’s like magic.

This approach may be useful while working on an application that’s part-way through the transitioning from CommonJS to ES Modules. It allows modules to be converted to ES Module syntax internally, while allowing consumers to continue importing as if it were a CommonJS module, with functions hanging off module.exports.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.