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:
- Standardize on one module system to avoid complexity from mixed usage
- If mixed usage is necessary, ensure understanding of transpiled code structure
- When using build tools (like Webpack, Rollup), configure correct module resolution rules
- In Node.js environments, consider using
.mjsextensions 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.