Pinning Dependencies with NPM

June 21, 2017

Our build broke at work this week because of a change in a nested dependency (that is a package one of our direct dependency depends on). We were not ready for it. We had pinned all our direct dependencies in package.json. What that means is that you specify a fixed version of your dependency instead of a range. A dependency declaration will look like "awesome-package": "1.2.3" instead of something like "awesome-package": "^1.2.3".

But that won't really give you a reproducible build. If a nested dependency changes, you may end up with a different version of that in future builds if its version is not pinned in one of your direct dependencies. So what to do? How to ensure we get the same version of all our dependencies (including nested dependencies) in all future builds?

The good news is if you use the latest and greatest npm (that is npm 5 or newer), you shouldn't have this problem. npm 5, by default, generates a package-lock.json for any operations where npm modifies either the node_modules tree, or package.json. This file has a snapshot of the versions of all your nested dependencies. So the next time you try to install your dependencies, npm will look at the package-lock.json file and install the exact versions of all the dependencies. So just commit package-lock.json file to your repo and you're done!

If you're running an older version of npm, the best thing you can do is upgrade! Seriously, npm 5 is awesome and fast!!! If you can't upgrade for some reason, then use npm shrinkwrap. This generates a npm-shrinkwrap.json file. This file includes a snapshot of the versions of all your nested dependencies similar to package-lock.json. If npm-shrinkwrap.json is present, npm install will install the exact versions of packages as mentioned in that file and that'll give you a reproducible build.

So yeah, that's all you have to do to pin your nested dependencies with npm so that your builds don't break unexpectedly.

Thanks for reading. Cheers.