Node.JS (New) Package.json Exports Field
Heard about export map ? It’s quite powerful !
TL;DR
The documentation for the exports
package.json field is here, it comes from this proposal. I won’t cover everything, so check out the proposal if you need any additional information.
Plus I did not cover the mirror imports
field that is very interesting in the ESModule world.
“Export” what ?
The exports
field (or “export map”) provides a way to expose your package modules for different environments and JavaScript flavors WHILE restricting access to its internal parts.
- Environments like NodeJS, browser, of even node-env-like filters
production/development
, for instance React could (or does ?) use it for its development build with warnings & stuff, and its production build. - Flavors like EcmaScript Module
import … from '…'
vs. CommonJSconst … = require(…)
How to use it
Consider the following architecture:
my-awesome-lib
├── lib/
│ ├── whole-lib.browser.js (iife format)
│ ├── public-module-a.cjs (commonjs format)
│ ├── public-module-a.mjs (esmodule format)
│ ├── public-module-b.cjs
│ ├── public-module-b.mjs
│ └── internals/
│ ├── private-module-c.cjs
│ └── private-module-c.mjs
├── package.json
└── …
We want to export module-a
and module-b
while restricting access to module-c
. We also want our package to provide CommonJS and ESModule outputs.
Here’s an example of how you could use that the benefits of the exports
field:
Note: every path should be relative to the package root. Meaning each path must start with
./
By providing the following information about our package my-awesome-lib
, we can now use it anywhere (it is supported) like this:
In both flavors, notice the path rewrite: there’s no /lib/
in them. The path correspond to what we declared in the exports
map.
That’s it !
We’re done here. I didn’t cover how to use production/development exports, but you get the idea, you can experiment it on your own 😉
The story of how I met … exports map
Lately, while building a small NPM library, I checked how it looked on skypack (ex pika), just out of curiosity. And I found this:
My eye was caught by Export Map
. So I figured “Cool, I’ll just check that out, see how it would help me build a next-gen package”, I ended following the “learn more” link.
At the end of the road, you’re redirected to the NodeJS documentation, but the doc itself is − paradoxically − too big and too shallow (in my opinion).
A more detailed section of it − which I actually found out while writing this post − is the “Writing dual packages while avoiding or minimizing hazards”. And I gave it a shot. I changed a bit the build process of my lib, added the export map, and published it. Fine.
Some hours later, I check if I can still install my lib… and everything is broken 😱 ! I can’t even import/require it !
(Fortunately, it was a brand-new library, with very few downloads, and no one filed me an issue, so the lib was probably still usable in their use case).
So I’m like “Crap I doomed my build process again, what did I do wrong this time ?”. I started playing where’s Waldo 🧐 … and I finally found what the issue was: the exports
map. Which I didn’t expect at all. I was expecting to discover one of my changes to the build process, and absolutely not.
At this point I knew where the issue came from, but not how to fix it. “Not so fast” right ? Obviously, everywhere exports
field was mentioned, it was mentioned only as The package.json new "exports" field
, but every time I duckduckgo⋅ed it, there’s no obvious result showing how it works. And that’s because there are only extremely common words (for a dev) in that search: npm
, package.json
, field
and exports
. I had to go back to the proposal to understand the usage of it… Which I did !
And now you can scroll back to the top 😁.