loading...
Cover image for An Open Source Maintainer's Guide to Publishing npm Packages

An Open Source Maintainer's Guide to Publishing npm Packages

kadikraman profile image Kadi Kraman ・8 min read

The JavaScript community is built on Open Source and being able to give back to the community feels most gratifying. However, publishing a package for the first time might feel rather daunting, and publishing a release candidate may be a bit scary even for experienced authors. I hope to give some insight and helpful tips, especially for new authors.

I have owned two open source libraries: a tiny utility library for DraftJS called draftjs-md-converter and a React Native security library called react-native-app-auth. I am certainly not as heavily involved in Open Source as some, but I have had the sole responsibility of publishing new versions of these libraries for several years so I think I have enough experience to justify this blog post! I remember how scary it was to publish a library for the first time, and I still remember how apprehensive I felt publishing a release candidate. The purpose of this writeup is to produce a guide on how to publish packages: both a comprehensive guide for new authors, and a sanity check for future me.

I will cover the following:

  • Setting up your repo for publishing
  • Publishing a package (initial release)
  • Versioning
  • Publishing a package (subsequent releases)
  • Publishing alpha / beta / release candidate versions

You can use npm or yarn, it's completely up to you. The publish commands are identical. I will be using npm throughout.

Setting up your repo for publishing

Before you're ready to run the publish command, you should check that your package is in a good state to be published. Here are a few things you might want to consider:

Check the package name

The name field in your package.json will be the name of your published package. So for instance if you name your package package-name, users will import it with

import ... from "package-name";

The name needs to be unique, so make sure you check the name is available on https://www.npmjs.com/ or your publish command will fail.

Set the initial version

The version attribute in your package.json will determine the version of the package when published. For your initial release you might use:

{
  "version": "0.0.1"
}

or

{
  "version": "1.0.0"
}

NPM packages use semver for versioning, which means the version consists of 3 numbers: the major, minor and patch version. We will talk more about versioning in the "Versioning" section.

Make sure your repository is not private

In the package.json you may have an attribute "private": true. It is a built in protection so you wouldn't accidentally publish something that's meant to be private. It is generally a good practice to use this if you're building something that is not meant to be open source, like a personal or a client project. However if you're about to publish the repo as a package, this line should be removed.

Add a license

Ensure you have set the license in your package.json. This is to let people know how you are permitting them to use your package. The most common licenses are "MIT" and "Apache-2.0". They are both permissive, allowing users to distribute, modify, or otherwise use the software for any purpose. You can read more about the differences here. I have always used the "MIT" license.

Check the entry point

The main field in your package.json defined the main entry file for the package. This is the file the users will access then importing your package. It'll usually be something like ./index.js or ./src/index.js depending on the location of your entry file.

Restrict the files you want to publish

Be default, publishing a package will publish everything in the directory. Sometimes you might not want to do that, e.g. if your repository includes an example folder or if you have a build step and only want to publish the built bundle. For that purpose, there is a files field in the package.json. If omitted, the field defaults to ["*"], but when set, it includes only the files and directories listed in the array. Note that certain files are always included, even if not listed: package.json, README, CHANGES / CHANGELOG / HISTORY, LICENSE / LICENCE, NOTICE, and the file in the "main" field.

Add a build step

Sometimes, you may need a build step. For example if you've written your package using Flow, TypeScript or cutting edge JavaScript features that aren't implemented in all browsers, and want to compile/transpile your package to vanilla JavaScript that anyone could use. For this you can use a prepublish script like so:

{
  "scripts": {
    "prepublish": "npm run your-build-script"
  }
}

This will be run automatically when you run the publish command. For example in this package I use the prepublish script to rebuild the app in the dist directory. Notice also that the main field in this package.json is set to "main": "dist/index.js" since I want the users to access the built file.

There are more built in scripts for various occasions, but this is the only one I've had to use when publishing packages. You can read more about other available scripts here.

Publishing a package (initial release)

Clone your repo, and make sure you're on the latest master branch (or whatever your main branch is) with no uncommitted changes.

Create an account on https://www.npmjs.com/ if you don't have one already, and use these credentials to log in on your terminal:

npm login

Finally, in your project repo, run:

npm publish

If you've set up two factor authentication, you'll get a prompt for it in your terminal before the publish is completed. Once the command has finished successfully, you should be able to instantly see your package at https://www.npmjs.com/package/package-name where package-name is the name of your package set in the package.json, and people will be able to install your package with:

npm install package-name

Versioning

Publishing subsequent versions of the package involves more thought, because now we need to consider versioning. As mentioned above, npm packages are versioned using semver. This means that there are 3 types of releases: major, minor and patch, and you should use these to communicate the types of changes in your library.

  • Major changes include anything that is incompatible with the previous versions
  • Minor changes are usually new features and bigger bug fixes
  • Patch changes are tiny bug fixes or additive changes

One thing to note is that the semver naming is a bit misleading, specifically in "major" - a major release doesn't necessarily mean that a lot has changed. It means that when going from the previous to current version, there is a breaking change, and that the users of your library might need to change something in their code in order to accommodate the latest version. For instance, changing an exported function name or parameter order would be considered major changes. A lot of maintainers tend to collect breaking changes and release them all together to minimise how often the major version is incremented.

The reason you should only do breaking changes in major releases is because the users of your library may opt in to all future patch and minor versions silently, so the next time they run npm install you might end up breaking their build.

In the package.json, this is communicated with ~ and ^ where ~ opts in for all future patch releases and ^ opts in for all future patch and minor releases:

~1.2.3 will match all 1.2.x versions but will miss 1.3.0

^1.2.3 will match any 1.x.x release including 1.3.0, but will hold off on 2.0.0.

Side note: when the major version is 0, the package is considered unstable and so the ^ acts the same as ~ and for all releases before major 1 you may publish breaking changes in minor releases.

There is no option to opt in for all future major releases, because they are expected to be breaking and thus should be manual upgrades. Source.

Publishing a package (subsequent releases)

This is how you do releases after the initial one. As before, you should ensure you're on the master branch with no uncommitted changes. Consider what type of release this is: major, minor or patch. Now run the command to increment the version in your working directory, using major, minor or patch depending on the type of the change:

npm version minor

This is a convenience method that does a couple of things:

  • increments the version in your package.json based on the type of the change
  • commits this version bump
  • creates a tag for the current release in your .git repo

Now all that's left to do is to run the publish command as before:

npm publish

It is important to make sure you do the version bump before the publish. If you try to publish the same version of the library twice, the command will fail.

Finally, you'll want to push the version bump commit and the tag:

git push
git push --tags

Notice that you'll have to push the tags separately using the --tags flag.

If you're using GitHub to host your repository, the tags will show up under the Releases tab on your repository, e.g. https://github.com/kadikraman/draftjs-md-converter/releases

It is good practice to write some release notes against the tag. I usually also use this space to thank any contributors!

Publishing an alpha / beta / release candidate

An alpha / beta / release candidate is usually an early access version of a major release. When you're about to do a major change, you might want to give your users a chance to try out the new release first on an opt-in basis but with the expectation that it may be unstable.

Basically what we want to achieve is to publish a new version "silently" that would allow only interested users to opt in for.

Let's look at how you might create a release candidate. Suppose you are currently on v3.4.5 and you want to do a release candidate for v4.

First, we increment the version. The npm version command allows you to provide your own version number. In this case we're going to say it's the first release candidate for v4:

npm version 4.0.0-rc1

As before, this does the version bump, commits the change, and creates the tag. Now to publish the package. This is the most important step, since you want to ensure none of your users get this release candidate version by default.

The publish command has a --tag argument, which defaults to "latest". This means that whenever someone npm installs your library, they get the last publish that has the "latest" tag. So all we need to do is provide a different tag. This can be anything really, I have usually used "next".

npm publish --tag next

This will publish your package under the next tag, so the users who do npm install package-name will still get v3.4.5, but users who install npm install package-name@next or npm install package-name@4.0.0-rc1 will get your release candidate. Note that if you publish another release candidate with the "next" tag, anyone installing npm install package-name@next will get the latest version.

When you're ready to do the next major release, you can run npm version major (this will bump the version to 4.0.0) and npm publish as normal.

Conclusion

That's pretty much it! Publishing packages can be a bit scary the first few times, but like anything it gets easier the more you do it.

Thank you for contributing to Open Source!

Posted on May 23 by:

kadikraman profile

Kadi Kraman

@kadikraman

Building things in JavaScript, React, React Native, GraphQL. Love open source. Sometimes speak at conferences.

Discussion

markdown guide
 

I strongly recommend using the np utility for running the actual publishing process. It does a great job of running build and test steps, tagging, asking for 2FA input, pushing, and creating an initial release notes changelog based on commits.

I've been using it for the Redux packages for the last couple years, and it's saved me from a number of mistakes.

 

That's a great suggestion, thank you! I'm always rather conscious of the human error involved in publishing packages. Looks like this can save a world of trouble.

 

Good article, It's worth adding that if your package name is taken you can still publish using @user/ prefix. For this you need to add --access=public to publish command, otherwise npm will think that this is private repo.

 

Good point! Thanks for pointing that out.

 

Good article covering publishing.

I dont remember the exact differences for when to use --tags vs --follow-tags, but I might add a note about that option. I think it npm creates annotated tags and this option was added later and recommended over the former.

 

Brilliant write-up, thank you! πŸ™