May 21, 2018











Mastering your tools as a developer is a very important thing. One of them is Node Package Manager – and quite a crucial one for a JavaScript developer. Having a better understanding how dependencies work will improve your workflow greatly and help to avoid many problems both for you and your fellow developers. Today we will cover how to maintain a proper package.json file and understand how versioning works.

Semantic versioning

The first thing to understand is the concept of semantic versioning (SemVer). A version number following this conception contains three numbers separated by dots. They are called major, minor and patch.

Major

A release that bumps up the major number is very probable to break something and might need users to adapt to it with their code.

Minor

Increasing the minor number means introducing a feature that adds functionality in a backwards-compatible way and should not force their users to change their code.

Patch

New version increasing just the patch number means a backwards-compatible bug fix. It might deal with a security flaw or other issues.

This separation allows us to set some rules on how do we want to deal with new versions of our dependencies.

“dependencies” property

In our package.json file we have a property called dependencies. It is an object that will hold information about the packages, that your project depends on, therefore they will be installed with npm install command. You might notice that there are a few ways to describe the version that we would like to use.

A strict version

1 2 3 "dependencies: { " react ": " 16.3.2 " }

This one means, that we accept just this specific version, and will not let any other to be used.

Accepting new features

1 2 3 4 "dependencies: { " react ": " ^ 16.3.2 ", " redux ": " 4.x " , }

This represents a set of rules:

Don’t accept breaking changes Accept new features (if they’re not breaking) Accept any fixes (if they’re not breaking)

Accepting only new patches

1 2 3 4 "dependencies: { " react ": " ~ 16.3.2 ", " redux ": " 4.0.x " , }

Now the rules slightly change. It still won’t accept breaking changes, but now it won’t accept any new features also. The only thing that you are going to be updated with are patches. New features will be ignored.

Accepting any latest version

1 2 3 4 "dependencies: { " react ": " x ", " redux ": " * " , }

With that range of versions specified, any latest version will be pulled, even if there are breaking changes. Might not be a good idea!

There is a very important thing to remember: even if according to the version number the update should not break your code, it is not guaranteed. It is just that its author thinks. By default, npm adds a ^ prefix. If you would want to change that, you can run npm config set save-exact true which will stop that from happening.

The meaning of devDependencies

Since now we only mentioned the dependencies property, but that is not all that is to it. Another property that we will look into today is called devDependencies.

When you run npm install , all the packages will be installed – both from dependencies and devDependencies. If you would ever like to change that behaviour, you can run the installation process with the production flag. You can do that by running npm install --production or NODE_ENV=production npm install . If you do that, packages from the devDependencies will be skipped. It might be useful if you are publishing your own library, because dependencies listed in devDependencies will not be pulled by someone who uses your published package. Although, it might prove not to be desired in standard application situations, especially if you are bundling your code with Continuous Integration: you wouldn’t want to skip any packages used in this process. This operation might even involve libraries used for testing and linting since it would be a good practice to run it before releasing a new version into production.

package-lock

With Node Package Manager 5 came a new, big feature: package-lock. Package-locks are generated automatically when you install packages. They keep versions of all dependencies currently installed. It is different from package.json in a way, that it always has a strict version number of all your dependencies. Actually, it is a fully-formed node_modules directory representation. That means that it keeps track of both your own dependencies and the packages, that your libraries depend on. It is what might have been missing for you before, since writing down specific versions of your dependencies in package.json guaranteed them only at the top level.

1 2 3 4 5 6 7 8 9 10 11 "react" : { "version" : "16.3.2" , "resolved" : "https://registry.npmjs.org/react/-/react-16.3.2.tgz" , "integrity" : "sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==" , "requires" : { "fbjs" : "0.8.16" , "loose-envify" : "1.3.1" , "object-assign" : "4.1.1" , "prop-types" : "15.6.1" } } ,

It leaves no place for doubts and ensures that the same packages would always be installed. You can see that it even saves the URL of the package in the resolved property and its integrity hash. If it fails to resolve it, it would fall back to a normal package resolution. If you edit your package.json in any way (either manually, or by running one of the npm commands), it will be updated accordingly. If you make a change to your package.json it will be more important than what is already in the package-lock.json. That wasn’t always the case and was the reason for many misunderstandings and finally led to introducing that behaviour in one of the PRs.

You should definitely commit this file into the repository.

Summary

Working on projects with a team requires you to have a basic understanding of how the versioning works so that you can avoid problems with your libraries. This knowledge will help you to keep your package.json clean. Following this tips will make the work easier not only for you but for everyone on the team.