Ionică Bizău

How I npm

I write a lot of code every day, publishing my code on GitHub and npm. Each tiny package I create does something, and in most of the cases it's a module which can be used in other applications. :four_leaf_clover:

:thought_balloon: What is npm?

In case you don't know, npm is the default package manager for Node.js. Note that there we can publish modules which are not Node.js specific.

Its name stands for need pray more (or nuclear pizza machine, or npm's pretty magical etc etc).

Whaaat?!

Well, if you are here and think that npm stands for Node Package Manager, I have to tell you that's also correct, but it's not complete. npm is not an acronym, but it's a recursive bacronym for npm is not an acronym. In short, npm is an bacronym, not an acronym like many people believe.

You can refer to npm/npm-expansions for a list of things npm stands for. :sparkles:

At this very moment while writing this post, I currently have 561 packages. You can view them here (npm/~ionicabizau).

:raised_hands: How I do it

I started learning Node.js at the end of the year 2012. I was enjoying using modules by others, but I published my first npm package in August 2013. It was youtube-api–a friendly Node.js wrapper for the YouTube API. :tv:

I liked the npm publishing workflow and I didn't stop there. I created more and more packages.

:rocket: How to create an npm package?

Assuming you have Node.js and npm installed, you have to generate a package.json file then write your code and publish it:

# Generate the package.json file
npm init

# Work on your magic
echo 'module.exports = 42' > index.js

# Publish the stuff
npm publish

But a good package should include good documentation as well as easy to use APIs and easy examples.

:crystal_ball: Automatization and Magic

From publishing new stuff on npm, I found that I was doing a bit of manual work every time. That included:

  1. Creating GitHub repositories
  2. Creating releases on GitHub
  3. Maintaing documentation in sync with the code
  4. Maintaining the same documentation format across the repositories

I started to analyze where I wasted time and created small tools to do the work for me. :construction_worker:

:memo: Blah: Fancy Documentation

I created a tool called blah which, since 2014, takes care of generating the documentation for me:

  • It generates README.md files based on my templates.
  • Handles custom stuff by looking at the blah field in package.json
  • Generates documentation by looking at the JSDoc comments from my code.
  • Bumps the package.json version
  • Creates the CONTRIBUTING.md, LICENSE (takes care of updating the year as well!), .travis.yml and .gitignore files

Pretty cool. Since then I didn't have to manually write Markdown files anymore.

:page_with_curl: Packy: package.json defaults

Every time you npm init a package, you have to write stuff like the author, git repository, license etc.

I created packy which takes static (e.g. author) and dynamic (e.g. git repository url) fields by looking at a configuration file.

After this, I was manually writing the name and description fields and then skipping the others. Then, I ran packy which was autocompleting the author, keywords, git-related stuff and so on.

Since blah can run custom commands for me, I decided to integrate packy in my blah configuration.

:tophat: np-init: Automagically create new packages

Because I was still lazy to create the files manually, I created np-init which creates a minimal npm module:

np-init my-super-package-name 'Some fancy description'

Then I just have to cd my-super-package-name and work on my code directly because everything is there for me (example/index.js, lib/index.js, package.json etc).

:dizzy: babel-it: Babelify the things

Because I started to use ES2015 features in my code and since many people use versions of Node.js which do not support ES2015, I started publishing transpilled versions of my code on npm.

After building babel-it, I replaced the npm publish command with babel-it! That is smart enough to babelify my code, publish it on npm and then rollback to my original code.

:ship: Ship Release

The publishing process was boring again. I decided to create a tool to take care of that: ship-release.

So that being said, I don't even need to take care of babelifying the things because ship-release does that (using babel-it).

After fixing a bug into a package, I simply do:

# Creates and switches on the new-version branch
ship-release branch -m 'Fixed some nasty bug'

# Bump the version
ship-release bump

# Publish
#  - generate docs using Blah
#  - push the changes on GitHub
#  - create and merge the pull request
#  - create a new release in the GitHub repository
#  - transpile the code
#  - publish the new version on npm
ship-release publish -d 'Fixed some nasty bug.'

That's the theory. Let's see all these running in the real world.

Example

Today I'm creating a small module called stream-data which will collect the data emitted by a stream and send it into a callback.

Creating the package

As mentioned above, I'm using np-init to do that:

np-init stream-data 'Collect the stream data and send it into a callback function.'

This created the following directory:

stream-data/
├── (.git)
├── example
│   └── index.js
├── lib
│   └── index.js
└── package.json

2 directories, 3 files

Write a minimal example

When building a house, you have to start with the base. When building an npm package, I like to start with the roof.

Even if the library doesn't do anything yet, I just set up an example the way I'm thinking I want the module to work.

So, I cd stream-data and write this (note that I already had something there created by np-init):

"use strict";

const streamData = require("../lib")
    , fs = require("fs")
    ;

// Create a read stream
let str = fs.createReadStream(`${__dirname}/input.txt`);

// Pass the stream object and a callback function
streamData(str, (err, data) => {
    console.log(err || data);
    // => "Hello World"
});

Write the functionality in the library

np-init generated a JSDoc comment, which initially looked like this:

/**
 * streamData
 * Collect the stream data and send it into a callback function.
 *
 * @name streamData
 * @function
 * @param {Number} a Param descrpition.
 * @param {Number} b Param descrpition.
 * @return {Number} Return description.
 */

I slightly changed the function parameters' names and updated the JSDoc comment:

"use strict";

/**
 * streamData
 * Collect the stream data and send it into a callback function.
 *
 * @name streamData
 * @function
 * @param {Stream} str The stream object.
 * @param {Function} cb The callback function.
 * @returns {Stream} The stream object.
 */
module.exports = function streamData (str, cb) {

    if (cb) {
        let buffer = []
          , error = null
          ;

        str.on("data", chunk => buffer.push(chunk));
        str.on("error", err => error = err);
        str.on("end", () => cb(error, buffer.join(""), buffer));
    }

    return str;
};

Run the example

Well, now we can see if it's really working.

$ node example/
Hello World!

Before publishing it, we need to set up some tests.

Tests

For testing I use tester. To add tester in my project I do tester-init.

$ tester-init
...
$ tree
...
└── test
    └── index.js

After writing a few tests, the module is ready to be published on GitHub and npm.

Publishing

We have to create a GitHub repository. I use ghrepo by @mattdesl (thanks! :cake: :grin:). It's smart enough to create the GitHub repository with the right data (taken from the local git repository url and package.json).

ghrepo --bare --no-open
ship-release bump
ship-release publish -d 'Initial release'

Looking in my directory now, I have a couple of new files (README.md, CONTRIBUTING.md, .gitignore etc).

Now my module is on GitHub and npm ready to be npm installed:

// After doing: `npm install --save stream-data`

const streamData = require("stream-data")
    , fs = require("fs")
    ;

// Create a read stream
let str = fs.createReadStream(`${__dirname}/input.txt`);

// Pass the stream object and a callback function
streamData(str, (err, data) => {
    console.log(err || data);
});

:mortar_board: Things I learned

Do not do manual work. Optimize things as much as you can. Otherwise, you'll waste your precious time. Create your team of bots to help you to optimize things.

Happy npming! :tada:


If you like to support what I do, here is how you can do it.

Have feedback on this article? Let @IonicaBizau know on Twitter.

Have any questions or suggestions? Contact me. Found a bug? Report it now