Build a JavaScript Util Library for NPM with ES6 Modules and CommonJS

Distribute Your JavaScript Utils Across Multiple Projects

Michael Paravano
6 min readOct 21, 2020
Photo by NeONBRAND on Unsplash

There’s no shortage of awesome JavaScript util libraries out there. They’re incredibly helpful but, if you’re like me, you often wish for certain functions or features that don’t exist.

We could definitely write our own and include them in our project, but what happens when we work in multiple repositories or other teams want to make use of our cool world-saving utils?

We need our own JavaScript utility library that we can distribute via NPM.

TL;DR

Here’s an example GitHub repo with my personal util functions after which you can pattern your architecture:

Goals

  1. Have one code source that transpiles into both ES6 and CommonJS (ES5)
  2. Tree-shakable for ES6
  3. Implement unit testing with Jest
  4. Auto-generate the README.md for documentation

Let’s Get Started!

First things first.

Boring Disclaimer

I’ve chosen to use TypeScript for this project, but you don’t have to. It’s perfectly fine if you want to use regular ES6 with Babel. Everything will still pertain to you with the exception of the The tsconfig(s) section.

And now that we’ve gotten that out of the way…

The File Structure

Here is a diagram of the core file structure that we want to setup:

/dist
└───/lib
└───/es5
└───/es6
└───CHANGELOG.md
└───LICENSE
└───README.md
└───package.json
/src
package.json
tsconfig.es5.json
tsconfig.json

The dist directory will contain everything that we wish to publish as part of our NPM package. We’ll generate everything in lib as well as the README.md.

The tsconfig(s)

As noted in the disclaimer, if you’re not planning to use TypeScript and instead will use ES6 with Babel, you can skip this step and replace it with your own Babel setup.

We are going to have two tsconfig.json files at the root of our project. One will be for ES6 and the other CommonJS:

The bin Directory

The bin is where we hold our files for generating the README.md. I’m not going to go over the code of these files because the implementation is outside the scope of this article, but I will explain at a high-level how they are used.

The generateReadme.js file will:

  1. Crawl through our src directory to find all of our utils
  2. Parse the JSDoc info in each to determine what to output for the README.md.
  3. Use the readme.ejs file as the template for the markdown for the final README.md
  4. Place the main README.md in the dist directory and the root.
  5. Create a README.md for each util in the src/[util] directory.

Note that you may want to change the behavior of this code for item #5 for your project where you have all of the documentation included in one single README.md file.

I have my utils repo setup the way it is because it’s easier for me and others to find what they’re looking for and isolate what they’re looking at.

If you want to distribute one large README.md file, there’s nothing wrong with doing that and you would have to make only a few minor tweaks to the code in the generateReadme.js and readme.ejs files.

The package.json(s)

We are going to have two package.json files: one at the root and one in the dist directory.

package.json (root):

Notice the compile script makes use of the two tsconfig files and executes the readme script at the end.

package.json (dist):

The points of interest here are:

  1. Change name to what you want your npm package to be.
  2. Change the repository, bugs, and homepage urls to match your repository.
  3. Change author to your name.
  4. Increment the version as you publish.
  5. "sideEffects": false, is critical for tree shaking to occur. It tells Webpack (and the like) that your individual modules are self-contained without any external side effects.
  6. main is the entry point for our CommonJS code.
  7. module is the entry point for our ES6 code. If the code that is utilizing the library understands module, it will use it. Otherwise, it will use our CommonJS code in main.

How does it know to use ES6 or CommonJS?

When the app consuming our library is transpiled to CommonJS (ES5), it won’t recognize module in our package.json as a property it knows how to use so it will ignore it and enter through main. When the app is run as ES6, it will recognize module, use that and ignore main.

Let’s Create Some Utils!

We should organize our utils by category to help with maintainability as our library scales in the future. I find that this structure works really well:

/src
└───/myCategory
└───/myUtil
└───/index.ts
└───/index.test.js
└───/index.ts

You’ll see how this plays out as we create our first util.

Our First Util

Let’s create a simple value util that checks to see if a value is null or undefined.

We’ll create the following tree:

/src
└───/value
└───/isNil
└───/index.ts
└───/index.test.js
└───/index.ts

The two key things to note are:

  1. Export your function as the name of the util, in this case isNil
  2. Structure the JSDoc like the above with a description, since, category, the parameters, returns, and an example(s).

Including and structuring the JSDoc properly will become important to autogenerate the README.md so don’t skip this step!

The Unit Test

We need to make sure we test our functions before we send them out. Here’s a sample test for isNil as the index.test.js file:

Define the Exports

The next thing we need to do is define the exports to make it easy for our consumers to access our utils. To do that, we’ll create an index.ts file at the root of our source and include the following:

export { isNil } from './value/isNil';

Follow this same pattern in the same file to add any other utils you might have in the future. For example:

Tree Shaking

It’s very important that we use the export syntax outlined above in order to get the tree shaking benefits in ES6. If we were to do this or any variation of this instead, we would lose all tree shaking benefits:

// Bad! Do NOT do anything like this!
import { isNil } from './value/isNil';
export { isNil };

Once something is imported, Webpack will consider it necessary so nothing will be shaken out, even if our consumer uses only one function in our list.

Compile It

Let’s bring our new library to life! From the root of the project, run one of these commands:

npm run compile

Check the README.md

You’ll recall that we included our script for generating the README.md as part of the compile script in our root package.json file. If our compile has run without error, we should see a brand new autogenerated README.md file in our dist directory.

Test It

From the root of the project, run one of these commands:

npm test

Or if you want code coverage stats:

npm run test:coverage

If you’ve never done testing with Jest before, you should see your tests listed out in the terminal and, hopefully, a lot of green PASS tags:

Publish It

Navigate to the dist directory to execute our publish command.

npm publish

For all of the ins and outs of publishing to NPM, you’ll want to check out the NPM docs page for publishing.

Consume It

Once published, we should be able to go to any of our other projects to install it:

npm i @username/utils

Since all of our functions are named exports, you can import them like this:

import { isNil, myOtherUtil } from '@username/utils'

Profit!

We can now distribute our utils package across multiple projects and multiple teams, we can be confident that it will work in an ES6 or CommonJS (ES5) application, we’ve tested each function, and we’ve generated our documentation for each function.

Go forth and make the JavaScript world more convenient for you and your teams!

--

--

Michael Paravano

Senior Front-end Engineer specializing in all things front-end software development.