513

What is the difference between Node's module.exports and ES6's export default? I'm trying to figure out why I get the "__ is not a constructor" error when I try to export default in Node.js 6.2.2.

What works

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

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

// This works
module.exports = SlimShady

What doesn't work

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

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

// This will cause the "SlimShady is not a constructor" error
// if in another file I try `let marshall = new SlimShady()`
export default SlimShady
Marty Chang
  • 6,269
  • 5
  • 16
  • 25

3 Answers3

603

The issue is with

  • how ES6 modules are emulated in CommonJS
  • how you import the module

ES6 to CommonJS

At the time of writing this, no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS. But how exactly does that happen?

Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.

ES6 default exports are actually also named exports, except that default is a "reserved" name and there is special syntax support for it. Lets have a look how Babel compiles named and default exports:

// input
export const foo = 42;
export default 21;

// output
"use strict";

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

Here we can see that the default export becomes a property on the exports object, just like foo.

Import the module

We can import the module in two ways: Either using CommonJS or using ES6 import syntax.

Your issue: I believe you are doing something like:

var bar = require('./input');
new bar();

expecting that bar is assigned the value of the default export. But as we can see in the example above, the default export is assigned to the default property!

So in order to access the default export we actually have to do

var bar = require('./input').default;

If we use ES6 module syntax, namely

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

Babel will transform it to

'use strict';

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

var _input2 = _interopRequireDefault(_input);

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

console.log(_input2.default);

You can see that every access to bar is converted to access .default.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Don't we have a duplicate for this? – Bergi Oct 27 '16 at 22:39
  • 3
    @Bergi: I didn't search tbh (shame on me :( ). There are certainly question about the same problem, but asked in a different way. Let me know if you find something that fits! – Felix Kling Oct 27 '16 at 22:42
  • 1
    OK, it took some time to find these, but you may now use your newly acquired powers and choose one of [How to correctly use ES6 “export default” with CommonJS “require”?](http://stackoverflow.com/q/35971042/1048572) and [Can't require() default export value in Babel 6.x](http://stackoverflow.com/q/33704714/1048572) as a dupe target :-) – Bergi Oct 27 '16 at 22:51
  • 1
    How ironic that I can do this now :D – Felix Kling Oct 27 '16 at 23:03
  • What if you combine them? Like so: `export default module.exports = { //module code here }` – djKianoosh Mar 31 '17 at 20:51
  • 1
    @djKianoosh: [See for yourself](https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015&targets=&browsers=&builtIns=false&experimental=false&loose=false&spec=false&code=export%20default%20module.exports%20%3D%20%7B%0A%20%20%2F%2Fmodule%20code%20here%0A%7D&playground=true). After the assignment to `module.exports`, `exports` and `module.exports` have different values, so the assignment to `exports.defaults` has no effect (because `module.exports` is what gets exported). In other words, it's exactly the same as if you only did `module.exports = { ... }`. – Felix Kling Mar 31 '17 at 21:55
  • 2
    How can we export both a default and a named value so that both these two will work on the client code side: `ES6` -> `import library, { a, b, c } from "library"; `, `commonJS` -> `const library = require("library"); const { a, b, c } = require("library")`? Just like with `React`, where when using ES6 we can do `import React, { useEffect, useState } from "react";` and when using commonJS we can do `const React = require("react"); const { useEffect, useState } = require("react");`... How can we achieve the same when authoring our own libraries? Thank you! – tonix Apr 23 '20 at 10:46
  • @tonix: Just define a default and multiple named exports? `export default 42; export const a = 21, b = 22, c = 23;` – Felix Kling Apr 23 '20 at 12:42
  • 1
    @FelixKling Thank you for your reply. I don't know, but it seems that React achieves this in some other way. If you look at their `index.js` file (https://github.com/facebook/react/blob/master/packages/react/index.js), there isn't any default export... How is that possible? What do you think? Thank you! – tonix Apr 23 '20 at 13:18
43

Felix Kling did a great comparison on those two, for anyone wondering how to do an export default alongside named exports with module.exports in nodejs

module.exports = new DAO()
module.exports.initDAO = initDAO // append other functions as named export

// now you have
let DAO = require('_/helpers/DAO');
// DAO by default is exported class or function
DAO.initDAO()
moein rahimi
  • 774
  • 10
  • 20
  • So let's say `initDAO`needs the object `DAO`. Do I have to import the current file itself? Or can I call something like `this.DAO()` – Paul Jan 16 '22 at 17:54
  • @Paul Since `initDAO` is on the `DAO` instance itself, you can use `this` to refer to the DAO object. – PoolloverNathan May 24 '22 at 15:18
1

You need to configure babel correctly in your project to use export default and export const foo

npm install --save-dev @babel/plugin-proposal-export-default-from

then add below configration in .babelrc

"plugins": [ 
       "@babel/plugin-proposal-export-default-from"
      ]