Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Importing vs Requiring with Babel in Node [duplicate]

I want to import a class inside one file:

"use strict";
import models from "../model";
class Foo {
    bar() {
    }
}
export default new Foo();

It works when I use import, like:

import Foo from "./foo";
console.log(Foo.bar); // [Function bar]

The problem is that doing it with require gives me undefined:

var Foo = require("./foo");
console.log(Foo.bar); // undefined

and the Foo variable seems to be an empty class. Why is that so? What's going on?

like image 809
user99999 Avatar asked Jan 04 '23 11:01

user99999


1 Answers

TL;DR

This is because importing with import is different than require. When you import a module with the syntax import X from "Y", the default export is automatically imported, as the syntax is meant to import the default export by specification. However, when you require, the default export is not automatically imported like you'd expect. You must add .default to get the default export with require like: require("./foo").default.

Babel Transpilation

Take a look at how Babel transpiles some example code:

export { x }

Becomes:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.x = x;

This makes sense as x is just exported normally. Then you would proceed to do:

require("module").x;

To receive x. Similarly, try the following:

export default new Foo();

That becomes:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = new Foo();

You'll see that the default export is attached to the exports object as a property named default, just as x was exported with property x. That means, to get the default export with require, you'll need to do:

require("module").default;

Now to explain the why it's undefined. In the line:

var Foo = require("./foo");

Foo here is actually just an object with a default property:

{
    default: //Whatever was exported as default
}

Thus, trying to do Foo.bar will yield undefined because there is no bar property. You need to access the default property to access your instance.

You might think this is a little clunky, and other do too. That's why there's a plugin to get rid of the need for the extra .default. The plugin does module.exports = exports["default"] to assign the module.exports from ES5, to exports["default"].1

Difference Between import and require

For import syntax, Babel does the following transpilation:

import Foo from "./foo";
Foo.bar();

Becomes:

"use strict";

var _foo = require("./foo");

var _foo2 = _interopRequireDefault(_foo);

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

_foo2.default.bar();

To break it down line by line, Babel just requires the module. Then, it calls _interopRequireDefault on the module. A check is done with obj && obj.__esModule to make sure that the module actually exports anything and the module is exported with ES2015/ES6 syntax. If it is, then the module is returned as is, or else { default: obj } is returned. This is to ensure that module.exports = x in ES5 is treated the same as export default x in ES2015/ES6. You'll notice that for default import syntax, Babel automatically adds .default to retrieve the default export.

However, the corresponding code with require:

var Foo = require("./foo");
Foo.bar();

Is completely valid ES5 code, so nothing is transpiled. Consequently, .default is never added to Foo when it is used. Thus, the default export is not retrieved.


Notes

1It should be noted that module.exports and exports just refer to the same object, see this answer. module.exports holds the data which is exported from the module. The reason why module.exports = x successfully exports x as default and does not need the extra .default on require is because you're assigning the module.exports to one single thing. Since module.exports holds the data that's imported, require("module") imports whatever module.exports is, which is x. If module.exports were 3, then require("module") would be 3. If module.exports were an object like { blah: "foo" }, then require("module") would be { blah: "foo" }. By default, module and module.exports are objects.

One the other hand, exports.default exports an object (that refers to the same object as module.exports) with a default property that holds the default export. You may want to do exports = x to export x as default but since exports is assigned a reference to module.exports this will break the reference and exports will no longer point to module.exports.

like image 177
Andrew Li Avatar answered Jan 07 '23 11:01

Andrew Li