1.0.6 • Published 9 years ago

jam3-lesson-module-creation v1.0.6

Weekly downloads
3
License
MIT
Repository
github
Last release
9 years ago
jam3-lesson » module-creation

Creating a Module

This is a continuation of the module basics lesson. Here we'll learn to write a new module, work with Node's CommonJS syntax, and publish the code to npm.

steps

Once you're comfortable with the process, splitting code into its own module becomes trivial. The benefits of reusability and semantic versioning often outweigh the small upfront cost of creating a new module.

first steps

So, you've got a great idea for a module? The first step is to search npmjs.com to make sure you aren't re-inventing the wheel. If something already exists on npm, there is a chance it might be exactly what you need, but better tested and more depended-upon.

But let's say your module is unique, or specific to your needs, or the existing modules are dangerous to depend on (i.e. broad scope, no tests). This is where you can make your own module.

For this tutorial, we'll create a module that converts HSL to RGB values for a color picker. We can imagine an interface like this:

hsl2rgb([0, 0.5, 0.5]) 
// -> [0.75, 0.25, 0.25]

We use floats (0 .. 1) and bare arrays since these will compose well with other modules and various functional paradigms.

setup

Module names are lowercase and dash-separated. They are also unique, so make sure the name isn't already taken on npm!

Let's call our module float-hsl2rgb since it describes the problem we are trying to solve. In the terminal, make a new folder and move into it:

cd /path/to/npm-modules
mkdir float-hsl2rgb
cd float-hsl2rgb

Now, we need to set up our module. The simplest way of doing this is with npm init. This command will generate a package.json in your current directory. This file holds some information about your module.

npm init

Fill out the description and keyword fields as best you can. Leave the rest of the options unchanged for now.

npm init

entry point

Now let's create an entry point for our module. This is the code that will be used when people "import" the module into their applications. Create an empty file like so:

touch index.js

Note: In package.json, the "index" field must point to this file!

Now open the file in your editor, and copy the following method stub:

function hsl2rgb(hsl) {
  var h = hsl[0];
  var s = hsl[1];
  var l = hsl[2];

  //... HSL -> RGB logic ...

  return [ r, g, b ];
}

module.exports = hsl2rgb;

Node uses CommonJS as its module syntax. Here the module.exports is telling Node that our default export is the hsl2rgb function. You can export anything with this: like a string, or an object, or a class.

Now, when somebody installs our module, they will require it like so:

var hsl2rgb = require('float-hsl2rgb');
console.log(hsl2rgb(0.5, 0.25, 0.75));

You can also export multiple functions from a single entry point:

module.exports.foo = function() {
  //..
};

module.exports.bar = function() {
  //..
};

Then, they can be required individually:

var foo = require('some-module').foo;
var bar = require('some-module').bar;

development

Before we can start adding logic to our function, we need a way to test it and make sure its working as expected. Let's create a test file:

touch test.js

And copy the following into that file:

var hsl2rgb = require('./index.js');

console.log('is a function?', typeof hsl2rgb === 'function');

Here we are using a "relative" require statement. Require statements come in three flavours:

  • if the path starts with ./ or ../, the search is relative to the current file
  • if the path starts with /, the file path is assumed to be absolute
  • otherwise, the search looks within your node_modules folder for an installed module

When a folder is encountered, Node looks for the package.json "index" field, or defaults to index.js if there is none specified. The following are all valid require statements:

//these all resolve to ./foo/bar/index.js
require('./foo/bar/index.js');
require('./foo/bar/index');
require('./foo/bar');

//looks within the local node_modules folder
require('domready');

//some modules come built-in with Node
require('url');
require('fs');

test runner

We can test our file like this:

node test.js

It should print true because of the console.log we copied earlier.

Instead of re-typing this command all the time, we can use a tool to re-run the test on file change. Install nodemon globally like this:

npm install nodemon -g

Now we can run it on our file:

nodemon test.js

Now changing either test.js or index.js will re-run the script.

nodemon

testing

Now let's add some logic to our function. Usually, you would be writing it yourself, but for this tutorial you can copy the implementation here into index.js.

While we add the code, let's also test it to make sure its working as expected. We can start adding some assertions like this to test.js:

var hsl2rgb = require('./index.js');

console.log('is a function?', typeof hsl2rgb === 'function');

var rgb1 = hsl2rgb([0, 0, 0]);
console.log('returns array of 3 values?', rgb1.length === 3);

var redHSL = [0 / 360, 1, 0.5]; //(hue=0,   sat=100%, light=50%)
var redRGB = [1, 0, 0];         //(red=255, green=0,  blue=0)
var rgb2 = hsl2rgb(redHSL);
console.log('actual:', rgb2, 'expected:', redRGB);

Now the nodemon process should print the following:

is a function? true
returns array of 3 values? true
actual: [ 1, 0, 0 ] expected: [ 1, 0, 0 ]

automated testing

You'll notice the last step involves a lot of eye-balling. When you have a lot of assertions, you can start to miss things. It's better to automate the tests so we always know when something is broken. For this, we will use the tape module.

npm install tape --save-dev

This will save the result into node_modules/tape. The --save-dev flag updates our package.json with the new module.

Since this is only used for testing, we are saving it as a "devDependency". If our index.js depended on another module to work, we would list it as a "dependency" and use the --save flag instead.

We can change the test.js file to the following:

var hsl2rgb = require('./index.js');
var test = require('tape');

test('converts [H,S,L] to [R,G,B]', function (t) {
  t.equal(typeof hsl2rgb, 'function', 'is a function');
  t.equal(hsl2rgb([0, 0, 0]).length, 3, 'returns array of 3 values');
  t.deepEqual(hsl2rgb([0 / 360, 1, 0.5]), [1, 0, 0], 'converts red');
  t.end();
});

Now the nodemon process will report whether any of the tests failed.

test

You can see here for some examples of other HSL to RGB assertions.

README.md

Now that everything works, add a README.md file with some details on your module and how to use it. This uses Markdown for styling.

touch README.md

You can see an example readme here.

GitHub repository

Next, we can make a new GitHub repository for our module. Before we make our repository, make sure to include a .gitignore file:

node_modules
*.log
.DS_Store

Tip: Copy the above and run pbpaste > .gitignore to create a new file.

For a quick way to publish a new repository, you can use ghrepo which is geared toward npm modules. Install it like so:

npm install ghrepo -g

Then run it in your module folder:

ghrepo -m 'first commit'

This will create a new repository on your account and push your current folder to it.

github

publishing to npm

(The float-hsl2rgb module already exists on npm, so this is hypothetical.)

The final step is to publish the module to npm. The first time around, you'll need to create an account:

npm adduser

Now you can publish the module to the npm database like so:

npm publish

And it should be live on npmjs.com:
https://www.npmjs.com/package/float-hsl2rgb

maintenance

When you need to make changes to your module, make sure to use semantic versioning. You can use the following commands to bump your module's version and create a new git tag:

npm version major
npm version minor
npm version patch

further reading