...

/

ESM: Default, Mixed, and Async

ESM: Default, Mixed, and Async

Learn about ESM default exports and imports and how to use mixed and async imports.

Default exports and imports

One widely used feature of CommonJS is the ability to export a single unnamed entity through the assignment of module.exports. We saw that this is very convenient as it encourages module developers to follow the single-responsibility principle and expose only one clear interface. With ESM, we can do something similar through what’s called a default export. A default export makes use of the export default keywords, and it looks like this:

Press + to interact
// logger.mjs
export default class Logger {
constructor (name) {
this.name = name
}
log (message) {
console.log(`[${this.name}] ${message}`)
} }

In this case, the name Logger is ignored, and the entity exported is registered under the name default. This exported name is handled in a special way, and it can be imported as follows:

Press + to interact
main.mjs
logger.mjs
// main.mjs
import MyLogger from './logger.mjs'
const logger = new MyLogger('info')
logger.log('Hello World')

The difference with named ESM imports is that here, we can import it and at the same time assign it a local name of our choice because the default export is considered unnamed. In this example, we can replace MyLogger with anything else that makes sense in our context. This is very similar to what we do with CommonJS modules. Also, note that we don’t have to wrap the import name around brackets or use the as keyword when renaming.

Internally, a default export is equivalent to a named export with default as the name. We can easily verify this statement by running the following snippet of code:

Press + to interact
main.mjs
logger.mjs
import * as loggerModule from "./logger.mjs";
console.log(loggerModule);

When executed, the previous code will print something like this:

[Module: null prototype] { default: [class Logger] }
Output

One thing that we cannot do, though, is import the default entity explicitly. In fact, something like the following will fail:

Press + to interact
import { default } from './logger.mjs'

The execution will fail with a SyntaxError: Unexpected reserved word error. This happens because the default keyword cannot be used as a variable name. It’s valid as an object attribute, so in the previous example, it’s okay to use loggerModule.default, but we can’t have a variable named default directly in the scope.

Mixed

...