Skip to content

Conversation

@EzraBrooks
Copy link
Contributor

Fixes #277 by specifying both a CJS and ESM import path for browsers.

Supersedes #272.

@EzraBrooks
Copy link
Contributor Author

This seems to behave correctly, at least when generating an import map with jspm:

"scopes": {
    "./": {
      "bson": "https://unpkg.com/bson@7.0.0/lib/bson.mjs",
      "cbor2": "https://unpkg.com/cbor2@2.0.1/lib/index.js",
      "eventemitter3": "https://unpkg.com/eventemitter3@5.0.1/dist/eventemitter3.esm.js",
      "fast-png": "https://unpkg.com/fast-png@7.0.1/lib/index.js",
      "uuid": "https://unpkg.com/uuid@13.0.0/dist/index.js",
      "ws": "https://unpkg.com/ws@8.18.3/browser.js"
    },
    "https://unpkg.com/": {
      "@cto.af/wtf8": "https://unpkg.com/@cto.af/wtf8@0.0.2/lib/index.js",
      "iobuffer": "https://unpkg.com/iobuffer@6.0.1/lib/iobuffer.js",
      "pako": "https://unpkg.com/pako@2.1.0/dist/pako.esm.mjs"
    }
  }

(taken from the project I have been debugging this issue on)

@lpinca
Copy link
Member

lpinca commented Nov 19, 2025

This makes bundlers use the already bundled versions. I'm not very happy with it. I think it does not work well with source maps. All I can think of is adding an export for ./dist/*.js so that browser like environments can import 'eventemitter3/dist/eventemitter3.esm.js', etc.

@EzraBrooks
Copy link
Contributor Author

That isn't true in my testing. Vite is still bundling index.mjs, not the dist file.

@lpinca
Copy link
Member

lpinca commented Nov 19, 2025

You are right, the "browser" condition is under "exports" here, not top level. Anyway is adding it after "import" and "require" correct? How is it evaluated?

@EzraBrooks
Copy link
Contributor Author

Hmm, unsure. The Node.js docs say the rules should be ordered from most specific to least specific, so maybe this should be at the top instead.

@lpinca
Copy link
Member

lpinca commented Nov 19, 2025

Yes, after "types".

@lpinca
Copy link
Member

lpinca commented Nov 19, 2025

Moving it at the top (after "types") makes esbuild (and I guess other bundlers) use the dist files.

$ cat index.js 
import EventEmitter from 'eventemitter3';

console.log(EventEmitter);
$
$ npx esbuild --bundle index.js
(() => {
  // node_modules/eventemitter3/dist/eventemitter3.esm.js
  ...

@EzraBrooks EzraBrooks force-pushed the add-browser-esm-support branch from d66a861 to a10d558 Compare November 19, 2025 20:53
@EzraBrooks
Copy link
Contributor Author

Interesting that leaving it last seems to allow bundlers to continue using the unbundled code but allows jspm to generate an import map correctly 🤔

@EzraBrooks EzraBrooks force-pushed the add-browser-esm-support branch from a10d558 to c082c0c Compare November 19, 2025 21:22
@EzraBrooks EzraBrooks marked this pull request as draft November 19, 2025 21:33
@EzraBrooks
Copy link
Contributor Author

Okay, trying to outline the real problem here:

  • The convention for providing support for both CJS and ESM in Node.js is to have an ESM file that re-exports the CJS API
  • However, importing an ESM file that imports a CJS file in a browser will fail because browsers don't support CJS due to it being a Node.js invention
  • We can tell various browser tooling (bundlers, importmap generators, etc.) to resolve the pre-bundled ESM file which doesn't import anything by providing a browser field within exports in package.json
  • This makes it work in environments where the consuming bundler doesn't optimize/re-bundle EventEmitter3 (like mine and like the environment described in [ESM] Incorrect default export, causing issue in Vite-powered projects when loaded as ESM #277), but in environments where EventEmitter3 is being bundled, it causes the bundler to consume a bundle, potentially screwing up sourcemaps and just generally being a bit ouroboros-y.

So far I've yet to find a fix for this. I might just have to keep my sed workaround in my package for now..

@lpinca
Copy link
Member

lpinca commented Nov 20, 2025

As written above I think a possible workaround is to export the bundles

diff --git a/package.json b/package.json
index 553284b..717998b 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
       "import": "./index.mjs",
       "require": "./index.js"
     },
+    "./*.js": "./dist/*.js",
     "./package.json": "./package.json"
   },
   "main": "index.js",

so that for cases like #277 it is possible to do something like this

import EventEmitter from 'eventemitter3/eventemitter3.esm.js';

@EzraBrooks
Copy link
Contributor Author

Yeah, I was hoping to find something nicer since my library supports both Node and the browser and I'd rather have sourcemaps work. But that may be the only solution for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ESM] Incorrect default export, causing issue in Vite-powered projects when loaded as ESM

2 participants