NPM Shrinkwrap: a Great Way to Hack Dependencies

One of the greatest things about using Node.js is the huge number of modules that you can pull down and use in your project — more than 120,000 according to modulecounts.com.

This often makes it trivial to create a Node application — for example, you can create a web server to start serving a site in less than a minute.

The problem

However, when you start using other people's Node modules throughout your project, you might find yourself running into the problem of using a module which has a dependency that is out of date, and the module maintainer hasn't gotten around to releasing a new version of their package.

For instance, a project I'm working on uses gulp-sass to compile our SASS into CSS. Gulp-sass is essentially a wrapper around node-sass, which in turn is a wrapper around Libsass, a blazingly fast SASS compiler written in C/C++.

LibSass has come on well over the last year or so, and has pretty good coverage of the various SASS features, and is now able to compile most of the SASS directives to CSS.

However, earlier versions of Libsass had less coverage of the SASS directives, meaning that if you were trying something even a little bit esoteric with your SASS directives, you could find yourself gazing at a screen full of errors.

And, it turns out that the most recent version of gulp-sass (1.3.3 as of writing this post) uses a version of node-sass (^2.0.1) which in turn uses an old version of Libsass which unfortunately breaks some of our SASS.

For instance, we make use of SASS maps, which appear in SASS 3.3 and allow us to create and use data-structures which in some way resemble JavaScript objects:

$icon-type: (
    email: #00824a,
    facebook: #3b5998,
    twitter: #55acee,
    googleplus: #dd4b39,
    linkedin: #007bb6,
    whatsapp: #64d448,
    reddit: #ff5700,
);

@each $icon-type, $bgcolor in $icon-type {
    a.icon--#{$icon-type},
    a.icon--before--#{$icon-type}:before {
        @include icon-svg('icons/icon-#{$icon-type}.svg');
    }
    background-color: $bgcolor;
    }
}

A one-off solution

If we use a more recent version of node-sass, which in turn uses a more recent version of libsass, the above will compile fine.

We could hack the gulp-sass package.json, changing:

"node-sass": "^0.9",

to

"node-sass": "^3.0",

and then, in the gulp-sass directory, re-install the package:

npm install

This would be fine for our individual use, but the next time that one of our team members checks out the project and runs npm install in the root directory, they'd end up with the old 0.9 version of node-sass.

The real solution

NPM shrinkwrap offers a nice solution to this problem.
It allows us to override that version of a particular dependency of a particular sub-module.

Essentially, when you run npm install, npm will first look in your root directory to see whether a npm-shrinkwrap.json file exists. If it does, it will use this first to determine package dependencies, and then falling back to the normal process of working through the package.json files.

To create an npm-shrinkwrap.json, all you need to do is

 npm shrinkwrap --dev

The --dev option is needed if the dependencies you're trying to override are in the devDependencies section of the package.json.

So, in our case, where we want to force gulp-sass to use a more-recent version of node-sass, we'd do the following:

  1. Hack gulp-sass/package.json to change "node-sass": "^0.9" to "node-sass": "^3.0"

  2. Run npm shrinkwrap --dev in our root directory to generate the npm-shrinkwrap.json

  3. Since the only package dependency that we're worried about is node-sass for the gulp-sass package, we can remove everything from the main "dependencies" block except for the "gulp-sass" block

  4. Commit our changes back to our repo, so that the next team member can checkout the repo and simply run npm install

There's much more detail to be found about shrinkwrap, both on npm and on the Node.js blog