Adaltas

Tutorial for creating and publishing a new Node.js module

In this tutorial, I provide complete instructions for creating a new Node.js module, writing the code in coffee-script, publishing it on GitHub, sharing it with other Node.js fellows through NPM, testing it with Mocha, Should and JsCoverage and integrating it to travis. Because of its simplicity, this module could also be used as a scaffolding to create your own modules. I will consider as a module best practices. Additionally, I recommend listening to this audiocast on GitHub about titled “a module authoring show”.

Let’s start by describing what we want to achieve. We will create a module made of a single function call hello that will return the string world. In the near future, I consider to publish this module on GitHub and share it on NPM. Here’s how, in CoffeeScript we will use it once completed:

1
2
tutorial = require 'tutorial'
console.log tutorial.hello()

The GIT repository

The first step is to create a new directory and initialise it as a Git repository.

1
2
3
mkdir tutorial
cd tutorial
git init

Lot of people prefer to declare each file to ignore. I personnaly like to have Git ignore all hidden files starting with . with the exception of the git ignore file and the Travis declaration file which is covered later. Also ignored are the dependencies modules even if our example module doesn’t require any.

1
2
3
4
5
6
7
8
9
cat > .gitignore <<DELIM
.*
/node_modules
!.travis.yml
!.gitignore
DELIM
git add .gitignore
git commit -m "Ignore hidden files"
DELIM

The layout

The code being written in CoffeeScript, it will be placed inside the “src” folder and generated into the “lib” folder. Tests are written inside the “test” folder. The documentation will be generated from the source code into the doc folder.

1
mkdir src lib test doc

The “package.json” file

Now, we will create a package declaration file. This is a json file present at the root of your module and named “package.json”. Not all fields are of equal importance. The name and version fields are the most important and are required. They form together an identifier that is assumed to be completely unique.

Also worth of interest are the “description”, “keywords”, “homepage”, “bugs”, “author”, “contributors”, “engines”, “repository”, and “scripts” fields. The “bugs” field is here defined as a string but you could also create an object with the “url” and “email” fields inside. I found the email field only useful for popular projects which deserve a mailing list.

Three fields allow you to define your module dependencies which are “dependencies”, “devDependencies” and “optionalDependencies”. The module does not define any external dependency to run but we use coffee-script for development and mocha and should for testing. The main field define the entry point to your program when you call require('tutorial').

Finally we define a “test” script to run our test case by executing the command npm test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "name": "tutorial",
  "version": "0.0.1",
  "descrirption": "HelloNode is a sample Node.js module",
  "keywords": ["tutorial", "helloworld"],
  "homepage": "http://www.adaltas.com/projects/node-hellonode/",
  "bugs": "https://github.com/wdavidw/node-hellonode/issues",
  "author": "David Worms <david@adaltas.com>",
  "contributors": [],
  "engines": {
    "node": ">= 0.6.0"
  },
  "main": "./lib/index",
  "repository": {
    "type": "git",
    "url": "https://github.com/wdavidw/node-csv.git"
  },
  "dependencies": {},
  "devDependencies": {
    "coffee-script": "latest",
    "mocha": "latest",
    "should": "latest"
  },
  "optionalDependencies": {},
  "scripts": {
    "test": "make test"
  }
}

The code

We are writing the code in CoffeeScript. the “.coffee” file is located in “./src/tutorial.coffee” and the generated JavaScript file is located in “./lib/tutorial.js”. Here’s how the “.coffee” file looks like:

1
2
module.exports = ->
  return 'world'

The module.exports is what your script have access to when requiring a module. In our case, it is associated to a function which return the string “world” when called.

The command to generate the JavaScript is ./node_modules/.bin/coffee -b -o lib src/*.coffee.

Code coverage

We use JSCoverage to instrument our source code and measure code coverage in your test. You can read the install instructions or, on OSX, run brew install jscoverage if HomeBrew is installed.

The instrumented code will be generated to the ‘./lib-cov’. After running the command jscoverage --no-highlight lib lib-cov, a part of the newly generated file in ‘./lib-cov/hello.js’ should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_$jscoverage['hello.js'][2]++;
var tutorial;
_$jscoverage['hello.js'][4]++;
require("should");
_$jscoverage['hello.js'][6]++;
tutorial = require("..");
_$jscoverage['hello.js'][8]++;
describe("hello", (function () {
  _$jscoverage['hello.js'][9]++;
  return it("should return world", (function (next) {
  _$jscoverage['hello.js'][10]++;
  tutorial.hello.should.eql("world");
  _$jscoverage['hello.js'][11]++;
  return next();
}));
}));

The instrumented code is called by the test. The mechanism is described below.

The test case

Our module is simple enough to justify a single test case. We use mocha to run the test and should is used for assertion. The test is located in “./test/hello.coffee”. Mocha rely on the describe and it function.

1
2
3
4
5
6
7
8
require 'should'
src = if process.env.TUTORIAL_COV then 'lib-cov' else 'src'
tutorial = require "../#{src}/tutorial"

describe 'hello', ->
  it 'should return world', (next) ->
    tutorial.hello.should.eql 'world'
    next()

Notice how the tutorial module may be included from the ‘./src’ or the ‘./lib-cov’ directory. Set the environment variable “TUTORIAL_COV” to 1 with the command export TUTORIAL_COV=1 to run test coverage.

To run the test, you need to install mocha and should with npm install and execute the command ./node_module/.bin/mocha --compilers coffee:coffee-script. Mocha need the “compilers” argument to map “.coffee” file to the CoffeeScript compiler. Alternatively, you may create a “mocha.opts” file placed in your test folder with the content as “–compilers coffee:coffee-script”.

Note, when writing your mocha function, the next callback isn’t required in case you may run your test synchronously like in the test above.

The documentation

Some of my libraries, like “CSV”, “mecano” and “ron”, include a similar script that parse the comment of my source code and generate markdown out of it. I find this pattern quite usefull. At the time of this writing, I’m still in the process to externalise this source code in docgen and those libraries still include a “doc.coffee” script.

Most of the documentation will be generated but nothing should stop you to manually edit other markdown pages inside the doc folder.

The license

The license file must be present inside your project root directory and named “LICENSE” for NPM to discover it. Popular non-restrictive open source licenses used across the Node.js community are the the MIT license and the BSD license. I suggest you ready my article “About the new BSD license…” as there exist different versions of the BSD license based on the clause you wish to endorse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat > LICENSE <<DELIM
Software License Agreement (BSD License)
========================================
Copyright (c) 2011, SARL Adaltas.

All rights reserved.

Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

-   Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

-   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

-   Neither the name of SARL Adaltas nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of the SARL Adaltas.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
DELIM

The readme

Since our module is already well documented, there is no need to repeat the same information inside of the readme. I like to keep it bare simple, covering the following topics.

  • Short introduction
  • Usage instruction with a simple and optionally an advanced example
  • Note and migration instructions
  • Development information such as installation and how to run the tests
  • List of contributors

The make file

From the package declaration file, we’ve seen that the make file define a “test” command which use Mocha. We will also add a build command to compile the CoffeeScript file into a JavaScript file. Note that the “test”, “doc” and “coverage” commands will be preceded by the “build” command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
REPORTER = dot

build:
  @./node_modules/.bin/coffee -b -o lib src/*.coffee

doc: build
  @cp -rp doc/* $(TUTORIAL_DOC)

test: build
  @NODE_ENV=test ./node_modules/.bin/mocha --compilers coffee:coffee-script \
    --reporter $(REPORTER)

coverage: build
  @jscoverage --no-highlight lib lib-cov
  @TUTORIAL_COV=1 $(MAKE) test REPORTER=html-cov > doc/coverage.html
  @rm -rf lib-cov

.PHONY: test

Travis

Travis CI is a hosted continuous integration service for the open source community. The idea is that any push request to GitHub will trigger a hook. What Travis does is to execute your tests on different virtual machine with various Node.js versions. The exact procedure is published on the Travis Getting Started page. After going to travis and signing-in with your GitHub OAuth, you activate which repository you want to integrate, then GitHub will ask you for granting read and write access and finally, you must include a “.travis.yml” at the root of your repository. The content of the file looks like:

1
2
3
4
5
6
language: node_js
node_js:
  - 0.8
  - 0.9
  - 0.10
  - 0.11

As you can see, the file is written in YAML. Declaring the language as “node_js” tells Travis that it should execute the command npm test. The “node_js” property defines the different version of Node.js used to run the tests.

The example

It is a good practice to include a few samples. For this, a “./samples” directory could be created. For now, we will just create a “./samples/hello.js” file which look like the one in our article introduction, but in JavaScript. Not all our user code in CoffeeScript. We could later enrich the “samples” folder when our user asks usage related questions. Our “hello” sample looks like:

1
2
tutorial = require('tutorial');
console.log(tutorial.hello());

The marketing

Publishing your module on npm will create a dedicated page with information extracted from your package file, including your homepage. The url will be in the form of “https://npmjs.org/package/” followed by your module name.

Also, it is a good practice to add your own entry on the Node.js wiki module page which has been around since the first days of node, before NPM even existed.

The Node.js user mailing list accept module announcements. In such case, prefix your subject title with “[ANN]”.

There also exist a few blogs which may be happy to relay your module announcement.

Comments