Publishing an npm Package — It’s Easy!

I have published only a few packages in my node.js development tenure, but I would like to motivate myself to publish more and also provide a blog post with encouragement: if I can do it, anyone can do it!

You can publish practically anything legal and moral to npm pretty easily. I do mean anything. It doesn’t even have to be written in node.js … it doesn’t even have to be code. If you have an idea for something you want people to be able to easily install using the npm ecosystem, then publishing an npm package is for you!

This article will focus on publishing libraries for use by web/node.js projects. It assumes basic knowledge of working with node.js and git as version control.

Note: I prefer to use yarn over npm and will use yarn commands throughout the article. yarn publish works a bit differently

Publishing non-Transpiled Code

For the uninitiated, “transpilation” is a subset of “compilation…” that is, taking code from one language and transforming it into another language. The distinction is a bit arbitrary, but transpiling is a type of compiling where the output code is at the same level of abstraction as the source. In web/node land, this typically refers to transforming TypeScript or ECMAScript<TheFuture> into JavaScript that can be run by older versions of vanilla node.js. Writing code with the intention of transpiling it is very common nowadays, but we’ll start with publishing a library where the source is written to run on the intended target platform without modification.

Getting started

First off, create a directory for your project and set up version control, e.g. git init.

If you’re starting the project from scratch, a good first step is to run yarn init. This will ask a few questions about the project such as its name and create a package.json file for you.

Now, you can start writing your code as you normally would. To keep things simple, start with an index.js file that exports the key components of your code.

exports.anotherPackageFn = () => {};

If you’ve written code that exports other code, you’ve written code that will work as a published module.

Getting ready to publish

Once you’ve gotten your code working and you’re satisfied with your tests and ready to release it in the world, there are a few bookkeeping things you can do before publishing.

  1. Review your code and documentation to make sure everything is in order. Run your tests again, if you have them.
  2. Bump the version number in package.json once everything is ready to go. You may want to use the semantic versioning scheme, but there are no rules about how to do your versioning except that version numbers in your package.json have to be valid semver numbers. Users of your package are likely to expect it to follow semantic versioning.
  3. Create a git tag with the version you want to publish. npm can do this for you as part of the publishing as well.

Publishing

At this point, npm publish is all you need to do! If you did not bump the version number, npm will complain, but this is easy to correct. Now you can check out your package on npmjs.com, and even install it using yarn add <package-name> and start using it.

I did this with https://github.com/ajcrites/spongemock/tree/v0.0.1, a very simple node.js package written in vanilla JavaScript following all of the basic publishing procedures.

Adding binaries and using npm lifecycle scripts

Having an index.js file at the root of the repo may not be a problem even for very large projects, but for organizational purposes you may want to use the main property in your package.json to set the file is used when your library gets imported. This usually defaults to index.js, but if you want your source code to be under a src/ directory, you can move it and update your package.json

"name": "another-package-fn",
"main": "src/index.js"

If you publish this now, it will work identically to how it did before.

Adding binary/executables

You may want to add a binary or executable or what have you to your project as well. This will allow people to run it via yarn global add another-package-fn and then run it via the package name or a name that you give it.

"bin": "src/cli.js"
... or ...
"bin": {
"another-package-fn": "src/cli.js",
"alternate-name": "src/cli.js",
"different-exe": "src/another-cli.js"
}

Now when someone does yarn global add another-package-fn, they will have access to the command line executables another-package-fn and alternate-name which both run the same file, or different-exe which runs something else.

If you don’t provide key-values of names for bin, then the name of the executable will be the same as the package name.

Using .js files for your executables is not a requirement. They can actually be anything you want including shell scripts or any executable at all as long as you provide the correct path.

npm lifecycle hooks for additional publish steps

It’s likely that you’re going to want to use a transpiler at some point such as babeljs. However, you don’t want other users to have to be responsible for transpiling your code. Instead you want to publish the code already transpiled so that when it’s installed users can use it for their own environment.

There are a lot of npm scripts in the lifecycle, and you should use the ones that work for you. prepublish usually works for what I need it for, but more complex packages have other options for building packages for other users.

Let’s say we want to rewrite a package using all of the latest node v8.X stuff, but we want to be able to run it on node v5. We could go through and figure out all of the babel plugins we need in order to prepare our package to be installed, but fortunately babel-preset-env does this beautifully and can prepare our packages for different node versions or browsers. By the way, for anyone who’s unsure, a preset in babel is a collection of plugins.

A very simple setup for doing this would involve installing the babel core / cli for you to build and babel-preset-env, then setting the babel configuration and adding it as a prepublish step. So let’s do it.

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

We use --dev because we don’t need these dependencies to be installed when a user actually installs our package. We only need them for development / building locally. babel-core is a dependency of babel-cli, and we use the latter to do the actual transpiling.

Next, either add a .babelrc or add configuration to your package.json

"babel": {
"presets": [
[
"env",
{
"targets": {
"node": 5
}
}
]
]
},

Now when you run babel commands it will use the env preset to make sure the output code is usable for node version 5 once it’s been transpiled.

We can test this out by running babel directly. We could do yarn global add babel-cli, but since we’ve already installed it locally we can use it directly from node_modules

node_modules/.bin/babel -d lib src

This will run babel on our src directory and create the transpiled code in our lib directory. Now we can test this out by running nodev5 lib/bin.js, for example, and test out our transpiled code. Since this will be our prepublish step anyway, we can go ahead and add this to our package.json scripts as well.

"scripts": {
"prepublish": "babel -d lib src"
}

You may notice that I don’t include node_modules/.bin. This is because all binaries installed locally are automatically included in the path for npm lifecycle scripts. You could include node_modules/.bin above, but it’s not needed. Now we can just run yarn prepublish and it will do the same thing as our node_modules/.bin/babel -d lib src before. This lets us test out our prepublish step.

Note: don’t forget to update your package.json main and bin as needed:

"main": "lib/index.js",
"bin": "lib/cli.js"

Now that we are creating a lib directory from our src, we probably want to .gitignore the generated files. We can also .npmignore the source files since we don’t need these to be included when users install our module.

# .gitignore
lib
# .npmignore
src

Now we can publish our package for other environments to use and it will already be built for them.

A complete example where you can see the full package.json with the build information and working code is at https://github.com/ajcrites/spongemock/tree/v0.0.6

More to come

Now you can publish a node package, but there is still a lot more to learn and do. You can do anything you want for prepublish including more complex build steps using webpack and more.

I intend to publish an update to this article with more examples, especially publishing articles with TypeScript and Angular2 libraries, so stay tuned.