Can't Require() Default Export Value in Babel 6.X

Can't require() default export value in Babel 6.x

TL;DR

You have to use

const app = require('./app').default;
app();

Explanation

Babel 5 used to have a compatibility hack for export default: if a module contained only one export, and it was a default export, it was assigned to module.exports. So, for example, your module app.js

export default function () {}

would be transpiled to this

"use strict";

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

exports["default"] = function () {};

module.exports = exports["default"];

This was done purely for compatibility with require-ing Babel-transpiled modules (like you are doing). It was also inconsistent; if a module contained both named and default exports, it could not be require-d.

In reality, according to the ES6 module spec, a default export is no different than a named export with the name default. It is just syntactic sugar which can be statically resolved at compile time, so this

import something from './app';

is the same as this

import { default as something } from './app';

That being said, it appears that Babel 6 decided to drop the interoperability hack when transpiling modules. Now, your module app.js is transpiled as

'use strict';

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

exports.default = function () {};

As you see, no more assignment to module.exports. To require this module, you need to do

const app = require('./app').default;
app();

Or, more concisely, and closer to your original code:

require('./app').default();

Importing vs Requiring with Babel in Node

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.

module.exports vs. export default in Node.js and ES6

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.

Why can't I import a default export with import ... as with BabelJS

You can import the default export by either

import Test2 from './test';

or

import {default as Test2} from './test';

The default export doesn't have Test as a name that you would need to alias - you just need to import the default under the name that you want.

The best docs I've found so far is the article ECMAScript 6 modules: the final syntax in Axel Rauschmayers blog.

Mixed default and named exports in Node with ES5 syntax

You want to assign the value of module.exports to be your default function, and then put all the named exports as properties on that function.

const defaultFunction = () => { console.log('default!'); };
const namedFunction1 = () => { console.log('1!'); };
const namedFunction2 = () => { console.log('2!'); };

const myModule = module.exports = defaultFunction;
myModule.namedFunction1 = namedFunction1;
myModule.namedFunction2 = namedFunction2;

Let's say that was in myModule.js. Then you can do this:

const myModule = require('./myModule');
myModule(); // Prints: 'default!'
myModule.namedFunction1(); // Prints: '1!'

Re-export default in ES 6 modules

If you use proposal-export-default-from Babel plugin (which is a part of stage-1 preset), you'll be able to re-export default using the following code:

export default from "./App.js"

For more information see the ECMAScript proposal.


Another way (without this plugin) is:

export { default as App } from "./App.js"

The above is a very common practice when separate files, each with its own export, have all something in common, for example, utils, so if, for example, one would want to import 3 utility functions, instead of having to write multiple imports:

import util_a from 'utils/util_a' 
import util_b from 'utils/util_b'
import util_c from 'utils/util_c'

One could import any of the utilities in a single-line:

import { util_a, util_b , util_c } from 'utils' 

By creating an index.js file in the /utils folder and import all the defaults of all the utilities there and re-export, so the index file will serve as the "gateway" for all imports related to that folder.

The difference between require(x) and import x

This simple image will help to you understand the differences between require and import.

Sample Image

Apart from that,

You can't selectively load only the pieces you need with require but with import, you can selectively load only the pieces you need, which can save memory.

Loading is synchronous(step by step) for require on the other hand import can be asynchronous(without waiting for previous import) so it can perform a little better than require.

SyntaxError: Cannot use import statement outside a module

Verify that you have the latest version of Node.js installed (or, at least 13.2.0+). Then do one of the following, as described in the documentation:

Option 1

In the nearest parent package.json file, add the top-level "type" field with a value of "module". This will ensure that all .js and .mjs files are interpreted as ES modules. You can interpret individual files as CommonJS by using the .cjs extension.

// package.json
{
"type": "module"
}

Option 2

Explicitly name files with the .mjs extension. All other files, such as .js will be interpreted as CommonJS, which is the default if type is not defined in package.json.

Why does require not behave the same as import when same structure is given

You import the default export of module and require the module itself.

const ExamplePost = require(`${postName}.md`).default

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

Also check Can't require() default export value in Babel 6.x



Related Topics



Leave a reply



Submit