Posted on Wednesday, September 21 2016 by Ionică Bizău
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. 🍀
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. ✨
At this very moment while writing this post, I currently have 561 packages. You can view them here (npm/~ionicabizau
).
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. 📺
I liked the npm publish
ing workflow and I didn't stop there. I created more and more packages.
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.
From publishing new stuff on npm, I found that I was doing a bit of manual work every time. That included:
I started to analyze where I wasted time and created small tools to do the work for me. :construction_worker:
I created a tool called blah
which, since 2014, takes care of generating the documentation for me:
README.md
files based on my templates.blah
field in package.json
package.json
versionCONTRIBUTING.md
, LICENSE
(takes care of updating the year as well!), .travis.yml
and .gitignore
filesPretty cool. Since then I didn't have to manually write Markdown files anymore.
package.json
defaultsEvery 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.
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).
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.
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.
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.
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
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"
});
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;
};
Well, now we can see if it's really working.
$ node example/
Hello World!
Before publishing it, we need to set up some 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.
We have to create a GitHub repository. I use ghrepo
by @mattdesl (thanks! 🍰 😁). 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 install
ed:
// 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);
});
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! 🎉
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? Feel free to ping me on Twitter.