Managing Dependencies - NodeSource

The NodeSource Blog

You have reached the beginning of time!

Managing Dependencies

Previously, we built your first Node.js package and published it to npm.

This time we're going to look at some issues that can be addressed by pulling in someone else's hard work and using it to improve our package.

Numbers in JavaScript

Recall that our package contained the following index.js:

module.exports = function(width, height) {
  return width * height;
};

Working with floating point numbers in JavaScript can be tricky. We can easily demonstrate one of the problems using the Node.js REPL.

$ node
> 1.1 * 0.1
0.11000000000000001

We're going to use this simple multiplication as the kernel of our Node.js package. The obvious problem here, however, is that we expect the result of 1.1 * 0.1 to be 0.11, not 0.11000000000000001. Unfortunately, due to the way floating point numbers are represented in JavaScript, and other languages, we need to deal with this inaccuracy.

With so many packages in the npm registry, it's hard to imagine this particular problem going unsolved. Later we'll discuss how to find a package to meet our needs.

Testing

Before jumping in, this is a great opportunity to (re)introduce test driven development (TDD). Here we'll be using it to ensure the module we choose actually solves the problem we set out to solve.

tape is a great, little, and well supported test runner that we'll be using for the purpose of this exercise. To install it, use the following command:

npm install --save-dev tape

The --save-dev argument updates places the dependency into your package.json's "devDependencies".

Now, create a test.js with the following contents:

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

test('numerical stability', function(t) {
  t.equal(area(1.1, 0.1), 0.11); // expect the result to be .11
  t.end();
});

If you run this test with node test.js it will fail with the message:

not ok 1 should be equal
  ---
    operator: equal
    expected: 0.11
    actual:   0.11000000000000001
    at: Test.<anonymous> (/Users/tmpvar/your-first-node-package/test.js:6:5)
  ...

Which is good, we have a failing test! Now it's time to find a module that we can lean on to fix the underlying problem.

Finding Modules

Some good places to search for Node.js packages are: npmjs.org, node-modules.com, and npmsearch.com. There is also npm search on the command-line which you may find useful.

We'll be using npmsearch.com for this (I'm slightly biased, having written it). Searching directly for "decimal", like so: http://npmsearch.com/?q=decimal, yields roughly 320 packages. That may seem like a ton of packages, but npmsearch sorts them based on an automatically computed rating. Choosing closer to the top is generally better, but not always.

In this case decimal.js is near the top of the list, so it's probably a good idea to at least take a peek at this module and do a bit of due diligence to confirm it will fill our requirement.

Why did I choose this package over the others? Well I've used mathjs and know that decimal.js is one of its dependencies. So I've had some exposure to it already (even if indirectly) and it looks fairly easy to use—bonus!

Some advice for choosing packages

A good package will most importantly do "one thing well", in addition to having:

  • Comprehensive documentation
  • Comprehensive tests
  • A compatible open-source license
  • Legible and well commented source code
  • GitHub stars and npm dependents

You'll develop more criteria as you continue digging through packages, don't be afraid to read their source code.

The more you explore the npm registry, the faster you'll be able to identify suitable packages!

Installing Modules

Now that we have found a package that will solve the problem, let's make decimal.js a dependency of your-first-node-package

This step should look something like this:

$ npm install --save decimal.js
decimal.js@3.0.1 node_modules/decimal.js

npm does a bunch of work to make sure it gets all of the dependencies (recursively!) of the module you are installing.

The --save flag will update your package.json with the version of decimal.js installed by npm. This also means decimal.js will be installed as a dependency of your-first-node-package whenever it gets installed.

This is what our package.json looks like now:

{
  "name": "your-first-node-package",
  "version": "0.0.0",
  "description": "very first package",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/user/your-first-node-package.git"
  },
  "author": "Elijah Insua <tmpvar@gmail.com> (http://tmpvar.com)",
  "license": "MIT",
  "devDependencies": {
    "tape": "^2.13.4"
  },
  "dependencies": {
    "decimal.js": "^3.0.1"
  }
}

What does my dependency tree look like now?

$ npm list
your-first-node-package@0.0.0 /Users/tmpvar/your-first-node-package
├── decimal.js@3.0.1
└─┬ tape@2.13.4
  ├── deep-equal@0.2.1
  ├── defined@0.0.0
  ├─┬ glob@3.2.11
  │ └─┬ minimatch@0.3.0
  │   ├── lru-cache@2.5.0
  │   └── sigmund@1.0.0
  ├── inherits@2.0.1
  ├── object-inspect@0.4.0
  ├── resumer@0.0.0
  └── through@2.3.4

OK, Great, What's Next?

Reading through the decimal.js documentation it looks like the best way to use this is to instantiate a new Decimal(<value>) and then run operations on it. Let's use the Node.js REPL to try it out.

$ node
> var Decimal = require('decimal.js');
undefined
> var a = new Decimal(1.1);
undefined
> a.times(0.1)
{ constructor:
   ...
> a.times(0.1).toNumber()
0.11
>

Perfect! decimal.js can help us fix our failing test case.

We'll be modifying index.js so it looks like:

var Decimal = require('decimal.js');

module.exports = function(width, height) {
  return (new Decimal(width)).times(height).toNumber();
};

Now if we run the test, everything is looking good!

$ node test.js
TAP version 13
# numerical stability
ok 1 should be equal

1..1
# tests 1
# pass  1

# ok

Other Management Operations

The npm client has a lot of other tricks up its sleeve. It has ample documentation, found by typing npm help. Here's a taste:

  • Remove a package with npm rm --save <package name>
  • Upgrade a package to the latest with npm i --save <package name>@latest
  • Open the homepage of a package in a browser with npm doc <package name>

Summary

We've gone from an untested package with numeric accuracy issues and upgraded it into a package that has at least one test (you should add more!) while making it more robust. Depending on your situation, decimal.js might not be the package you were looking for. That's OK just use the npm rm --save <package> command to remove it.

Once you are satisfied with the changes you've made, don't forget to bump the version! In this case it's a patch that doesn't change the API so you can simply:

$ npm version patch
v1.0.1

Then follow the appropriate steps in the previous installment in this series to publish your new version!

Homework

The NodeSource platform offers a high-definition view of the performance, security and behavior of Node.js applications and functions.

Start for Free