Shuhei Kagawa

How to export CommonJS and ES Module

Jan 5, 2017 - JavaScript

After my previous post about jsnext:main and module, there came another issue.

Here is the twists and turns that I wandered to solve the problem.

Exports

The code of material-colors looked like the following.

colors.js specified in main (CommonJS version)

module.exports = {
  red: { /* ... */ },
  blue: { /* ... */ }
};

colors.es2015.js specified in jsnext:main/module (ES Module version)

export var red = { /* ... */ };
export var blue = { /* ... */ };

Then the ES Module file can get benefit of tree shaking if it's imported by named imports.

Problem of having only named exports

The colors.es2015.js broke react-color when built with Webpack 2 because it was doing default import but colors.es2015.js didn't have default export.

import material from 'material-colors';
console.log(material.red);

So @echenley suggested to change it to a wildcard import.

import * as material from 'material-colors';
console.log(material.red);

It worked well, but I removed jsnext:main and module because other libraries with default import may break on Webpack 2 and material-colors is already tiny without tree shaking anyway.

Have a default export

After a while, I came up with a better solution to have a default export in addition to named exports. Then it will work well with tree shaking and won't break default import. Pretty obvious after coming up.

export var red = { /* ... */ };
export var blue = { /* ... */ };

export default {
  red: red,
  blue: blue
};

So?

To keep maximum compatibility for CommonJS and ES Module:

  • If your CommonJS module exports only one thing, like encouraged in the npm world, export it as a default export.
  • If your CommonJS module exports multiple things, which essentially exports an object with them as properties, export named exports. In addition to it, it's safer to have a default export just in case for the problem described above.