We’ll assume that you have a basic working knowledge of npm
. However, quiteoften people struggle with the different types of dependencies and, in particularPeer Dependencies. If so, this post will improve your understanding. Thereare actually five different dependency types defined by npm:
- normal dependencies
- dev dependencies
- peer dependencies
- optional dependencies
- bundled dependencies
Of these, normal and dev dependencies are generally well understood and theuse cases for optional and bundled dependencies are few and far between, whichbrings us nicely to the subject of this post, peer dependencies.
We’ll describe what they are and discuss when it might be appropriate to usethem.
Dependencies vs. Peer Dependencies
If you’ve used nodejs at all, you will have come across normal dependencies inpackage.json. They are used to describe the modules that your application dependson. They will always be included in your built module. They look a bit like this:
"dependencies": { "react": "^17.0.1", "react-dom": "^17.0.1"}
And, you’ve probably also seen dev dependencies, which are used to describedependencies that are used as part of your development process but not neededin you product build. They are installed in your dev environment bynpm install
but they are not included in your built module.Test and buildtools are commonly in the dev dependencies,for example:
"devDependencies": { "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.6.0"}
Peer dependencies effectively declare a dependency without including thedependency in your built module. When an application includes your module,that application will in turn need to include the declared dependency. Withnpm
version 4 through to 6, a warning is issued when you run npm install
to remind you to install the peer dependencies. Prior to version 4, npmautomatically included peer dependencies if they weren’t explicitly included.That behaviour led to too much complexity in dependency tree calculation andit was dropped in version 4. With npm
version 7, a brand new dependency treemanager is being introduced. With this, automatic inclusion of peerdependencies is returning. You can read about the Arborist dependency treemanager here.
When to Use Peer Dependencies
Sometimes, and particularly when you are building a library that will be usedby other applications, you will have a dependency that will almost certainlyalso be a core dependency of those other applications. For example, if you arebuilding a library of React components, React will be a dependency you need,but almost certainly the application that uses your library will need React. Thisis where we use Peer Dependencies.
Peer dependencies provide the details of what the host application is expectedto provide. Taking our React example, our peer dependencies might look likethis:
"peerDependencies": { "react": "^17.0.1", "react-dom": "^17.0.1"}
Semantic Versioning
When specifying the allowed versions of a package in a peer dependency in ourpackage.json
, we often want to specify more liberal version ranges than wetypically use for normal or dev dependencies. As a brief recap, semanticversioning or semver is used in package.json to specify the versions of adependency that are compatible with the package described by the package.json.Semantic versioning generally defines the version of a package using threedigits, major.minor.patch
.
Most normal or dev dependencies use one of two specifiers:
- tilde (~) to allow newer patch level versions of a package
- caret (^) to allow newer minor level versions of a package
Caret is the default when we use npm install
or npm install --save-dev
.The npm option --save-prefix
can be used to change the specifier used.
To refresh your knowledge of npm’s semver usage, we’d recommend reading theofficial docs.
When it comes to peer dependencies, we are generally specifying the versionsof a dependency that our package can work with, rather than the versionwe’d prefer to use. This sometimes leads to broader specifications than thoseprovided by the tilde and caret specifiers. For example, we might know thatour package can work with more than one major version of a dependency. Considerthat we might be building a React Library that works for both React 16 versionsgreater than minor version 8 an also works with React 17 and is optimisticallyexpected to work with future minor updates to React 17. Our specifier mightlook like this:
"peerDependencies": { "react": "16.8 - 17"}
A useful tool for checking your specifiers is the semver calculator,available on the npm web site.
Unmet Dependencies When Testing
When you use peer dependencies, npm will not automatically install thosedependencies (see comments above in respect to npm version 7). This canlead to errors when you are running tests on your package, although youwill get warnings when executing npm to prompt you to install the peerdependencies. One way to avoid these warnings and errors is to also includethe peer dependencies as dev dependencies. That way, they will be availablefor local testing but will still be peer dependencies in the publishednpm module.
If you’d like to keep up with new posts, please consider following us.