205

Before, babel would add the line module.exports = exports["default"]. It no longer does this. What this means is before I could do:

var foo = require('./foo');
// use foo

Now I have to do this:

var foo = require('./foo').default;
// use foo

Not a huge deal (and I'm guessing this is what it should have been all along). The issue is that I have a lot of code that depended on the way that things used to work (I can convert most of it to ES6 imports, but not all of it). Can anyone give me tips on how to make the old way work without having to go through my project and fix this (or even some instruction on how to write a codemod to do this would be pretty slick).

Thanks!

Example:

Input:

const foo = {}
export default foo

Output with Babel 5

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
var foo = {};
exports["default"] = foo;
module.exports = exports["default"];

Output with Babel 6 (and es2015 plugin):

"use strict";

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

Notice that the only difference in the output is the module.exports = exports["default"].


Edit

You may be interested in this blogpost I wrote after solving my specific issue: Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
kentcdodds
  • 27,113
  • 32
  • 108
  • 187
  • I'm curious, what are the cases where need `require` if you are working in a codebase that uses Babel? Chances are, there are other approaches that would allow you to avoid that anyway. – loganfsmyth Nov 03 '15 at 18:10
  • I'm leveraging a feature of Webpack which will not require code if it's found in dead code like: `if (false) { require('./foo') }` with webpack would skip actually including `foo.js` in the resulting bundle. – kentcdodds Nov 03 '15 at 18:11
  • What ends up being your `false` toggle there? If it's a condition that is available in your webpack config, there may be another option. – loganfsmyth Nov 03 '15 at 18:19
  • 1
    This one caused me issues for hours before I found this post. I ended up replacing all of my `export default {foo, bar}` with `module.exports = {foo, bar}`. I quite liked the _incorrect_ method which is now not supported. – stumct Jan 23 '16 at 16:40
  • @loganfsmyth It's very useful for passing around entire modules without much repetition in code. Have a look at this gist https://gist.github.com/loopmode/3eeaf0764c30439add1d8008e39d0267 – Jovica Aleksic May 07 '18 at 20:02

4 Answers4

109

If you want CommonJS export behavior, you'll need to use CommonJS directly (or use the plugin in the other answer). This behavior was removed because it caused confusion and lead to invalid ES6 semantics, which some people had relied on e.g.

export default {
  a: 'foo'
};

and then

import {a} from './foo';

which is invalid ES6 but worked because of the CommonJS interoperability behavior you are describing. Unfortunately supporting both cases isn't possible, and allowing people to write invalid ES6 is a worse issue than making you do .default.

The other issue was that it was unexpected for users if they added a named export in the future, for example

export default 4;

then

require('./mod');
// 4

but

export default 4;
export var foo = 5;

then

require('./mod')
// {'default': 4, foo: 5}
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • I agree with you (and noted) that the before behavior was incorrect, but my question was how to get around the issue. I was relying heavily on the incorrect behavior (didn't realize it was incorrect until this morning). I would prefer to not have to update everything all at once... – kentcdodds Nov 03 '15 at 18:10
  • The only fix to get the current behavior would to be switch your code to use CommonJS directly, or to stay on Babel 5 until you have time to update. – loganfsmyth Nov 03 '15 at 18:12
  • 4
    @kentcdodds we can write a webpack loader to keep this working (or a babel plugin). I'm surprised they're not providing one (or publicizing the change more heavily!) – Jamund Ferguson Nov 03 '15 at 18:20
  • I am confused by this... if I do `export default function () {}` in module A and then `import a from 'a'` in module B, with Babel 6 `a` would be `{ default: function () {} }`... From what I can understand from http://exploringjs.com/es6/ch_modules.html#sec_mixing-named-and-default-exports this should work and I should get the exported function in B, not the object. – mamapitufo Jan 18 '16 at 16:49
  • @mamapitufo That should work, but it's hard to say what's wrong without an example to look at. Feel free to drop by Babel's support channel on Slack if you want to chat. – loganfsmyth Jan 18 '16 at 17:07
  • @loganfsmyth thank you, I will. That does work in a simple example, but not if I compile A as a separate library and then try to use it... I'll prepare an example and I'll post it in slack. Thanks again! – mamapitufo Jan 18 '16 at 17:08
  • @loganfsmyth For library authors writing in ES6 but who are targeting CommonJS, I think it is very fine to have the old behavior, and it should be easily configurable, especially if that library export mostly a single default class export. Luckily, the other answer links to the plugins that help with the issue. – trusktr Mar 19 '17 at 05:20
  • @loganfsmyth Besides, importing an ES6 module from a CommonJS module isn't even... spec'd. It's completely arbitrary how that should work. I would think the best solution is to let lib authors can control over it. I also think that the `__esModule` and `default` property are more for machine-generated output in order to enforce proper ES6 semantics in the output runtime, less so for human usability. CommonJS authors have the option to assign named exports onto the default export, so ES6 authors targeting CJS should be able to do that as well, if they want to, IMO. Afterall, it's not spec'd. – trusktr Mar 19 '17 at 05:27
94

You can also use this plugin to get the old export behavior back.

SimenB
  • 6,410
  • 2
  • 23
  • 27
34

For library authors you may be able to work around this problem.

I usually have an entry point, index.js, which is the file I point to from the main field in package.json. It does nothing other than re-export the actual entry point of the lib:

export { default } from "./components/MyComponent";

To workaround the babel issue, I changed this to an import statement and then assign the default to module.exports:

import MyComponent from "./components/MyComponent";
module.exports = MyComponent;

All my other files stay as pure ES6 modules, with no workarounds. So only the entry point needs a change slightly :)

This will work for commonjs requires, and also for ES6 imports because babel doesn't seem to have dropped the reverse interop (commonjs -> es6). Babel injects the following function to patch up commonjs:

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

I've spent hours battling this, so I hope this saves someone else the effort!

WickyNilliams
  • 5,218
  • 2
  • 31
  • 43
  • For some reason, I never got my heads spinned right about `module.exports` and `export default` stuff. Now we're back to square one? – windmaomao Feb 20 '18 at 17:44
  • @windmaomao what do you mean? This is a trick so that commonjs users do not have to `require("whatever").default`. If you're not a library author, this is probably irrelevant – WickyNilliams Feb 21 '18 at 15:37
2

I have had such kind of issue. And this is my solution:

//src/arithmetic.js

export var operations = {
  add: function (a, b) {
      return a + b;
  },

  subtract: function (a, b) {
      return a - b;
  }
};

//src/main.js

import { operations }  from './arithmetic';

let result = operations.add(1, 1);

console.log(result);
Ihor Pavlyk
  • 1,111
  • 13
  • 10