A Simple Guide to Publishing an npm Package

Mostafa Fouad
15 min readMar 16, 2019

--

In this guide, I will walk you through the process of building and publishing a simple package to the npm registry.

I tried to make this guide as simple yet as complete as possible but humans do make mistakes, so suggestions and comments are most welcome and appreciated.

Assumptions

This guide assumes that you are using Git as a version control system and GitHub for hosting your code.

TL;DR

This guide explains how to initialize and publish a simple package to npm. It also covers many of the things you need to do before publishing a package to npm such as setting up linting, testing, managing package version, code coverage, continuous integration and other topics.

You can use this CLI utility to generate the template created in this guide.

1. Getting Started

What is npm?

npm is the default package manager for Node.js and it is the world’s largest software registry. Open-source developers use npm to share software. It consists of a command line client (installed automatically with Node.js) and an online database of public and paid-for private package, called the npm registery.

Update Node and npm

First, make sure you have a recent version of Node.js and npm. Open a terminal window and make sure npm version is updated:

~ $ npm i -g npm@latest

Create an npm account

In order to publish an npm package, you will need an npm account, which makes sense. If you do not have one, go ahead and create an account.

Login to npm

Once you have created your account, login using your account credentials:

~ $ npm login

You should get a message similar to this:

~ $ npm login
Username: <your-username>
Password:
Email: (this IS public) <your-email>
Logged in as <your-username> on https://registry.npmjs.org/.

Needless to say that <your-username> and <your-email> will be your own username and email address.

2. Initialize The Package

Now that you are logged in, create a new directory for your package.

For no obvious reason, I will call my package ‘spongepoop’. However, you can choose any other name you want for your package.

~ $ mkdir spongepoop

Inside your package directory, run the initialization command:

~ $ cd spongepoop
spongepoop $ npm init

You will be asked a couple of questions about the name of your package, version, description and so. Enter whatever you want, you can always change things later.

Package.json file

Once you are done, you will see a new package.json file created in the package root directory. All npm packages contain such a file. This file is used to describe your package and allows npm to handle the package’s dependencies.

Package scope

There are lots of packages on npm. The name you have chosen might have already been chosen by someone else. In this case, you can create a scoped package.

A scoped package has a username (or an organization name) added onto the beginning of the package name and looks something like:

@somescope/somepackagename

You might have already seen this naming pattern in packages like @babel/cli, @emotion/core or @storybook/addons.

For this sample package, I will stick with ‘spongepoop’.

Check npm scopes for more information.

Package version

Each npm package needs a version number. This helps developers know if it’s safe to update to a certain version of your package without breaking their code.

The versioning system npm uses is SemVer (Semantic Versioning). Basically, the version number consists of three parts:

MAJOR.MINOR.PATCH

For a version number 10.12.5, the major number is 10, the minor number is 12 and the patch number is 5.

Version number parts should be incremented in the following manner:

MAJOR version is increased when you make incompatible API changes.

MINOR version is increased when you add backwards-compatible functionality.

PATCH version is increased when you make backwards-compatible bug fixes.

You shouldn’t have to worry about managing the version number manually. There is an npm command for bumping those numbers up:

$ npm version patch     # 0.1.0 -> 0.1.1
$ npm version minor # 0.2.6 -> 0.3.0
$ npm version major # 2.1.4 -> 3.0.0

Start with version number 0.1.0 then use the npm version command as you progress with the development.

Note that your working directory must be clean (no uncommitted changes) to use the npm version command.

Check SemVer and npm version for more information.

3. Publish Now

What you have now is sufficient to publish your package. Go ahead and try:

spongepoop $ npm publish

Scoped packages are private by default. If you have created a scoped package, you may need to add an access flag to make your package public:

spongepoop $ npm publish --access=public

A bunch of lines will appear then finally a line that looks like this:

+ spongepoop@0.1.0

This means that your package has been successfully published on npm with version number 0.1.0 and you can install it in any project just like any other npm package:

another-project $ npm i spongepoop
Congrats! You’re now on npm

4. Create a Repository

Your package will most likely contain code, and this code should be hosted somewhere. Now would be a good time to initialize a repository.

spongepoop $ git init

Next, create a repo on GitHub. I chose MIT license but you can choose any license you want.

Once you have created the repo on GitHub, add the remote URL to your local repo:

spongepoop $ git remote add origin <your-repo-url>
spongepoop $ git pull origin master

You should get three new files in the package directory: LICENSE, .gitignore and README.md. Now it’s time to write some code.

5. Create an EditorConfig File

An editor config file helps define consistent coding styles between different editors and IDEs and it is readable by all popular code editors including Sublime Text, Atom and VSCode.

Create a .editorconfig file inside your package directory:

root = true# General settings for whole project
[*]
indent_style = space
end_of_line = lf
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
# Format specific overrides
[*.md]
max_line_length = 0
trim_trailing_whitespace = false

You can read more on how to configure your .editorconfig and what each line does from here or you can use this example file as your starting point.

6. Start Writing Code

Create a src/index.js file inside your package directory and start writing some code:

/**
* Adds two numbers and returns the result in poop emoji.
* @param {Number} a First number
* @param {Number} b Second number
*/
module.exports = function(a, b) {
return Array.apply(null, Array(a + b)).map(
function() {
return '💩';
}
).join('');
};

You can structure your code any way you want. I personally like placing all source code files inside a src directory.

The ‘main’ file is the entry point to your program. You may need to edit package.json and change the main file to src/index.js

{
...
"main": "src/index.js",
...
}

Now, let’s test our little function:

spongepoop $ node  # start node REPL in the package directory
> var pooper = require('.'); // the dot resolves to the main file
> pooper(1,2); // logs three poop emojis
'💩💩💩'

Check npm docs for more information on package.json file.

7. Use Modern JavaScript

The JavaScript language has received a significant update which provides an immense amount of useful features. It’s 2019 and you should already be familiar with most of the new features by now, but if you’re not, then you can check this great post by Flavio Copes.

This step is optional and you can still write your code in plain old ES5 but it is highly recommended that you start using the new syntax.

Start by re-writing the code:

/**
* Adds two numbers and returns the result in poop emoji.
* @param {Number} a First number
* @param {Number} b Second number
*/
export const spongepoop = (a, b) => '💩'.repeat(a + b);

Let’s test our function again:

spongepoop $ node
> var pooper = require('.');
.../spongepoop/src/index.js:7
export const spongepoop = (a, b) => '💩'.repeat(a + b);
^^^^^^
SyntaxError: Unexpected token export

Now we get a Syntax Error. This is because Node.js doesn’t fully support all the new features yet. To fix this problem, we can use a tool to convert code to plain ES5 syntax which is supported by all Node.js versions. The tool we are looking for is called Babel.

Transpile the code

Inside the package directory, install the following dependencies:

spongepoop $ npm i -D @babel/core @babel/cli @babel/preset-env

@babel/core Contains the core functionality of Babel.
@babel/cli Allows using Babel from the terminal.
@babel/preset-env A preset that includes all plugins to support modern JS.

You can configure Babel in many different ways, one of them is using a .babelrc file.

Create the .babelrc file in the root of the package and modify its content to match the following:

{
"presets": [
"@babel/preset-env"
]
}

Next, create a script to transpile the code to normal ES5 syntax using Babel CLI. Add a build script to package.json file:

{
...
"scripts": {
...
"build:commonjs": "babel src --out-dir lib",
...
},
...
}

Running this script npm run build:commonjs will create a lib directory with the transpiled code inside. Commonjs is the standard module specification that Node.js uses.

Finally, change the main file in package.json to refer to lib/index.js instead of src/index.js. This will point projects using your package to import or require from lib directory where the normal ES5 code exists, not from src.

{
...
"main": "lib/index.js",
...
}

Now, run npm run build:commonjs inside the package directory to build (transpile) the code then test the package again:

spongepoop $ node
> var pooper = require('.');
> pooper(2,1); // successfully poops 3 times
'💩💩💩'

Create a UMD version

UMD stands for Universal Module Definition. Simply, it means bundling all your code and all of its dependencies into a single file. You can achieve this using a module bundler called Webpack.

Add the following development dependencies to your package:

spongepoop $ npm i -D webpack webpack-cli cross-env

Cross-env is a package that allows setting environment variables properly across different platforms.

Create the configuration file webpack.config.js for Webpack in the root of the package. Contents of webpack.config.js should be:

const path = require('path');
const { NODE_ENV, FILE_NAME } = process.env;
const filename = `${FILE_NAME}${NODE_ENV === 'production' ? '.min' : ''}.js`;
module.exports = {
mode: NODE_ENV || 'development',
entry: [
'./src/index.js',
],
output: {
path: path.join(__dirname, 'dist'),
filename,
libraryTarget: 'umd',
},
};

Next, add the following build scripts to package.json:

...
"scripts": {
...
"build:umd": "cross-env FILE_NAME=spongepoop webpack",
"build:umd:min": "cross-env NODE_ENV=production npm run build:umd",
...
},
...

Running commands npm run build:umd and npm run build:umd:min inside the package directory will create a new folder named dist that contains two files: spongepoop.js and spongepoop.min.js, respectively.

Clean before building

Just to be on the safe side, make sure to delete both lib and dist directories before building.

Add rimraf as a development dependency:

spongepoop $ npm i -D rimraf

Then add a clean script:

...
"scripts": {
...
"clean": "rimraf lib dist",
...
},
...

Finally, add a build script that does all of the above:

...
"scripts": {
...
"build": "npm run clean && npm run build:commonjs && npm run build:umd && npm run build:umd:min",
...
},
...

8. Use a Style Guide

A style guide is a set of standards that outline how code should be written and organized. In order to enforce a style guide, you would need a code linter.
A code linter is a program that helps save time and write clean and maintainable code by analyzing your code for potential errors.

Don’t leave trails of bad code

ESLint is the most popular JavaScript linting tool out there and it has plugins for many popular editors like VSCode, Sublime Text and Atom to provide realtime code linting.

Install ESLint

Install ESLint as a development dependency:

spongepoop $ npm i -D eslint

Configure ESLint

ESLint can be configured using a configuration .eslintrc file. Create this file in the root of the package and modify its contents so it looks like this:

{
"env": {
"browser": true,
"node": true
}
}

The env configuration key basically tells ESLint which environments our script is designed to run in. Each environment brings with it a certain set of predefined global variables.

AirBnB’s Style Guide

If you do not already have a style guide to follow, then you should definitely adopt AirBnB’s style guide. It is a very comprehensive set of coding standards and it is one of the most popular style guides on the internet.

Install it from npm as a development dependency:

spongepoop $ npm i -D eslint-config-airbnb

AirBnB’s style guide requires other packages to be installed, so let us add them as well:

spongepoop $ npm i -D eslint-plugin-import
spongepoop $ npm i -D eslint-plugin-jsx-a11y
spongepoop $ npm i -D eslint-plugin-react

Now, you need to declare that you will use the style guide in the .eslintrc file:

{
"extends": "airbnb",
"env": {
"browser": true,
"node": true
}
}

Linting non-standard ECMAScript syntax

New ECMAScript features such as class properties are really useful but they might not be part of the standards yet and ESLint will throw a Parsing Error if you use them. To get around this, we will use babel-eslint as an ESLint parser.

Install the babel-eslint as a development dependency:

spongepoop $ npm i -D babel-eslint

Then use it in the .eslintrc file:

{
"parser": "babel-eslint",
"extends": "airbnb",
"env": {
"browser": true,
"node": true
}
}

Add a script for linting

Create a ‘lint’ script in package.json file:

{
...
"scripts": {
...
"lint": "eslint src --ext .js,.jsx",
...
},
...
}

Now you can lint your code by running the following command:

spongepoop $ npm run lint

Running the command above gives one error: ‘Prefer default export’. You can always modify eslint rules to fit your needs. Let’s fix this error:

/**
* Adds two numbers and returns the result in poop emoji.
* @param {Number} a First number
* @param {Number} b Second number
*/
const spongepoop = (a, b) => '💩'.repeat(a + b);export default spongepoop;

9. Test Your Code

You can’t just assume that your code works. You need to be able to prove it by writing tests. For that, we will use Jest.

Jest is a fast and complete testing framework for JavaScript. It’s an open source project maintained by Facebook, and it’s especially well suited for (but not limited to) React code testing.

Add Jest as a development dependency:

spongepoop $ npm i -D jest babel-jest

Babel Jest is required for using Babel in your tests.

Add a test script

Edit package.json and change the test script to use Jest:

{
...
"test": "jest",
...
}

Create a directory tests in the root of the package and add a sample test file index.test.js:

import pooper from '../src';describe('Test', () => {
it('should poop 3 times', () => {
expect(pooper(1, 2)).toBe('💩💩💩');
});
});

You may get a few linting errors in the test file because describe, it and expect functions are not defined. This can be easily fixed by enabling Jest environment.

Edit file .eslintrc to enable Jest:

{
...
"env": {
"browser": true,
"node": true,
"jest": true
},
...
}

Now run npm test and the test should pass with no problems.

10. Symlink Your Package

It makes sense to test your package in a dummy project as if it was installed from npm and see if it is working properly. But it would not make sense to npm publish your package then npm install it every time you need to test it.

There is a handy way of linking your package locally in two steps:

First Step

You need to runnpm link in the package directory. This will create a symlink in the global node_modules folder that links to the package where the npm linkcommand was executed.

spongepoop $ npm link

Second Step

Next, in some other location where you want to test your package, run the command npm link <package-name> and you should be able to import or require the package as if it was an installed dependency.

Note that <package-name> is taken from package.json, not from the directory name.

Assuming you have created another test app or package called my-test-app:

my-test-app $ npm link spongepoop

This will create a symbolic link from your globally-installed package to the node_modules of the current folder.

You can unlink at any time by running the following command in the package directory:

spongepoop $ npm unlink

11. Use Continuous Integration

Travis CI is a continuous integration and delivery platform that runs your tests each time you make a commit or merge a pull request on your GitHub repo.

Travis CI is free for open source projects, so head over to their website and signup using your GitHub account.

Accept the Authorization of Travis CI then you’ll be redirected to GitHub.

Click the green Activate button, and select the repositories you want to use with Travis CI.

Create a .travis.yml file in the root of your package directory to tell Travis CI what to do. The file contents should be similar to the following:

language: node_jsnode_js:
- node

Commit your code and push then check the build on Travis website , it should pass.

You can learn more about travis configuration for Node.js here.

12. Add Code Coverage Statistics

Code coverage is a term that is used to describe how much application code is exercised when an application is running. Jest can generate code coverage by adding the flag --coverage.

Try running this command inside the package directory:

spongepoop $ npx jest --coverage

You will get a nice breakdown of how much code is covered. Let’s add a script for that in package.json:

{
...
"test": "jest",
"coverage": "npm test -- --coverage",
...
}

You can show that to people using a tool called Coveralls.

Coveralls is a hosted analysis tool that can be integrated with Travis CI to provide statistics about your code coverage. Coveralls is free for open source projects, so go ahead and signup with your GitHub account.

Once you’ve signed up, we will need to integrate Coveralls and Travis CI. Install the coveralls package as a development dependency:

spongepoop $ npm i -D coveralls

Then update .travis.yml file with the following:

language: node_jsnode_js:
- node
script:
- npm run coverage -- --coverageReporters=text-lcov | coveralls

This will send the coverage report to Coveralls after each build.

The last step to do is go to https://coveralls.io/ and click the ‘Add Repos’ link from the menu:

Then enable the toggle switch for the repository:

Commit your code and push to GitHub. Travis CI should pick up the push and start a new build. After the build is complete, check your repo on Coveralls and you should find an updated report.

13. Add Badges

Let’s add some badges to the readme file. You will write a readme file, right?
Head over to shields.io and choose some badges. I picked a few:

14. Be Careful

To avoid publishing mistakes, let’s make sure we test, lint and build before running npm publish. Modify package.json to add the following scripts:

{
...
"lint": "eslint src --ext .js,.jsx",
"test": "jest",
"coverage": "npm test -- --coverage",
"posttest": "npm run lint",
"prepublishOnly": "npm test && npm run build",

...
}

Script prepublishOnly will run automatically before npm publish runs and posttest will run automatically after npm test runs.

So, whenever you run npm publish command, the following scripts will run sequentially:
npm test then npm run lint then npm run build and finally npm publish.

Done

Now, let’s try to publish again but first let’s update the package version:

spongepoop $ npm version minor
v0.2.0
spongepoop $ npm publish
...
+ spongepoop@0.2.0

All done, congratulations! 🎉

CLI Tool

Personally, I would not do all of the above every time I start working on a new npm package, and neither should you. For that reason I have created a simple CLI tool that generates this template in seconds.

First install it globally:

$ npm i -g spongepoop

Then use it anywhere you want:

$ poop my-new-project

Conclusion

In this guide we’ve covered many of the things you need to do before you publish a package to npm such as setting up linting, testing, managing package version, code coverage, continuous integration and other topics. All that is left for you to do now is write some code and help solve problems. Happy hacking!

Thanks for making it to the end of this guide.

--

--