microbundle icon indicating copy to clipboard operation
microbundle copied to clipboard

Export maps and node support

Open ricokahler opened this issue 4 years ago • 5 comments

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?

  • Do I need to write a conditional exports map?
  • Do I need to post-fix the modern bundle with the .mjs extension? If so how do I do that with microbundle?

Thanks for any support!

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

ricokahler avatar Feb 01 '21 01:02 ricokahler

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!

ricokahler avatar Feb 01 '21 01:02 ricokahler

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.

developit avatar Feb 02 '21 20:02 developit

@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?

ricokahler avatar Feb 02 '21 20:02 ricokahler

@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

aheissenberger avatar Feb 04 '21 17:02 aheissenberger

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

developit avatar Mar 17 '21 02:03 developit