module.exports vs. export default en Node.js y ES6

318

¿Cuál es la diferencia entre Node's module.exportsy ES6's export default? Estoy tratando de averiguar por qué aparece el error "__ no es un constructor" cuando intento hacerlo export defaulten Node.js 6.2.2.

Que funciona

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

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

// This works
module.exports = SlimShady

Lo que no funciona

'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
fuente

Respuestas:

402

El problema es con

  • Cómo se emulan los módulos ES6 en CommonJS
  • cómo importas el módulo

ES6 a CommonJS

Al momento de escribir esto, ningún entorno admite módulos ES6 de forma nativa. Al usarlos en Node.js, debe usar algo como Babel para convertir los módulos a CommonJS. Pero, ¿cómo sucede eso exactamente?

Muchas personas consideran module.exports = ...equivalente export default ...y exports.foo ...equivalente a export const foo = .... Sin embargo, eso no es del todo cierto, o al menos no cómo lo hace Babel.

Las defaultexportaciones de ES6 en realidad también se denominan exportaciones, excepto que defaultes un nombre "reservado" y hay un soporte especial de sintaxis para ello. Veamos cómo compila Babel las exportaciones con nombre y predeterminadas:

// 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; 

Aquí podemos ver que la exportación predeterminada se convierte en una propiedad del exportsobjeto, al igual que foo.

Importar el módulo

Podemos importar el módulo de dos maneras: usando CommonJS o usando la importsintaxis ES6 .

Su problema: creo que está haciendo algo como:

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

esperando que barse le asigne el valor de la exportación predeterminada. Pero como podemos ver en el ejemplo anterior, la exportación predeterminada se asigna a la defaultpropiedad.

Entonces, para acceder a la exportación predeterminada, en realidad tenemos que hacer

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

Si usamos la sintaxis del módulo ES6, a saber

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

Babel lo transformará a

'use strict';

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

var _input2 = _interopRequireDefault(_input);

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

console.log(_input2.default);

Puede ver que cada acceso a barse convierte en acceso .default.

Felix Kling
fuente
¿No tenemos un duplicado para esto?
Bergi
3
@Bergi: No busqué tbh (lástima de mí :(). Ciertamente hay preguntas sobre el mismo problema, pero preguntaron de una manera diferente. ¡Avísame si encuentras algo que encaje!
Felix Kling
1
OK, tomó algún tiempo encontrarlos, pero ahora puede usar sus poderes recién adquiridos y elegir uno de ¿Cómo usar correctamente ES6 "export default" con CommonJS "require"? y no se puede requerir () valor de exportación predeterminado en Babel 6.x como objetivo de engaño :-)
Bergi
1
Qué irónico que yo puedo hacer esto ahora: D
Felix Kling
1
@djKianoosh: compruébalo por ti mismo . Después de la asignación a module.exports, exportsy module.exportstienen valores diferentes, entonces la asignación a exports.defaultsno tiene efecto (porque module.exportses lo que se exporta). En otras palabras, es exactamente lo mismo que si solo lo hicieras module.exports = { ... }.
Felix Kling
1

Debe configurar Babel correctamente en su proyecto para usar el valor predeterminado de exportación y exportar

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

luego agregue la siguiente configuración en .babelrc

"plugins": [ 
       "@babel/plugin-proposal-export-default-from"
      ]
Hassan Azhar Khan
fuente
1

Felix Kling hizo una gran comparación con esos dos, para cualquiera que se pregunte cómo hacer un valor predeterminado de exportación junto con las exportaciones nombradas con module.exports en nodejs

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

// now you have
let DAO = require('_/helpers/DAO');
// DAO by default is exported class or function
DAO.initDAO()
moein rahimi
fuente
-61

tl; dr ahora para que esto funcione, el archivo que requiere o importa SlimShadydebe compilarse usando Babel con 'use strict'.

Estoy usando babel-cli6.18.0 en el proyecto donde inicialmente encontré este error.

Sin 'use strict'es malas noticias osos

var SlimShady = require('./slim-shady');
var marshall = new SlimShady();  // uh, oh...

'uso estricto', por favor

'use strict'
import SlimShady from './slim-shady'
var marshall = new SlimShady()  // all good in the hood
Marty Chang
fuente
13
Esto no tiene sentido. Cada fuente que usa importdeclaraciones es un módulo, y esos ya son estrictos. La diferencia real es sobre requerir vs importar.
Bergi
1
Lo que tiene sentido es usar en importlugar de requirey en export defaultlugar de exports.default.
Corey Alix
104
Esta tiene que ser la respuesta más votada que he visto en stackoverflow
Jimi
44
@Jimi Eso es porque es la cuarta respuesta más votada en todo el sitio.
pppery