1. Do you want to write it in TypeScript?
If so, you’ll need to compile it before publishing. Bookmark the the tsconfig.json reference.
2. Do you want to compile using tsc, swc, or esbuild?
3. Do you want it to run in Node?
Now you need to decide your module format.
4. Do you want to output your package as ESM or CJS?
CommonJS modules (CJS) was the original way to include code from another file in Node. ECMAScript modules (ESM) is now the standard way to include JS code and Node just recently implemented support for it. The community wants us to move to pure ESM, but many things are CJS and they don’t exactly interop seamlessly.
5. Are any of your dependencies pure ESM?
Using them in your CJS project is not going to work out of the box.
6. Are any of your dependents CJS?
They won’t be able to use your pure ESM library out of the box.
7. Since Node can run ESM and CJS, do you want to support both?
Then you need to avoid the Dual Package Hazard when both versions of your library might end up loaded at the same time.
8. Do you want it to run in browsers?
9. Will you bundle all your code into a single JS file?
This will make it download faster. You probably want it minified too.
10. Will you inline your dependencies in the bundle?
This will make it download slower, but it will be more portable.
11. Will you use a CDN to get it into the browser?
You’ll need to know about services like jsDelivr or Skypack which automatically serve packages in the npm registry. Some services let you leave the npm dependencies out of the bundle while their CDNs serve them for you. Otherwise your users will need to download the bundle like the old days or npm install it and link to it in node_modules from their HTML page. ES modules must have an explicit path to a real file, no index.js or package.json magic. Or you can use import maps.
12. Do you want to bundle it for node?
Esbuild talks about doing this in their docs. It can be faster to read from the disc.
13. Where will you specify your entry point in package.json?
What. A. Nightmare.
I will write TypeScript.
I will use nx to generate the project because their developers have many sane decisions for me. Thank you!
I will build a sole CJS version for Node since I plan to consume this package in an Electron app and Electron doesn’t yet support ESM. This was a hard decision because respected community members are pushing valiantly for pure ESM packages. I agree philosophically 100%. If I wasn’t the one consuming this package, I’d go pure ESM. However, I don’t feel like hacking around Electron to get my own code to run, so I’m compromising on my values for today.
I will use tsc to build the CJS version to avoid another tool. If my project were huge, I’d use to swc to build and tsc to lint.
I will leave the CJS version unbundled because…what happens in node_modules, stays in node_modules.
I will use the “exports” field in package.json to point to the Node entry point. It has some advantages over “main” and will make it easier to switch to pure ESM in the future.
I will use esbuild to bundle an ESM version for the web.
I will inline my dependencies because I don’t have many and they are not big. If they were big, I’d consider using a CDN like Skypack.
I will use the “browser” field in package.json to point to the bundle.
Hope For Brighter Days
My introduction to programming came from Ruby on Rails. It championed the principle “convention over configuration” when the back-end landscape was overgrown with XML config files for Java VMs.
Today we are in having a “configuration-heavy” moment in the front-end world, but I have hope that good conventions will emerge. Then we can get back to doing the real work.