Course contentsShow
← Node.js
Lesson 108 of 1,9802. Module Systems and Environment ConfigurationFree lesson

Handling Default Exports in Interop Scenarios

Manage the complexities of named versus default exports when bridging older and newer module standards.

In this lesson, you will learn how to bridge the gap between CommonJS (CJS) and ECMAScript Modules (ESM) when dealing with default exports.

The Compatibility Challenge

When you use the require syntax (CJS) to import a modern ES Module, or use import (ESM) to bring in older CJS code, they don't always "speak the same language" regarding default values.

In CJS, a module exports a single object via module.exports. In ESM, a module can have a default export alongside several named exports. When Node.js performs the interop, it tries to map these concepts together, but the behavior can be surprising.

Importing CJS in ESM

If you have a CJS module that uses module.exports = { ... }, when you import it into an ESM file, Node.js treats the entire object as the default export.

// math.cjs
module.exports = { add: (a, b) => a + b };

// app.mjs
import math from './math.cjs';
console.log(math.add(1, 2)); // Works as expected!

However, if the CJS module uses exports.add = ... (a named export), you cannot use the default import syntax; you must use the named import: import { add } from './math.cjs'.

The "Default" Trap

The most common friction point occurs when you try to import a CommonJS module that has both module.exports and named properties. Because ESM expects a specific default property, if your CJS file looks like this:

// CJS file
module.exports = { data: 'hello' };
module.exports.other = 'world';

When importing this into ESM, you often need to use the "namespace import" syntax to capture everything safely:

import * as myModule from './legacy.cjs';
// myModule.default.data -> 'hello'
// myModule.other -> 'world'

By using the * as syntax, you create a static object that contains both the default export and any named exports, effectively shielding your application from the underlying module resolution differences. This ensures your code remains predictable regardless of how the source module was originally structured.

Key Takeaway: When mixing module standards, use namespace imports (import * as x) to ensure you capture both default and named exports consistently across the CJS/ESM boundary.