NPM Quality Standards: __esModule

Myself and probably others included have no doubt stumbled into the annoyance of __esModule which is a weird thing to allow both exporting a default and named fields.

Traditionally module.exports or exports for short only allows you to export a single variable. Simple but many people adhere to a usage pattern of mixing up both for there they want to export a non-object by default but also provide named exports from the same path, partly because normally you require a mode module as a single named dependency with no explicit support for sub imports (though you can import subpaths).

Someone would want to make both possible:

var main = require(‘library’);
main();
var {util} = require(‘library’);
util();

Traditionally the only way to do this would be:

function main() {}
function util() {}
main.util = util;
module.exports = main;

This appears to be one origin of weird build warnings about circular references.

The natural choice here is to just bite the bullet and have:

module.exports = {main, util};
var {main, util} = require(‘library’);

Instead people have been using __esModule which as far as I’m aware is purely some babel thing though I’m not really sure. All it does is displace the entry point to default.

This means that:

var main = require(‘library’);

Would compile to the equivalent of:

require(‘library’).default;

Instead you might put…

exports.default = main;
export.util = main;

I have no idea how people are compiling this with babel but they’re releasing libraries with modules that don’t have that compiled out (which you can’t really do without clobbering the function or doing really weird proxy objects).

This becomes really problematic considering:

  • It’s redundant with node’s support for ESM modules, albeit experimental.
  • People who do this have a terrible habit to not document that you need to add .default and instead have the simple require.

The latter point is really annoying. With ESM coming along I think it’s time to flag this hack as a quality issue and to recommend against it or that documentation is clear about it. Though there will be some potential unpleasantness for libraries providing both ESM and CJS. ESM bit the bullet on supporting the way people are doing things which isn’t really standard code (to change the return based on assignment context).

Providing an additional require function would probably be an easy fix.

This particular hack either ends up pointless, requires .default everywhere, a transpile pass or a custom require function. Some kind of node.js hack is not ideal since we already have import with ESM as a keyword and doing things with require turns it from a plain old function living free to something that has to be context aware. Though there is a question of how dynamic imports handle the problem.

Either way, there really is an issue with packages having usage examples that are broken.