Navigating the Seas of Change: Upgrading npm Packages with Breaking Changes & Vulnerabilities

Navigating the Seas of Change: Upgrading npm Packages with Breaking Changes & Vulnerabilities

In this article, we will discuss, how to use npm in your project so that you can upgrade to newer versions.

Introduction

Hi, Folks. In this blog post, we embark on a journey of resilience and adaptability, exploring the art of upgrading npm packages with breaking changes. Whether you're a junior developer or a senior developer, learn to embrace change and empower your projects for the exciting road ahead.

In the world of Javascript is a developing one and another such major release of features can once again trigger breaking changes. In this article, we will discuss, how to use of (npm) Node package Manager in your project. so that you can upgrade to newer versions with the least possible breaking changes.

I have articulated this article based on 3 scenarios as the following & also added tips & tricks that can become handy.

  1. Simple & minor update on dependencies

  2. Fixing vulnerabilities on package sub-dependencies

  3. Upgrading to a new version of the package (with breaking changes)

Let's begin

Simple & minor update on dependencies

In this first case. We may have a project started a few months ago. The dependencies would be new but still, the packages you installed on the project may have some new minor version updates or patches released by the package owner or maintainer.

So, you may need to update the dependencies to the next minor or patch version. The updates should be only minor or patch versions, not complete new versions. The package update would for example

 "dependencies": {
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "qrcode": "^1.5.3"
  }

Now, you can notice express:^4.18.2 . The ^(Caret) symbol means it can auto-update to a newer patch or minor version while setting up the project next time. This is known as semantic versioning.

Using semantic versioning you can specify which update types your package can accept from dependencies in your package's package.json file.

The easiest way to update the dependencies in this case is to use the command npm update --save .This command will update all the packages listed to the latest version, respecting the semver constraints of both your package and its dependencies.

npm update --save

It will also install missing packages.

If the -g flag is specified, this command will update globally installed packages.

Tip & Tricks

While setting up the new project the npm can auto-update to the latest minor & patch release of the package. This may cause an inconsistency between you & your team member's dependencies & lead to unexpected bugs & errors.

To avoid these there is a specific command while setting up a new project. npm ci

npm ci

aliases: clean-install, ic, install-clean, isntall-clean

The project must have an existing package-lock.json. This command is similar to this, except it's meant to be used in situations where you want to make sure you're doing a clean install of your dependencies.

If dependencies in the package-lock do not match those in package.json, npm ci will exit with an error, instead of updating the package-lock.


Fixing vulnerabilities on package sub-dependencies

In this second case. We may have a project started a year ago. The dependencies would be new but still, the packages you installed on the project may have some second & third-level dependencies & that package can have some vulnerabilities & minor updates.

Now you may have a question What are second & third-level dependencies?

Understanding npm dependency resolution | by Bhammarker Rahul ...

Explaining a npm dependency resolution is out of the scope of this article. So, in simple words, it's dependencies or packages used to build that package. They may also need to be updated.

There are many possible ways to fix this issue. I am going to discuss two ways. They are

  1. Updating the parent packages

  2. Using resolutions or overrides

Updating The Parent Packages

This is a simple & elegant way to update your second & third level dependencies. You can check for the packages that use the vulnerable packages by npm ls <package_name>.

npm ls <package-spec>

This command will print all the versions of packages that are installed, as well as their dependencies when --all is specified.

npm@10.2.5 /path/to/npm
└─┬ init-package-json@0.0.4
  └── promzard@0.1.5
├─┬ chalk@2.4.2
│ ├─┬ ansi-styles@3.2.1
│ │ └─┬ color-convert@1.9.3
│ │   └── color-name@1.1.3
│ ├── escape-string-regexp@1.0.5
│ └─┬ supports-color@5.5.0
│   └── has-flag@3.0.0

Now you can see the parent package using vulnerable second & third-level dependencies & update those parent packages manually.

Using resolutions or overrides

In this method, we are going to specifically update the vulnerable packages by defining them on the package.json file. Both resolutions & overrides are doing the same process. The difference is if you are running on an older npm version older than 8.0.0. Then you can use the NPM Force Resolutions package to enforce the dependent packages to use specific versions.

Resolutions :

"resolutions": {
  "hoek": "4.2.1"
}

The npm-force-resolutions uses the preinstall script so that it patches the package-lock file before every npm install you run.

Overrides:

Since NPM 8.0 the equivalent to resolutions is called overrides.

To make sure the package foo is always installed as version 1.0.0 no matter what version your dependencies rely on:

{
   "overrides": {
     "foo": "1.0.0"
   }
 }

These are popular ways to upgrade the nested dependencies. If you know a better or alternative way discuss & share your knowledge in comments.


Upgrading to a new version of the package (with breaking changes)

In this third case. you have an npm package that has been widely used in your project that you were long ignoring to update but now has a major stable release with all the security and bug fixes.

But the package is so extensively used that updating it all at once will be a nightmare & tedious task for a development team. The right way to handle such a task is always an iterative approach, a few changes at a time.

Assume that your project has a package axios is being used in the project which has to be updated from the version 1.4.0 to 2.1.3.

axios will correspond to one version of the package only in this case 1.4.0. But you begin to wonder that to update iteratively, some part of your codebase has to use the existing installed version of the package and the other new one, but how it is even possible to use two versions of the same package at a time?

How Is Any Of This Possible How Is That Even Possible GIF - How Is ...

Using two versions of a package in your code.

We all know npm install <alias>@npm:<name>@<version> that this command will install the specific version of the library or package.

For instance, this command installs the exact version 2.1.3 of the package axios

npm install axios@2.1.3

This updates the Axios package to version 2.1.3 and you still cannot have co-existing old and newer versions of the package. The way to use both packages at the same time is to install that package with an alias.

We can use this command npm install <alias>@npm:<name> to Install a package under a custom name (alias).

npm install axios2.1.3@npm:axios
//example command
npm i axios2.1.3@npm:axios@2.1.3

For instance, this command installs the package axios under the custom name axios2.1.3. This installs the version 2.1.3 axios under a new name, so that you can use it independently without affecting the original package.

Now you can use the new version by importing or requiring it like below.

const newAxios = require('axios2.1.3');

Tips & Tricks

Commit package-lock.json

Developers often ignore to commit changes package-lock.json or have package-lock.json git ignored. This is NOT recommended.

Save exact versions of packages

We can use --save flag while installing a new package to save an exact version of the packages

npm i express --save

I have tried to share the best practices I have discovered during my experience of handling updates to packages with breaking changes in newer versions of npm packages in large Node.js projects. Let me know in the comments, I would like to know your experiences as well.

References

Conclusion

I hope we all gained some value from reading this article today. Let me know in the comments, I would like to know your experiences as well.

Share it with your friends and let us grow together.

Did you find this article valuable?

Support Saravana Sai by becoming a sponsor. Any amount is appreciated!