Locking transitive Dependencies with NPM

  • July 24, 2017
Table Of Contents

As a developer I am lazy. I don’t build everything by myself because others have done it already. So, when I come upon a problem someone has already solved and that someone put that solution into some library, I simply pull that library into my own - I declare a dependency to that library.

This post describes an important caveat when declaring “soft” dependencies using NPM and how to lock these dependencies to avoid problems.

package.json

In the javascript world, NPM is the de-facto standard package manager which takes care of pulling my dependencies from the web into my own application. Those dependencies are declared in a file called package.json and look like this (example from an angular app):

"dependencies": {
    "@angular/animations": "~4.2.4",
    "@angular/common": "^4.0.0",
    ...
  }

Unstable Dependencies

In the package.json you can declare a dependency using certain matchers:

  • "4.2.4" matches exactly version 4.2.4
  • "~4.2.4 matches the latest 4.2.x version
  • "^4.2.4 matches the latest 4.x.x version
  • "latest" matches the very latest version
  • ">4.2.4" / "<=4.2.4" matches the latest version greater than / less or equal to 4.2.4)
  • * matches any version.

Matchers like ~ and ^ provide a mechanism to declare a dependency to a range of versions instead of a specific version. This can be very dangerous, since the maintainer of your dependency might update to a version that does no longer work with your application. The next time you build your app, it might fail - and the reasons for that failure will be very hard to find.

Stable Dependencies with package-lock.json

Each time I create a javascript app whose dependencies are managed by NPM, the first thing I’m doing is to remove all matchers in package.json and define the exact versions of the dependencies I’m using.

Sadly, that alone does not solve the “unstable dependencies” problem. My dependencies can have their own dependencies. And those may have used one of those matchers to match a version range instead of a specific version. Thus, even though I declared explicit versions for my direct dependencies, versions of my transitive dependencies might change from one build to another.

To lock even the versions of my transitive dependencies to a specific version, NPM has introduced package locks with version 5.

When calling npm install, npm automatically generates a file called package-lock.json which contains all dependencies with the specific versions that were resolved at the time of the call. Future calls of npm run build will then use those specific versions instead of resolving any version ranges.

Simply check-in package-lock.json into version control and you will have stable builds.

Not Working?

NPM doesn’t generate a package-lock.json? Or the versions in package-lock.json are not honored when calling npm run build? Make sure that your NPM version is 5 or above and if it isn’t, call npm install npm@latest (you may also provide a specific version to npm install, if you prefer :)).

Written By:

Tom Hombergs

Written By:

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more than fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more