Publish ES module (.mjs) to NPMJS, with backwards

2020-05-23 19:38发布

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?

1条回答
孤傲高冷的网名
2楼-- · 2020-05-23 20:35

Update:

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
(node:25573) ExperimentalWarning: The ESM module loader is experimental.
mjs






Old answer:

This method is no longer possible in more-recent versions of Node which removed automatic extension resolution for ESM, see Proposal for dual ESM/CommonJS packages for more information and some links to some potential new ways of doing this that may land in the future.

The trick is not to specify an exact path in the package.json main entry. Instead use an extension-less main entry, or supply both an index.js and index.mjs at the root of the package.

Option 1 - Extension-less main:

If you don't include the extension, Node will dynamically use the .mjs extension if available and using the ES6 loader, or fallback on .js.

"main": "lib/entry"

This will resolve to lib/entry.mjs in ES6 module mode, or lib/entry.js in CommonJS mode, with the ES6 loader falling back on the CommonJS version if the MJS file is not available.

Option 2 - Use index.mjs and index.js instead:

If your package supplies a root index.mjs and index.js Node will prefer the index.mjs when import-ed, and still use the index.js when require-ed (if no index.mjs is supplied, the ES6 loader will use the index.js). This means you can supply both an ES6 module version from index.mjs and a CommonJS transpiled version from index.js.


Possible Issue:

There is one potential issue that I can think of though, if users of your package mix using both the ES6 and CommonJS modules and expect them to reference the same set of objects. In certain edge-cases, this could be an issue, but multiple packages using the exact same module instance was never a given anyway because different packages can require different versions of the package.


Example:

Example project:

index.mjs

import testmod from 'testmod';
console.log(testmod);


index.js

const testmod = require('testmod');
console.log(testmod);


node_modules/testmod/package.json

{
    "name": "testmod",
    "version": "1.0.0"
}

In this file, you can optionally use an extension-less main entry like this:

{
    "name": "testmod",
    "version": "1.0.0",
    "main": "index"
}


node_modules/testmod/index.mjs

export default {
    from: 'index.mjs'
};


node_modules/testmod/index.js

module.exports = {
    from: 'index.js'
};

Example output (ExperimentalWarning omitted):

$ node --experimental-modules index.mjs
{ from: 'index.mjs' }

$ node --experimental-modules index.js
{ from: 'index.js' }

$ node index.js
{ from: 'index.js' }
查看更多
登录 后发表回答