An Introduction to the 3 Types of Dependencies in React Libraries
Introduction to project dependencies, devDependencies, and peerDependencies.
The end goal of writing a library is having an application consume our code. In other words, our library would become a dependency of a project. In the same way as projects can depend on our library, our library can also have its own dependencies.
There are three types of dependencies:
Dependencies we rely on (dependencies)
Dependencies we only need for development (devDependencies)
Dependencies we expect our users to provide (peerDependencies)
How package.json defines dependencies#
package.json file defines the three types of dependencies. Each dependency has a name to a corresponding version.
We can use every type of dependency, or none of them at all. There is no rule on the number of dependencies in a project. Though keep in mind adding a dependency to our library comes at a cost.
Every dependency added may make our library grow in size, and adds additional security concerns. Let's take a look at an example
When defining dependencies we want to stick to a version, or a range of versions we know will work with our library.
Packages in npm (Node Package Manager) use semantic versioning.
The semantic versioning standard communicates changes on a project. Version numbers are immutable, or cannot change once they're published.
Semantic Versioning is defined as numbers separated by periods:
MAJOR.MINOR.PATCH. When the major number is incremented there is a high probability of a breaking change in the code.
For example, upgrading Webpack 4.0.0 to 5.0.0 is a major jump in features and deprecations; the code using Webpack will likely break when upgrading versions.
Minor numbers are incremental updates. These could be new features, but breaking changes are minimal.
Lastly, patch numbers communicate bug fixes and security patches. There should be no breaking changes.
|Backwards compatible bug fix||Patch||1.0.1|
|Backwards compatible new feature||Minor||1.1.0|
|Breaking change that is not backwards compatible||Major||2.0.0|
Now that versioning is cleared up, what is up with those symbols in
package.json of the last example?
npm has additional utilities to make handling dependencies easier. For example, the caret
^ tells npm to use version
5.0.0 except if a newer minor or patch version exists.
5.0.1 had been published when we run
yarn, then version
5.0.1 would be installed instead of
These utility symbols are useful to reduce micromanaging dependencies, and allow security fixes to be installed automatically.
A full list of symbols can be found here.
|^||1.X.X||Will allow updating the package to the latest MINOR or PATCH version.|
|>, <, =, >= or <= for comparisons, or - to specify an inclusive range||1.0.0-2.5.1||Will allow specifying ranges of versions (eg. any version between 1.0.0 and 2.5.1)|
|~||1.X.0||Tilde will allow any MINOR version greater than what's specified|
|||||1.0.0 || >3.0.0||Will allow for combining multiple version ranges (eg. version 1.0.0 or any version greater than 3.0.0)|
Keep semantic versioning in mind when managing dependencies and publishing your packages.
When specifying peer dependencies, including a range in the semantic version can reduce warning messages users get when installing our library.
We may know our library works on a certain major version of a dependency, but specifying an exact version
X.0.0 in our peer dependencies will throw warning messages
when a user tries to install a security fix
Be careful allowing ranges with major versions! Always test before publishing.