This documentation answers my question very poorly. I didn't understand those explanations. Can someone say in simpler words? Maybe with examples if it's hard to choose simple words?
EDIT also added peerDependencies
, which is closely related and might cause confusion.
As an example, mocha would normally be a devDependency, since testing isn't necessary in production, while express would be a dependency.
To save a package to package.json as dev dependencies:
When you run
npm install
it will install bothdevDependencies
anddependencies
. To avoid installdevDependencies
run:When trying to distribute an npm package you should avoid using
dependencies
. Instead you need to consider adding it intopeerDependencies
or remove it fromdependencies
.peerDependencies
didn't quite make sense for me until I read this snippet from a blog post on the topic Ciro mentioned above:The plugin does expect a specific version of the host...
peerDependencies
are for plugins, libraries that require a "host" library to perform their function, but may have been written at a time before the latest version of the host was released.That is, if I write
PluginX v1
forHostLibraryX v3
and walk away, there's no guaranteePluginX v1
will work whenHostLibraryX v4
(or evenHostLibraryX v3.0.1
) is released.... but the plugin doesn't depend on the host...
From the point of view of the plugin, it only adds functions to the host library. I don't really "need" the host to add a dependency to a plugin, and plugins often don't literally depend on their host. If you don't have the host, the plugin harmlessly does nothing.
This means
dependencies
isn't really the right concept for plugins.Even worse, if my host was treated like a dependency, we'd end up in this situation that the same blog post mentions (edited a little to use this answer's made up host & plugin):
... and the host obviously doesn't depend on the plugin...
... that's the whole point of plugins. Now if the host was nice enough to include dependency information for all of its plugins, that'd solve the problem, but that'd also introduce a huge new cultural problem: plugin management!
The whole point of plugins is that they can pair up anonymously. In a perfect world, having the host manage 'em all would be neat & tidy, but we're not going to require libraries herd cats.
If we're not hierarchically dependent, maybe we're intradependent peers...
Instead, we have the concept of being peers. Neither host nor plugin sits in the other's dependency bucket. Both live at the same level of the dependency graph.
... but this is not an automatable relationship.
If I'm
PluginX v1
and expect a peer of (that is, have a peerDependency of)HostLibraryX v3
, I'll say so. If you've auto-upgraded to the latestHostLibraryX v4
(note that's version 4) AND havePlugin v1
installed, you need to know, right?npm
can't manage this situation for me --... or...
How about no, npm?!
So npm doesn't. It alerts you to the situation, and lets you figure out if
HostLibraryX v4
is a suitable peer forPlugin v1
.Coda
Good
peerDependency
management in plugins will make this concept work more intuitively in practice. From the blog post, yet again...There are some modules and packages only necessary for development, which are not needed in production. Like it says it in the documentation:
Summary of important behavior differences:
dependencies
are installed on both:npm install
from a directory that containspackage.json
npm install $package
on any other directorydevDependencies
are:npm install
on a directory that containspackage.json
, unless you pass the--production
flag (go upvote Gayan Charith's answer).npm install "$package"
on any other directory, unless you give it the--dev
option.peerDependencies
:npm install
, and you have to solve the dependency yourself manually. When running, if the dependency is missing, you get an error (mentioned by @nextgentech)Transitivity (mentioned by Ben Hutchison):
dependencies
are installed transitively: if A requires B, and B requires C, then C gets installed, otherwise, B could not work, and neither would A.devDependencies
is not installed transitively. E.g. we don't need to test B to test A, so B's testing dependencies can be left out.Related options not discussed here:
bundledDependencies
which is discussed on the following question: Advantages of bundledDependencies over normal dependencies in NPMoptionalDependencies
(mentioned by Aidan Feldman)devDependencies
dependencies
are required to run,devDependencies
only to develop, e.g.: unit tests, CoffeeScript to JavaScript transpilation, minification, ...If you are going to develop a package, you download it (e.g. via
git clone
), go to its root which containspackage.json
, and run:Since you have the actual source, it is clear that you want to develop it, so by default, both
dependencies
(since you must, of course, run to develop) anddevDependency
dependencies are also installed.If however, you are only an end user who just wants to install a package to use it, you will do from any directory:
In that case, you normally don't want the development dependencies, so you just get what is needed to use the package:
dependencies
.If you really want to install development packages in that case, you can set the
dev
configuration option totrue
, possibly from the command line as:The option is
false
by default since this is a much less common case.peerDependencies
(Tested before 3.0)
Source: https://nodejs.org/en/blog/npm/peer-dependencies/
With regular dependencies, you can have multiple versions of the dependency: it's simply installed inside the
node_modules
of the dependency.E.g. if
dependency1
anddependency2
both depend ondependency3
at different versions the project tree will look like:Plugins, however, are packages that normally don't require the other package, which is called the host in this context. Instead:
E.g. if
dependency1
anddependency2
peer depend ondependency3
, the project tree will look like:This happens even though you never mention
dependency3
in yourpackage.json
file.I think this is an instance of the Inversion of Control design pattern.
A prototypical example of peer dependencies is Grunt, the host, and its plugins.
For example, on a Grunt plugin like https://github.com/gruntjs/grunt-contrib-uglify, you will see that:
grunt
is apeer-dependency
require('grunt')
is undertests/
: it's not actually used by the program.Then, when the user will use a plugin, he will implicitly require the plugin from the
Gruntfile
by adding agrunt.loadNpmTasks('grunt-contrib-uglify')
line, but it'sgrunt
that the user will call directly.This would not work then if each plugin required a different Grunt version.
Manual
I think the documentation answers the question quite well, maybe you are not just familiar enough with node / other package managers. I probably only understand it because I know a bit about Ruby bundler.
The key line is:
And then under npm-config(7) find
dev
: