The first step to understanding how to properly manage package upgrades is to understand how they are versioned. NPM uses what's called semantic versioning, or semver. The idea behind semver is that all packages in the registry will follow the same versioning rules so that users can reason about what is included in each new package version that gets released. I say "idea" because not all package maintainers follow these rules closely or correctly, sometimes mistakes are made, and sometimes the rules must be broken. In any case, semver uses the following basic format:
Example: version: 1.2.3
More information can be found at the official semver web page.
NPM stores dependency information in a package.json file for each project. It includes version information to determine which versions of a package are compatible with your project. You can mostly treat this as a hands off file and let NPM and other tools manage it for you, but it can also help to understand the syntax so you can understand what is going on—and why—when performing upgrades with the various tooling.
The four main types of versions you'll likely come across are strict versions, tilde (~) versions, carat (^) versions, and wildcards.
/* This version and this version only should be installed. */
/* This version and any higher patch revisions in the 2.22.X version range can be installed. For example, 2.22.2, 2.22.3, but not 2.23.1 or 2.22.0. */
/* This version and any higher minor and patch revisions in the 2.1.X version range can be installed. For example, 2.1.3, 2.3.10, but not 2.1.1. */
/* *, x, or X are used as wildcards in specifying versions. Valid formats are "*", "1.*", "1.*.*", etc. */
There is some special treatment for packages with major versions less than 1 because revisions to these packages are assumed to have a lot of breaking changes. A version of ^0.1.1 will allow that version and any higher patch versions in 0.1.X. A version of ^0.0.1 will only allow 0.0.1.
Finally, prerelease tags are also sometimes used when releasing an alpha or beta version of a package. This could be specified as 1.0.0-alpha.1, 1.0.0-alpha.2, etc. These are treated as part of the 1.0.0 version range so "~1.0.0-alpha.1" could upgrade to anything in the 1.0.X range and "^1.0.0-alpha.1" could upgrade to anything in the 1.X.X range. "1.0.0-alpha.1" would only allow installations of that specific version.
This covers the basic syntax, and under normal use these are all you should run across when making use of NPM in a typical project. For more advanced syntax and special edge cases you can consult the official NPM version documentation.
This is important! Know that you can restore your package.json file in the event that you make some changes that you need to back out. Keep it in source control, make a local copy somewhere else, etc.
NPM has a couple of built in CLI commands to work with the packages in your solution. They take a safe route to updating and follow the version rules outlined above. If you're looking to play it safe and stay as backwards compatible as possible, these are the commands to use.
"npm outdated" will show which packages can be updated according to your package.json version rules. This is why some packages don't show updates even though newer packages are available. For instance, at the time of writing the latest version of react is 16.4.0.
"npm update" can be run against a specific package and it will perform the updates compatible with your package.json on that one package.
Or "npm update" can be run against the entire project, again performing all updates that are compatible with your package.json.
So maybe that wasn't enough and you need to update further. Maybe you want the latest and greatest and need more information to figure out where to start. There are a few good 3rd party tools that help with the task.
"npm-check updates" gives a quick and simple tool for updating all dependencies to the latest versions in your project. Be careful as this will update the major versions as well. You might have some breaking changes to fix.
Install the tool globally with the following command:
"npm i -g npm-check-updates"
Running the "ncu" command inside your project directory will display all possible updates to your solution.
Running "ncu –u" will update all packages to the latest version in package.json file. Run "npm install" after it finishes to install the dependencies.
"npm-check" operates similar to "npm-check-updates" but gives more information about the version changes available and also lets you interactively pick which packages to update instead of an all or nothing approach.
Install the tool globally with the following command:
"npm i -g npm-check"
Running the "npm-check" command inside your project directory will display all possible updates with information about the type of update, project URL, commands, and will even attempt to check if the package is still in use.
This makes it pretty easy to parse through and see what packages might be safe to update.
Running "npm-check –u" will display an interactive command prompt with a list of all package updates organized by update type.
You can arrow down through the list and press space to select/deselect packages. Press the Enter key to confirm your selections and begin the upgrade. The packages will be installed automatically. No need to run a separate command.
A couple of final tips to help with automated build environments. How nice would it be if this could all be automated? Well, there is no magic solution for that, but there are a couple of approaches that can help with package maintenance in the long term.
Tools like greenkeeper.io and David are SaaS solutions that can integrate with GitHub repos to automatically notify you about updates to the packages used by your project. Greenkeeper can even attempt to update your dependencies and test the updated project to see if it passes and notify you of breaking changes.
If you're on another version control and build system (like us) you could create automated jobs that check out the code periodically, run the "ncu" command to see if any updates are available, and then proceed to "ncu –u", "ncu install", and run some tests to verify and report the results.
Finally, make sure to check in both package.json and the generated package-lock.json to your source control repository and use "npm ci" instead of "npm install" when installing packages on an automated build. This will validate that the installed packages (all throughout the node_modules package hierarchy) are an exact match to your local version. If there are differences across the files, the installation will fail instead of generating an application that either doesn't work or that seems to work but doesn't match your local development copy.
There are no silver bullets for keeping NPM solution dependencies up to date, all the time, with no input from the developer. However, there are some tools that can make it easy to figure out what can be updated, what is safe to update, and to put the updates in place. Make sure to back up your package.json file somehow. That way you can always revert back to a working version in case of emergency!