9

Up to Node v8.5.0, publishing a module written in ES6 to NPMJS was a straightforward process: transpile the ES6 code using a tool like Babel, and publish to NPMJS the resulting lib directory, while your GitHub repo contains the src files.

With v8.5.0, Node has released experimental support for native modules (export/import) via the --experimental-modules flag. It is now possible to publish purely-ES6 modules to NPMJS, and use them without any transpilation, as long as the files involved have an .mjs extension.

How can I publish an ES6 module (.mjs) so that it can also be used with older Node versions, which don't support ES native modules?

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
  • 1
    Given that nothing else supports `.mjs` files yet, it seems like a good way to complicate things with minimal gains. – loganfsmyth Sep 26 '17 at 05:06
  • @loganfsmyth: yarn and npm "support" `.mjs` in that they don't care about the extension. The gain is the very reason why native module support has been worked on. – Dan Dascalescu Sep 26 '17 at 09:07
  • Yeah I should clarify. It's going to be awesome, I just feel like it's too early to start doing this. For instance, Node's layer for loading CommonJS from ES6 is different from Babel and Webpack, so code that works in one is not guaranteed to work in the other. Webpack also hasn't been set up to parse `.mjs` files in a spec-compliant way. Babel for instance allows `module.exports` and `require` usage in ES6 modules, and Webpack allows `require`. My is essentially that if you publish a module saying it supports native modules, people will expect it to work in those usecases too. – loganfsmyth Sep 26 '17 at 16:20
  • Browsers also won't support loading CommonJS (probably ever), so I guess you could do this for browser and Node support if you were planning to write a zero-dependency library, but that's not super common? Or if all you care about is Node, go for it, but then you're essentially supporting two separate interfaces (ES6 and CommonJS), one of which is still experimental. – loganfsmyth Sep 26 '17 at 16:23
  • 1
    Just an FYI: I've updated my answer with a way to continue to use the `main` entry. – Alexander O'Mara Oct 27 '17 at 23:23
  • 1
    The [documentation](https://nodejs.org/docs/latest-v9.x/api/esm.html) didn’t mention this case. It should be improved IMO. – Franklin Yu Jan 26 '18 at 15:09

1 Answers1

12

This is possible with 13.7.0+ using conditional exports (which as of 13.10.0+ are no longer experimental). It's not well documented or obvious how to do this in a completely backwards-compatible way, but here's the trick which I previously researched back when it was experiemental:

node_modules/mod/package.json

{
    "main": "./lib.js",
    "exports": {
        ".": [
            {
                "import": "./lib.mjs",
                "require": "./lib.js",
                "default": "./lib.js"
            },
            "./lib.js"
        ]
    }
}

node_modules/mod/lib.js

exports.format = 'cjs';

node_modules/mod/lib.mjs

export const format = 'mjs';

Now it's possible to use both CommonJS:

main.js

const {format} = require('mod');

console.log(format);
$ node main.js
cjs

And ES Modules:

main.mjs

import {format} from 'mod';

console.log(format);
$ node main.mjs
mjs

Prior to this is was possible at one point to just use an extension-less main entry in package.json, but this feature was removed. See the revision history on this answer if interested.

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • Thank you! This worked and I've just published [my first module with support for both ES6 native modules and](https://github.com/dandv/local-iso-dt) the old-style transpiled `index.js`. – Dan Dascalescu Dec 12 '17 at 05:54
  • Hey, just wanted to let you know that [Node.js v14](https://nodejs.org/api/packages.html#packages_conditional_exports) has made breaking changes to the Conditional Exports api, so you may want to update your answer (and also fix the broken link). Thanks. – chharvey Oct 29 '20 at 22:46