developit / microbundle

📦 Zero-configuration bundler for tiny modules.
https://npm.im/microbundle
MIT License
8.03k stars 362 forks source link

.mjs extensions in microbundle v0.15 break dependencies with multiple exports #978

Closed donmccurdy closed 2 years ago

donmccurdy commented 2 years ago

After updating a package from microbundle v0.14 to v0.15, I ended up with the following change in my package.json, to account for #950 —

"main": "dist/my-package.js",
-"module": "dist/my-package.modern.js",
+"module": "dist/my-package.modern.mjs",

Since then I've gotten reports from downstream users that my package no longer works in their Webpack builds, because my package also has a dependency on gl-matrix, which uses multiple entrypoints like gl-matrix/mat4 and gl-matrix/vec3 extensively:

https://github.com/toji/gl-matrix/blob/8962b2e7727594022e59e48e605049c69403da60/package.json#L6-L20

This manifests in end-user projects (which depend directly on my package, and transitively on gl-matrix) with the following error:

"The request 'gl-matrix/mat4' failed to resolve only because it was resolved as fully specified (probably because the origin is a strict EcmaScript Module, e.g. ... a '*.mjs' file..."

Is there a workaround here that doesn't require the end-user to reconfigure Webpack when consuming my package? A way to opt-out of the .mjs renaming might be a solution for me, as using .js extensions hadn't been an issue before.

rschristian commented 2 years ago

Webpack (unfortunately) treats .js and .mjs ESM differently.

A way to opt-out of the .mjs renaming might be a solution for me

This is done with "type": "module". Webpack treats .mjs the same as "type": "module".

I would recommend against pointing "module" at the modern output, and certainly would recommend against it using .mjs. Use the esm output with .js instead.

donmccurdy commented 2 years ago

This is done with "type": "module".

This results in microbundle renaming my main entry to .cjs, and unfortunately seems to break the test suite, for weird reasons, on another package I have consuming the original package. That is not microbundle's fault, the test suite (tape) does not do so well with ESM, and I may need to replace it anyway, but this is not an easy switch at the moment.

I hadn't realized microbundle was writing modern output to .mjs files and transpiled output to .js files in this update — why that distinction?

rschristian commented 2 years ago

This results in microbundle renaming my main entry to .cjs

Well, that's just how dual output works w/ Node semantics. .js & .mjs or .cjs & .js. As Node prioritizes "exports" over "main", I suppose you might be able to rename your main entry to .js, though I wouldn't really recommend that.

Altering package exports is (usually) a task for a semver major -- reverting & waiting might be a better option.

I hadn't realized microbundle was writing modern output to .mjs files and transpiled output to .js files in this update — why that distinction?

Not sure I follow. What distinction? The linked PR just brings Node extension semantics to Microbundle's esm and modern outputs.

donmccurdy commented 2 years ago

Not sure I follow. What distinction?

Sorry, to clarify — I thought that esm and modern were both ES Modules, just with the latter using modern EcmaScript features. If both are modules, why is only one written with an .mjs extension?

I would recommend against pointing "module" at the modern output, and certainly would recommend against it using .mjs. Use the esm output with .js instead.

I can't support as many old browsers and node.js versions as the microbundle defaults include, it is not so simple as just transpiling. But using browserslist to force ES2017+ on the esm output might work here? I'll try that. Thank you!

rschristian commented 2 years ago

Sorry, to clarify — I thought that esm and modern were both ES Modules, just with the latter using modern EcmaScript features. If both are modules, why is only one written with an .mjs extension?

That's correct, however, Node extension semantics only applies to modules you wish to load in Node.

Node doesn't consume "module". That was a community driven spec to allow bundlers to make use of ESM long before Node supported it well. This means that the file that "module" points to doesn't necessarily need to play by Node's rules.

I can't support as many old bundlers and node.js versions as the microbundle defaults include, it is not so simple as just transpiling. But using browserslist to force ES2017+ on the esm output might work here? I'll try that. Thank you!

If you're just targeting ES2017, feel free to keep using modern.

Keep in mind that Microbundle outputs according to what's valid in Node (and therefore the spec going forward). If you don't care about Node, or know your way around these configs, feel free to do some post-build renaming of the output. That's totally valid.

It's impossible to support everything, so Microbundle tries to ensure it's not outputting modules that will crash Node if you try to load them. That seemed like the best default.

Edit: Looking at the attached report, honestly it looks like you should just ditch "exports" and do the following:

{
    "main": "./dist/foo.js",
    "module": "./dist/foo.esm.js"
}
rschristian commented 2 years ago

Going to close this out, as there's nothing really actionable here for the project. We made this adjustment for correctness, and while it's unfortunate that some tooling has quirks regarding correct usage, the previous output was invalid in Node.

Certainly feel free to respond and I can try to help though.

donmccurdy commented 2 years ago

Things seem to be working now, so for posterity here's where I ended up:

  1. Adding browserslist to make sure all my builds use ES2017+
  2. Switching to what the installation guide suggests

I should've tried that first when I hit the v0.14 → v0.15 upgrade issues. As described in https://github.com/developit/microbundle/issues/977 I'd hoped to switch several packages to ESM-only, but there are enough pain points in other tools with that (😭) that I think just need to experiment with some of the newer and less stable packages before going further on ESM.

No more questions here, thanks again @rschristian!

rschristian commented 2 years ago

Glad to hear it! The working solution, that is.

Hopefully in a couple more years this will get a lot simpler, though, I think some of us have had our fingers crossed for a few years now....