Understanding the Differences Between module.exports and export default in Node.js and ES6

Nov 12, 2025 · Programming · 11 views · 7.8

Keywords: Node.js | ES6 Modules | CommonJS | module.exports | export default | Babel Transpilation

Abstract: This article provides an in-depth analysis of the core differences between Node.js's CommonJS module system using module.exports and ES6's module system using export default. Through concrete code examples, it demonstrates the implementation mechanism of default exports during Babel transpilation, explains why directly using export default in Node.js environments causes 'XX is not a constructor' errors, and offers correct import methods and compatibility solutions.

Fundamental Concepts of Module Systems

In modern JavaScript development, module systems are essential mechanisms for code organization and reuse. Node.js traditionally employs the CommonJS module system, while ECMAScript 6 (ES6) introduced native module support. These two systems exhibit significant differences in syntax and implementation mechanisms, and understanding these distinctions is crucial for avoiding common module import errors.

CommonJS Module Export Mechanism

In Node.js's CommonJS module system, module.exports serves as the primary export mechanism. When using module.exports = SlimShady, the entire module's export value is set to the SlimShady class. This means that when imported in other files via require('./module'), the result is directly the SlimShady constructor, which can be immediately instantiated using the new operator.

'use strict'
class SlimShady {
  constructor(options) {
    this._options = options
  }

  sayName() {
    return 'My name is Slim Shady.'
  }
}

// Correct export method
module.exports = SlimShady

Transpilation Implementation of ES6 Default Exports

While ES6's export default appears syntactically similar to module.exports, their underlying implementations differ fundamentally. Since current Node.js environments don't natively support ES6 modules, transpilation tools like Babel are required to convert ES6 modules to CommonJS format.

The key insight is that ES6 default exports are essentially named exports using the special default export name. Observing Babel's transpilation output:

// Input ES6 code
export const foo = 42;
export default 21;

// Output CommonJS code
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
var foo = exports.foo = 42;
exports.default = 21;

We can see that the default export is converted to an exports.default property, rather than replacing the entire exports object.

Error Analysis and Solutions

When using export default SlimShady and attempting to import via CommonJS require:

// Incorrect usage
var bar = require('./input');
new bar(); // Throws "bar is not a constructor" error

This occurs because the imported bar is actually an object containing a default property, not the direct constructor. The correct access method should be:

// Correct usage
var bar = require('./input').default;
new bar(); // Works correctly

Transpilation Handling of ES6 Import Syntax

When using ES6 import syntax, Babel automatically handles default export access:

// ES6 import syntax
import bar from './input';
console.log(bar);

// Transpiled to CommonJS
'use strict';

var _input = require('./input');
var _input2 = _interopRequireDefault(_input);

function _interopRequireDefault(obj) { 
  return obj && obj.__esModule ? obj : { default: obj }; 
}

console.log(_input2.default);

The _interopRequireDefault function checks whether the module has been marked as an ES6 module (via the __esModule property), returning it directly if true, otherwise wrapping the entire object in {default: obj} format.

Practical Development Recommendations

In projects mixing CommonJS and ES6 modules, we recommend:

  1. Standardize on one module system to avoid complexity from mixed usage
  2. If mixed usage is necessary, ensure understanding of transpiled code structure
  3. When using build tools (like Webpack, Rollup), configure correct module resolution rules
  4. In Node.js environments, consider using .mjs extensions to clearly identify ES6 modules

Compatibility Considerations

As Node.js's native support for ES6 modules continues to improve, these transpilation issues will gradually diminish. However, during this transition period, understanding the underlying transpilation mechanisms remains crucial for debugging and code optimization. Developers should monitor Node.js version updates and adjust module usage strategies accordingly.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.