20

I have created some npm modules and compile them to:

  • commonJS (using exports.default =) and
  • esm (using export default)

I set up my package.json like so:

main: "index.cjs.js",
module: "index.esm.js"

When I npm install the package and I simple import it like:

import myPackage from 'my-package'

It will automatically choose the main file, not the module.

My question:

Is there a way to import the module file instead when doing import myPackage from 'my-package' in a JavaScript file?

Why I choose the commonJS file for "main":

I noticed that using Node, importing an esm file is not possible because of export default, it has to be commonJS. I have some simple helper JS functions like this and this, and I would want them to be usable to the widest audience. That's why I chose cjs for the "main" path in package.json.

Why I define a separate "module" in package.json:

Lots of famous libraries like Vue.js are already doing this. See further information on this Stackoverflow thread:

What is the "module" package.json field for?

Why do I want to be able to import the "module" file instead of the "main" file:

There is currently a bug in Rollup where it will not properly show JSDoc comments when coding after having imported a cjs file, but it does work when importing a es file.

The workaround I want to avoid:

Just set "main" to the esm file in package.json, right? But then all users who are using my packages in Node apps will not be able to use it anymore...

→ I'm really confused about all this as well, but I think I did enough research to make sense of all it. That being said, if anyone knows a better approach or any other advice, please do tell me in the comments down below!!

mesqueeb
  • 5,277
  • 5
  • 44
  • 77
  • 1
    I don't think 'module` is a valid key for package.json. You might want to work around using something explained here: https://stackoverflow.com/a/16631079/1971378. I have elaborated that in an answer. Hope that helps. – trk Sep 26 '18 at 05:17
  • ... on second thoughts is there a reason why you are not pointing `main` directly to `index.es.js` ? – trk Sep 26 '18 at 05:20
  • 1
    @82Tuskers Thanks for the link, but your answer is a bit "off". First, [module](https://stackoverflow.com/questions/42708484/what-is-the-module-package-json-field-for) in package.json is widely used by many famous libraries & frameworks. Second, your link points to an explanation on how to export different functions... I'm talking about the same function compiled as both CommonJS and ES5. Finally, my reason for directing `main` to the commonJS one was to make sure it works locally with `node` as well. If I'd point to the `index.es.js` file - which uses `export default` - node will not run it. – mesqueeb Sep 26 '18 at 05:22
  • What is the goal? If you're using this module in Node, then you should configure the `main` to point at the "module" script. If you're using this in a browser then it is up to you to choose how it is delivered to the client, not the `package.json`. – Jake Holzinger Sep 26 '18 at 05:24
  • @JakeHolzinger Oh? In my experience node can't import files compiled to es5 (which use `export default`) but only commonJS compiled files. That's why I set `main: "index.cjs.js"`. > " If you're using this in a browser then it is up to you to choose how it is delivered to the client, not the package.json" I'm not sure what you mean by this. If I write a small JS helper function that people can `npm i` and `import x from 'my-pkg'` then the JavaScript will automatically choose the file set at `"main":`, I don't think you can choose another inpoint. That is exactly what my question is! : D – mesqueeb Sep 26 '18 at 05:28
  • But why is the question? There is only one entry point to a module. In what context are you trying to use the "module"? It sounds like it's not Node, so I'm assuming the browser. If you're using it in the browser, it has to be loaded somehow and that is not controlled by the `package.json`. – Jake Holzinger Sep 26 '18 at 05:34
  • @JakeHolzinger Good question. Thank you!! So there's [a bug](https://github.com/rollup/rollup/issues/2478) with my commonJS builds where it's not possible to get any JSDoc hints when importing my helpers from npm into other projects of mine. This makes working with my JS helper packages (which I uploaded to NPM) much harder when coding. Until rollup fixes this bug I was wondering how to use the `"module": 'index.es.js'` file instead when doing `npm i` and then `import x from 'my-pkg'`. I hope you understand my reasoning. – mesqueeb Sep 26 '18 at 05:39
  • @JakeHolzinger Another workaround is just set the `index.es.js` file to `"main"`, but I'm afraid that people who want to use my packages in node, won't be able to use it anymore, since it will use `export default` instead of `exports.default = ` in this case. And node cannot import an `export default` kind of package... – mesqueeb Sep 26 '18 at 05:41
  • This is good information, it might help if you added this context to the question above. I'm not very familiar with JSDoc and how the hints work. It might be worth taking a peak at how some of those famous libraries like `jquery` or `lodash` handle their builds/configuration to see if you can adopt their approach. – Jake Holzinger Sep 26 '18 at 05:48
  • @JakeHolzinger Thank you for your advice and kind words. I will update the question and hope someone will come along who can help me. : ) PS: How would you compile/export code to prepare for NPM for simple JS helpers like [these](https://github.com/mesqueeb/find-and-replace-anything)? I'm having a difficult time finding a good guide on _"which compilation/export methods to choose for NPM packages and why"_ – mesqueeb Sep 26 '18 at 05:51
  • I mostly work with `Typscript` currently, so I can't give much advice outside of that, but generally if you're looking to support multiple platforms you build a version in CommonJS for NodeJS and a "browserified" version the browser. I don't think there is a "best practice" on how to do it, [lodash](https://github.com/lodash/lodash#lodash), for instance, requires the browser version to be built manually, while others ship it with the NPM module. – Jake Holzinger Sep 26 '18 at 06:03

2 Answers2

6

Just don't use extension for main file and have es6 and CommonJS version as two separate files with the same name and in the same directory, but with different extension, so:

index.js // transpiled CommonJS code for old nodejs
index.mjs // es6 module syntax

and in package.json:

{
  "main": "index"
}

If node is launched with --experimental-modules flag, it would use *.mjs file, otherwise *.js.

Remek Ambroziak
  • 790
  • 8
  • 11
  • 2
    Author suggested they wanted their helper functions "usable to the widest audience" and `.mjs` is [not](https://tools.ietf.org/html/rfc4329#section-8.2) [associated](https://www.iana.org/assignments/media-types/application/javascript) with any scripting media types as far as I can tell. – vhs Jan 13 '19 at 16:56
  • Does anyone know why using `require()` in a browser based app (such as React) does not select the module option over main? – jlewkovich Feb 10 '19 at 02:21
  • You can use `require()` in a browser based app b/c it's being bundled with something like webpack. Your bundler config will determine what type of module is used. – Jack Steam Mar 17 '21 at 14:06
2

Nodejs does not support "module" but does support the newer "exports" spec.

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

https://github.com/nodejs/node/blob/v16.14.0/lib/internal/modules/esm/resolve.js#L910

  "exports": {
    "import": "./main-module.js",
    "require": "./main-require.cjs"
  },
Jamie Pate
  • 1,783
  • 20
  • 18