In a JavaScript project, understanding the distinctions between dependencies
,
devDependencies
, and peerDependencies
is crucial for effective package
management. Each plays a distinct role in shaping how a project is built and
distributed. In this blog, we'll explore these terms and their differences.
The packages that are really needed for a project to function should be listed
under dependencies
. These packages are always installed within that project.
If the project is also a package, then these dependencies will also get
installed in the host project that uses this package. Below are some common
examples of what might go under dependencies
.
"dependencies": {
"dayjs": "1.11.1",
"immer": "^10.0.2",
"ramda": "^0.29.0",
"react": "^18.2.0"
}
It's important to understand that above packages does not always have to be
under dependencies
. For example if dayjs
is needed only for development,
deployment or testing purposes, It should not be listed under dependencies
but
rather in the devDependencies
section, because dependencies
will be bundled
with the main code, whereas devDependencies
will not. So adding dependencies
that are only used in development purposes under dependencies
is unnecessary
and will increase the bundle size, affecting the performance of the application.
To add a package to dependencies
section, simply run:
yarn add package-name
If we are shipping a package, all our dependencies are installed in the root of
host projects node_modules
. However, an exception occurs if the host project
already has the same dependency but with a different version. In this scenario,
that specific conflicting dependency will be installed within the node_modules
of our package to avoid conflicts between versions.
To explain it bit more lets say we have [email protected]
as one of our dependency
but the host project uses 2.0.0
. Then both version will be installed, 2.0.0
would be the in root node_modules
and 1.0.0
in our package's node_modules
.
This way our package can continue to use 1.0.0
while host uses 2.0.0
. The
folder structure of that host will look something like below.
host-project-app
├── src
│ └── index.js
├── node_modules
│ └── react
│ └── dayjs
│ └── my-awesome-lib
│ └── node_modules
│ └── dayjs
├── package.json
└── README.md
Packages listed in devDependencies
are used specifically for development
purposes. Any dependencies that does not go into our actual code is listed here
and not under dependencies
. These dependencies won't be installed in a
production environment or in a host project if our project acts as a package.
Here are a few examples of what might belong in devDependencies
.
"devDependencies": {
"@babel/core": "^7.16.5",
"eslint": "^8.41.0",
"husky": "^7.0.4",
"jest": "27.5.1",
"prettier": "^2.8.8"
}
Just like in dependencies
, These packages may not always be in
devDependencies
. For example if we are building a package that enhances Jest
tests capabilities, then we should place jest
under dependencies
. Otherwise
our host project will break, because jest
will not be installed in the host
project.
To add a package to devDependencies
section, simply run:
yarn add -D package-name
peerDependencies
are needed only if we are building a package. It allows host
to install any desired version unless we specify otherwise.
If we are using yarn
as the package manager then peerDependencies
are not
installed automatically. We have to install manually, even in our own package.
But there is a slight difference if you are using npm
. Up to version 6 npm
does not install peerDependencies
automatically. However, this changes from
version 7 onwards, as it will install peerDependencies
automatically. So if we
are using yarn
or npm<=v6
and we have for example Storybook or Jest tests in
our package project, then we have to install peerDependencies
as
devDependencies
(not as dependencies
which defeats the purpose) as well.
"devDependencies": {
"@babel/core": "^7.16.5",
"eslint": "^8.41.0",
"husky": "^7.0.4",
"jest": "27.5.1",
"prettier": "^2.8.8",
"dayjs": "1.11.1",
}
"peerDependencies": {
"dayjs": "latest"
}
You might wonder about its purpose if manual installation is required. It serves as a means for our package project to specify essential dependencies required for the package to function properly. Simultaneously, it grants control to the host project to choose which versions to install.
To add a package to peerDependencies
section, simply run:
yarn add --peer package-name
Now the tricky part is determining whether a particular dependency should go
under dependencies
or peerDependencies
. Unfortunately there is no clear
answer here. But there are some questions we can ask ourself to narrow it down.
If the specific version of the dependency is important for our package, that
is if using a different version breaks our package, then definitely we want
to place that package under dependencies
.
It is possible to place such dependencies under peerDependencies
and
specify supported versions like below, but it is not a good practice.
"peerDependencies": {
"dayjs": "1.2.0 || 1.5.1",
}
react
, better place it
under peerDependency
and ensure that our package works with different
version of that dependency. This way, the dependency installed in the host
project can be reused by our package without installing a separate version.peerDependencies
. Good example
of this is, at neeto we have a package called
neeto-commons-frontend
which extracts numerous common functionalities
utilized across various products. One such functionality is our error
handling system, for which we use an
Axios interceptor. To ensure the
functionality of this interceptor, it's crucial to apply this interceptor to
the same instance of Axios. To elaborate further, if we add Axios under
dependencies
in neeto-commons-frontend
, but the host project uses a
different Axios version, we will be making changes to the Axios instance
that's in node_modules
of neeto-commons-frontend
and not of the host
project, means the functionality will not work on the host project.Another rationale behind utilizing peerDependencies
is its substantial impact
on reducing the bundle size, particularly when bundling our package. Now if we
are using Rollup, placing certain packages under peerDependencies
doesn't
automatically exclude them from the bundle. We need to explicitly specify this
in the Rollup configuration. This is achieved through the Rollup external
configuration option, where we provide a list of peerDependencies
to be
excluded from the bundle. To simplify this process,
rollup-plugin-peer-deps-external
automates the inclusion of peerDependencies
within the external
configuration.
import peerDepsExternal from "rollup-plugin-peer-deps-external";
export default {
plugins: [
// Preferably set as first plugin.
peerDepsExternal(),
],
};
If this blog was helpful, check out our full blog archive.