A guide to building npm packages

July 12, 2018

Let’s say I have some code or a React component I want to share between apps. I code in ES6 all the time and, in case of a component, it might also have some JSX, so it needs to be transpiled.

The usual solution would be to have it as a package on npm.

But how does the extra build step work when publishing a package? What’s the appropriate build process for this?

Follow along and find out!

Step 0: The code

Well, if you want to publish to npm, first thing you need is some code to be published. We’re going to publish a package with a function pads a string to the left with a given character to a given length. Let’s call it… 🤔💡😊 …leftPad.

//left-pad.js
export default (str, len, char = ' ') => {
  let result = str

  while (result.length < len) {
    result = char + result
  }

  return result
}

This is a naive implementation that doesn’t handle corner cases. Please don’t use it in your production apps!

That’s an incredibly simple function and we still have plenty of things that need to be transpiled (assuming our target is ES5):

  1. The export statement
  2. The arrow function (=>)
  3. The default value assignment for the char argument
  4. let into var.

You can assume this is a file inside a larger project that needs to be extracted.

Step 1: Start the project

Let’s start a new project to get it going. I’m going to call the package left-padder.

$ mkdir left-padder && cd left-padder
$ yarn init -y
$ git init

That repository will be the new home of our left-padder package.

Step 2: Babel

I guess it’s no mistery we were going using Babel at some point in this process. In this step, we’re going to install it and set it up.

Let’s start by getting it installed. Since we’re on the command line, we’ll also add the babel-preset-env package.

$ yarn add --dev babel-cli babel-preset-env

Next we need to set up Babel to do some production-grade transpilation of our lib. Add the following "babel": entry the package.json file:

{
  "name": "left-padder",
  "version": "1.0.0",
  "license": "MIT",
  "babel": {
    "presets": ["env"],
    "env": {
      "production": {
        "compact": true
      }
    }
  },
  "dependencies": {},
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0"
  }
}

That will use the magical env preset to transpile our code to ES5. env’s capabilities are heavily underused in this case. It allows us to define targets such as browser versions, node versions, or things like browsers used by 99% of the U.S. population, among others. You should check it out sometime (not now).

In our case, by not specifying any targets, we’ll just transpile everything down to ES5.

Step 3: Moving the code

This is the folder structure we’re aiming for:

Project Folder Structure

Since this lib is exceedingly simple, all the code will be inside src/index.js. If you’ve been following along, you can paste the code from Step 0 inside this file.

Your lib could have more code and therefore a more complicaded folder structure. As long as your src/index.js file exports all functions and/or classes that should be exposed, everything should be fine.

Step 4: Building the library

At this point we’re ready to build (transpile) the library. Here’s a command that will do just that:

BABEL_ENV=production node_modules/babel-cli/bin/babel.js -d lib/ src/

This isn’t something that we want to be typing over and over again, so let’s define a script inside package.json to shorten that up a bit:

"scripts": {
    "clean": "rm -rf lib",
    "build": "BABEL_ENV=production yarn clean && node_modules/babel-cli/bin/babel.js -d lib/ src/"
}

This script cleans up the previous build with yarn clean and rebuilds the project, outputting the production files to /lib.

We’re ready for the next step, which is publishing to npm.

Step 5: Publishing to npm

Before publishing our package to npm we need to set the main entry in package.json to let consumers of our library know what is the entry point of the code. This means when someone does import leftPad from 'left-padder', the object exported by the main module will be returned:

// package.json
{
  "main": "lib/index.js"
}

We’re now ready to publish! I won’t go into details on how to setup your npm account, you can read all about it here.

$ npm publish

Summary

We’ve seen how to simple it is to use Babel to build packages that will be shared across projects. We’re now able to write and maintain our packages using ES6+, AND we can also apply this same technique to share React components.