isaacs / node-primordials

https://isaacs.github.io/node-primordials/
11 stars 3 forks source link

Dual package hazard #3

Closed jcbhmr closed 1 year ago

jcbhmr commented 1 year ago

This package seems to encounter the dual package hazard where the same code is bundled twice. Once for CJS and once for ESM in a final browser dist bundle. There's no actual published npm packages that have this as a dependency (yet), but I encountered this in dev like this: https://stackblitz.com/edit/vitejs-vite-4y95fw?file=html-prompt.js,node-util-types.cjs,main.js,dist%2Fassets%2Findex-bc7f32d1.js&terminal=dev (simulated by one mjs file and one cjs file that could easily be actual package.json packages)

https://nodejs.org/api/packages.html#packages_dual_package_hazard

The fix to this is to use CJS as the main implementation, and either a) do nothing and let Node.js infer named exports or b) explicitly export things in a wrapper.mjs or similar file or c) only support ESM with NO CJS build

// wrapper.mjs
import moduleExports from "./index.cjs";
export const { a, b, c } = moduleExports;

There's an open issue on typescript github to support changing file extensions at tsc buildtime. That hasn't been encouraged since it's mostly used to cause the not-so-great dual package hazard. Instead TS team seems to recommend that you just build an ESM or CJS and then wrap it in another .mjs or .cjs file as a facade for the other module system. https://github.com/microsoft/TypeScript/issues/49462

isaacs commented 1 year ago

I write almost all my packages as hybrids these days. (The only exceptions are purely CLI packages, where it's just easier to use ESM only, since there's no chance that someone might require() or import it.)

I assume that multiple instances of any node package might be present. Module uniqueness hasn't ever been guaranteed by npm; in fact just the opposite, if there are conflicting dependency versions, then multiple copies of a module may be present. I'm actually the person who made it that way, back in 2010. I disagree that the "dual module hazard" is a hazard at all, or at least, a new hazard related to dual modules; it should be called just "the module hazard", since all node packages have always been subject to it.

Since JavaScript modules may not ever assume that they are the only instance loaded, by design, and thus the "dual module hazard" imposes no additional hazards that are not already present without shipping dual modules, and shipping dual modules means that they work as expected with both import and require, I'm going to keep doing it.

It'd be nice if TSC would provide a way to emit .mjs/.cjs from a .ts file, which would save me having to write package.json files into my dist folder.