cbor-redux icon indicating copy to clipboard operation
cbor-redux copied to clipboard

Support ESM in Node, or move to ESM-only

Open joeltg opened this issue 2 years ago • 2 comments

First, thanks so much for writing this library! It's been awesome to watch the Deno ecosystem grow, and great to have a minimal modern CBOR implementation.

I'm writing an ESM-only library for use in Node, the browser, and Deno. However, the way cbor-redux is structured doesn't actually let me use it in Node by default:

// test.mjs
import { CBOR } from "cbor-redux"
console.log(CBOR)
% node test.mjs
file:///Users/.../test.mjs:1
import { CBOR } from "cbor-redux";
         ^^^^
SyntaxError: Named export 'CBOR' not found. The requested module 'cbor-redux' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'cbor-redux';
const { CBOR } = pkg;

    at ModuleJob._instantiate (internal/modules/esm/module_job.js:121:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:166:5)
    at async Loader.import (internal/modules/esm/loader.js:178:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

This is because Node.js supports native ESM modules, but only interprets an imported library as an ESM module if it has "type": "module" in package.json.

This is independent of the exports map in package.json. Node.js is trying to load ./esm/CBOR.js, but when it does will try to parse it as a CommonJS module (which will fail). For example:

// test2.mjs
import CBOR from "cbor-redux"
console.log(CBOR)
% node test2.mjs
(node:23296) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/.../node_modules/cbor-redux/esm/CBOR.js:22
export class TaggedValue {
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:988:16)
    at Module._compile (internal/modules/cjs/loader.js:1036:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
    at Module.load (internal/modules/cjs/loader.js:937:32)
    at Function.Module._load (internal/modules/cjs/loader.js:778:12)
    at ModuleWrap.<anonymous> (internal/modules/esm/translators.js:199:29)
    at ModuleJob.run (internal/modules/esm/module_job.js:170:25)
    at async Loader.import (internal/modules/esm/loader.js:178:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

There are a variety of ways to support this, but the simplest is just moving ESM-only and leaving CommonJS behind entirely. There's an massive ongoing movement across the OSS web ecosystem to do this, summarized by Sindre Sorhus here: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c

The upside is that you could have a single unified ESM dist target that works for Node and the browser (Deno is obviously a separate thing); the downside is that it involves tinkering with however the library is built, which is always annoying, and releasing a new major version that no longer support old versions of Node (< v12).

Is this something you'd consider? I know it's exhausting to continually change export formats but I think it's become clear that ESM-only is truly the one long-term stable universal solution for JS.

joeltg avatar Sep 07 '21 20:09 joeltg

I'll be cuing this up this month. My plan is to move wholly over to Deno-only support in the repo and then deploy Node-compatible version to NPM using https://github.com/denoland/dnt.

aaronhuggins avatar Apr 05 '22 16:04 aaronhuggins

Awesome, that sounds like a great plan for everyone!

joeltg avatar Apr 09 '22 00:04 joeltg