developit / microbundle

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

Export maps and node support #795

Open ricokahler opened 3 years ago

ricokahler commented 3 years ago

Hello there 👋,

I recently switched to microbundle for a bunch of my libs and I love it so far, however, I ran into an issue where the modern bundle (via exports) doesn't seem to run inside node.js.

My (simplified) package.json looks like this:

{
  "name": "color2k",
  "sideEffects": false,
  "exports": "./dist/index.modern.js",
  "main": "./dist/index.js",
  "unpkg": "./dist/index.umd.js",
  "module": "./dist/index.module.js",
  "source": "./src/index.ts",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "microbundle"
  }
}

And after I run microbundle, my dist folder looks like this

index.d.ts
index.js
index.js.map
index.modern.js
index.modern.js.map
index.module.js
index.module.js.map
index.umd.js
index.umd.js.map

And after I publish with this config and run the following code with Node.js, I get the following error:

// my-file.js
const color2k = require('color2k');

// ...
SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:979:16)
    at Module._compile (internal/modules/cjs/loader.js:1027:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)

I suspect this is due to Node.js not knowing what to do with exports. For more context, I'm not using the mjs extension when running this code in node LTS v14.15.3.

Maybe this is more of a question rather than a bug but in general: How do I use exports with microbundle? How can I author a package that is both for Node.js and browsers?

Thanks for any support!

For now, I'll just remove the exports field.

ricokahler commented 3 years ago

For some more things I've tried:

I've tried using a conditional exports map where I pointed the import field to the .modern.js bundle, however, this doesn't seem to work correctly when using Node.js's native es module support (.mjs).

I believe node.js requires the .mjs extension in order to use import and export natively.

For that setup, it's:

{
  "exports": {
    "require": "./dist/index.js",
    "import": "./dist/index.modern.js" // <-- does not work
  }
}

However, if I rename the modern file to index.modern.mjs, it works

{
  "exports": {
    "require": "./dist/index.js",
    "import": "./dist/index.modern.mjs" // <-- works ✅
  }
}

I'm not really sure what's the right way to handle this but I feel like the docs need to be updated to cover Node.js cases.

Hope this helps, thanks for the great lib!

developit commented 3 years ago

Hiya @ricokahler! In order to support native ESM in Node, .mjs is required:

{
  "name": "color2k",
  "sideEffects": false,
  // CJS:
  "main": "./dist/index.js",
  "exports": {
    // Node CJS:
    "require": "./dist/index.js",
    // Node ESM:
    "default": "./dist/index.modern.mjs"
  },
  // Bundler ESM:
  "module": "./dist/index.module.js",
  // unpkg/cdn UMD:
  "unpkg": "./dist/index.umd.js",
  // only used by microbundle:
  "source": "./src/index.ts",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "microbundle"
  }
}

Agreed regarding the docs - if the readme examples aren't using .mjs to force Node's ESM behavior they should be fixed to do that for sure.

ricokahler commented 3 years ago

@developit is there is a way to get microbundle to output a file with the .mjs extension? Right now when I run microbundle with that config, it outputs dist/index.modern.js.

Should index.modern.js be index.modern.mjs by default?

aheissenberger commented 3 years ago

@developit is there is a way to get microbundle to output a file with the .mjs extension? Right now when I run microbundle with that config, it outputs dist/index.modern.js.

Should index.modern.js be index.modern.mjs by default?

I think this depends on the type of the package "type": "module" as I found out with my problem #801

developit commented 3 years ago

Microbundle should just use the extension you specify in your package.json. It doesn't right now, which is a bug.