Publishing a simple package to npm

by
, updated (originally posted )

This guide expects you to have used Node and npm before, but you don’t have to be an expert! You should also know how to publish an open source repository on GitHub and a bit about testing.

It’s time. You’ve been mooching off of npm for awhile now, and you want to make an open source package. Let me be your guide.

Go check out the finished source if you’d like to see all of this together. Unfortunately, you won’t be able to publish a package called “startinterval” because I’ve already done it. It might be best to follow along but to avoid polluting npm with example packages.

What we’ll build

JavaScript has had setInterval for a long time. It runs a function every so often. Frequently, I find myself doing something like this:

setInterval(myFunction, 1000);
myFunction();

I’d like to build a nearly-identical function that does the same thing as setInterval but also runs the function immediately. It’ll turn the above code into this:

startInterval(myFunction, 1000);

This is a pretty simple package, so it’ll be helpful when we learn all of the complexities of a npm module.

A first version

There’s a lot of stuff you can do for an npm package. Let’s start with a respectable first version.

package dot json

Every npm module has a file called package.json inside. It’s a simple JSON document that’s got a lot of options.

(Oh hey: make sure you’ve made a new directory when you’re doing this stuff.)

You can make this file yourself, or let npm init do it for you. Personally, I think npm init adds a lot of stuff, so let’s just put this inside for now (I’ll explain in a minute):

{
  "name": "startinterval",
  "description": "setInterval but also calls the function immediately",
  "author": "Your Name <yourname@example.com> (https://example.com)",
  "version": "0.1.0",
  "main": "index.js"
}

So there are four keys:

That’s a really basic package.json. Let’s write the code.

The code

The first version of our code is just 5 lines.

function startInterval(fn) {
  fn(); // do the function right now
  return setInterval.apply(this, arguments); // defer to setInterval
}

module.exports = startInterval; // let me be required

Drop this into index.js, like we specified in package.json.

An aside: there’s nothing too special about index.js—you could’ve called it squigglebutt.js, and as long as you changed it in package.json, you’re good. (index.js is a tiny bit special, though – if you require('/some/folder/'), it’ll be as if you did require('/some/folder/index.js').)

Note that this package will use CommonJS exports and not ECMAScript Modules. If you don’t know what that means, all good. If you do know what that means, know that I’m using CommonJS for simplicity. Future versions of this guide might change!

Associate an npm account

If you’ve already done these two steps, you don’t need to do them again:

  1. Sign up for an npm account if you haven’t already
  2. Run npm adduser and log in on your computer

Feel the thrill.

Publish it!

We’re ready to publish a first version! My body quivers with excitement.

npm publish . # where . is just the path to this package

If all goes well, npm will spit out some information…and then, without much fanfare, your package will appear on the npm website! It might ask you for two-factor authentication credentials, too. I’ve experienced a delay of a few minutes, so you can frantically refresh the homepage until your package appears (or until npm crashes).

And you’ve made a first version! Try making a new project, installing it, and using it with require('startinterval')…and be amazed.

It’s not finished, though. Read on to make a more “mature” npm package.

The little things in life

There are a lot of little things we can do right now to make our project really special.

Add a readme

npm will give you (and anyone who installs your package) a warning if you don’t have a file in your root directory called README.md. It’s a Markdown-formatted readme. Go ahead and make one to describe your package!

Add a license

You’ll want to license your code so that people can legally use it (unless your intended users are the hardest of criminals). If you need help choosing a license, I’d recommend GitHub’s ChooseALicense dot com (although I should add the disclaimer that I’m not a lawyer and nothing I say is legal advice).

npm packages seem to like the MIT License but you can choose whatever you want.

Two things you’ll want to do:

  1. Add the license to a file called LICENSE in the root directory.

  2. Add the license to package.json:

    // ...
    "license": "MIT"
    // ...
    

Put the source code somewhere

Nearly every npm package hosts its source somewhere. Microsoft’s GitHub is (unfortunately) the most popular choice, but GitLab and Bitbucket are two other popular ones.

It’s a good practice to publish your package as an open source project. It’ll help other people see the source code of your package, make contributions if they wish to, and ask questions.

I’m going to assume you know how to do this, so here’s some npm-specific stuff:

Testing with Mocha and Chai and Sinon

I’m not going to preach the benefits of testing your code. There are already enough people in the world making you feel bad. But let’s test it.

Just like a lot of things in JavaScript, there are a million different libraries that do what we want. There’s Jest and Ava for running tests, or Assert and Should for doing assertions. Know that there are other options out there, and if you don’t like the options I outline below, there’s a lot of good shopping to do.

Today, we’re going to use three popular testing libraries: Mocha, Chai, and Sinon. They’re a bit old-school, but they work well.

What are these things?

Testing in a nutshell: basically, your code will have a bunch of functions and classes and whatnot. You want to make sure that, no matter what you throw at them, they’ll perform how you want them to. For example, this function should always return a string that says "hello" in it. Testing ensures that everything goes down exactly how you planned.

Mocha is a testing framework. At the end of the day, it’s the thing that actually runs your tests. It’s the syntax you use to say “here’s what I’m testing, let me set it up, blah blah, blah”.

Chai is an assertion library. While Mocha lays out the test, Chai (almost literally) says “I expect this function to return ‘hello’”. The actual syntax is expect(thisFunction()).to.equal('hello'), which reads a lot like English.

Sinon does “spies”. This basically lets us define functions that know how many times they’ve been called (which, for this library, is very useful). It also lets us make fake clocks and fast-forward time. We’ll get to that in a bit.

Getting set up

The first thing to do is to install Mocha, Chai, and Sinon.

npm install mocha chai sinon --save-dev

This installs the packages and saves them under the “devDependencies” field of package.json. “Regular” dependencies are things that you need to run the package, and they’re specified under the “dependencies” key. “devDependencies” are needed when developing the package. Testing usually falls under the latter category, because end users won’t need to run your tests.

Now that we’ve installed Mocha, let’s add a script to our package. Add this to package.json (I’ll explain in a moment):

// ...
"scripts": {
  "test": "mocha"
}
// ...

Basically, this allows you to type npm test in your terminal and run the tests! (If you’re curious how this works, npm temporarily injects all the bins from node_modules into your PATH. So even if you don’t have Mocha globally installed on your system, this will still work.)

Now, make a folder called test and put test.js inside. We’ll make a simple example test for now. Fill it with this code (which the in-line comments hopefully explain):

// First, we require `expect` from Chai.
const { expect } = require("chai");

// `describe` makes a "suite" of tests; think of them as a group.
describe("fake suite of tests", () => {
  // The tests have an English description...
  it("has 2 equal to be greater than 0", () => {
    // ...and a code assertion.
    expect(2).to.be.above(0);
  });

  // You can have multiple tests in a suite.
  it("has 1 equal to 1", () => {
    expect(1).to.equal(1);
  });
});

When you go into your project’s root directory and type npm test, you should see some green text that says “2 passing”! This is because you have two tests and they both pass.

I won’t show it here, but I’d recommend making a failing test (expect(1).to.equal(2) or something) to see what that looks like.

Writing the real tests

Our project doesn’t need to test whether numbers are equal to each other. That’s silliness. What we do need to test is our function, startInterval! It’s been awhile since we talked about that.

Let’s write a first real test!

// Require everything we need (including our function!)
const { expect } = require("chai");
const sinon = require("sinon");
const startInterval = require("..");

describe("startInterval", function () {
  it("calls the function immediately", function () {
    const fn = sinon.spy(); // make a spy function
    const interval = startInterval(fn, 1000);
    expect(fn.calledOnce).to.be.true; // we can call this on the spy
    clearInterval(interval); // make sure we "clean up" the test
  });
});

Run npm test and see what happens! Our function works…well, only the stuff we’ve tested! Let’s add a second test:

it("calls the function many times over time", function () {
  // Set up the things we need. Most notably, use Sinon's "fake clock".
  const fn = sinon.spy();
  const clock = sinon.useFakeTimers();
  const interval = startInterval(fn, 100);

  // Should've been called once in the first 99 ms...
  clock.tick(99);
  expect(fn.callCount).to.equal(1);

  // But then we get "pushed over" into having called it again.
  clock.tick(2);
  expect(fn.callCount).to.equal(2);

  // Test that a few more times.
  clock.tick(100);
  expect(fn.callCount).to.equal(3);
  clock.tick(100);
  expect(fn.callCount).to.equal(4);

  // Teardown
  clock.restore();
  clearInterval(interval);
});

Try running npm test to see that things work!

Cleaning up the tests

This is a pretty simple function with only two tests, but you can see that we have repeated code. fn is identical each time, and we’re calling clearInterval at the end of every test. Let’s use Mocha’s beforeEach and afterEach features to clean that up. Here’s what the code looks like now:

describe("startInterval", function () {
  // Before each spec, make the fake spy and clock.
  beforeEach(function () {
    this.fn = sinon.spy();
    this.clock = sinon.useFakeTimers();
  });

  // After each spec, cancel the interval we start and restore the clock.
  afterEach(function () {
    clearInterval(this.interval);
    this.clock.restore();
  });

  it("calls the function immediately", function () {
    // Notice how our code is much shorter!
    this.interval = startInterval(this.fn, 1000);
    expect(this.fn.calledOnce).to.be.true;
  });

  it("calls the function many times over time", function () {
    this.interval = startInterval(this.fn, 100);
    this.clock.tick(99);
    expect(this.fn.callCount).to.equal(1);
    this.clock.tick(2);
    expect(this.fn.callCount).to.equal(2);
    this.clock.tick(100);
    expect(this.fn.callCount).to.equal(3);
    this.clock.tick(100);
    expect(this.fn.callCount).to.equal(4);
  });
});

It’s much shorter and clearer now! Well done.

All done!

We’ve seen that a even simple function can become a big npm package pretty quickly! Let’s do a final update of your package and push it to npm. If you’re all ready to “lock in” the API for version 1, update the version in package.json:

// ...
"version": "1.0.0",
// ...

And publish again!

npm publish .

Welcome to npm, my friend.