npm: Understanding dependencies

1. dependencies

Suppose, you create a javascript package name your-package and release a your-package.js file.

And Bob (someone) want to use your-package.js in his project name bob-project. Then Bob install your-package via npm:
npm install your-package

But your-package.js required lodash 1.0.0 to work with.
'use strict';

var _ = require('lodash');

// call some lodash functions

so if Bob want to use your-package.js then he must have lodash 1.0.0 somewhere in bob-project.
And because of Bob only care about your-package.js, it painfull for Bob to install lodash 1.0.0 manually.

In this case, you should define lodash 1.0.0 as your-package dependency, inside package.json file:
"dependencies": {
  "lodash": "1.0.0"
}

# Note 1
You may need lodash 1.0.0 as well to run test. So you should install lodash via npm:
npm install lodash@1.0.0
npm will automaticlly add dependency lodash 1.0.0 to package.json file.

Now if Bob install your-package, npm automaticlly install lodash 1.0.0 along with it.
bob-project folder structure is like:
bob-project/
├──node_modules/
│  ├──your-package/
│  └──lodash/              # version 1.0.0
├──package-lock.json
└──package.json

# Note 2
If already have other version of lodash (ex 2.0.0) installed before Bob installing bob-project, folder struct will become:
bob-project/
├──node_modules/
│  ├──your-package/
│  │  └──node_modules/
│  │     └──lodash/        # version 1.0.0
│  └──lodash/              # version 2.0.0
├──package-lock.json
└──package.json
when execute require, nodejs will search modules from current folder first, if package can not be found nodejs will find package in parent folder an so on, so your-package.js will get lodash 1.0.0, separate with lodash 2.0.0. Everything still work!

2. devDependencies

To create your-package you may need some task-runner like gulp, for example to concat all js file into one. So you install gulp via npm:
npm install gulp

By this way (# Note 1 above), npm will add gulp to dependencies, and Bob will get automaticlly install gulp that he does not need to run your-package.js.

In this case, you should define gulp as "devDependencies" by install gulp with --save-dev:
npm install gulp --save-dev
your package.json will be like:
"dependencies": {
  "lodash": "1.0.0"
},
"devDependencies": {
  "gulp": "^3.9.1"
},

Now when Bob install your-package, npm does not care about "devDependencies" and Bob only get lodash be installed.

3. peerDependencies

If your-package actually is a grunt plugin for example, your-package.js file will be like:
'use strict';

var _ = require('lodash');

module.exports = (grunt) => {

  // call some lodash functions

  // use grunt instance to do some small stuff to support main grunt function
}
Without require('grunt'), it's using host grunt package instance (the package installed in bob-project). That mean your-package still need grunt for running, and like "1. dependencies" above it may need to automaticlly install grunt as dependencies.

But, because of your-package is a grunt plugin so maybe Bob has already installed other version of grunt in bob-project, therefore a version of grunt will be automaticlly installed inside your-package folder structure (# Note 2) and would never be used, that is not good.

So your-package should not automaticlly install grunt. By this way you need to tell Bob (when he's installing your-package) that he need grunt for your-package, moreover which version of grunt that your-package can run with. To do this, you need peerDependencies!

You define grunt as peerDependencies like this:
"dependencies": {
  "lodash": "1.0.0"
},
"devDependencies": {
  "gulp": "^3.9.1"
},
"peerDependencies": {
  "grunt": "0.4.2"        # suppose that your-package only work with 0.4.2
},
Now when Bob install your-package, he will get a warning that he need to manually install grunt 0.4.2.

Yes, peerDependencies is just for giving warnings, nothing else!

Event if your-package using direct dependency like lodash 1.0.0 (1.0.0 is lasted version of lodash when you create your-package). But now lodash release 2.0.0 with many great features, using your-package with lodash 2.0.0 would great, and you too lazy to test and update your-package. In this case, you would consider to define lodash in peerDependencies like this:
"dependencies": {
},
"devDependencies": {
  "gulp": "^3.9.1"
},
"peerDependencies": {
  "grunt": "0.4.2"        # suppose that your-package only work with 0.4.2
  "lodash": ">=1.0.0"     # 1.0.0 or newer
},
Now when Bob install your-package, he just manually install lodash 2.0.0 to work with.

Why not define direct dependency to automaticlly install lodash 2.0.0 like this ?:
"dependencies": {
  "lodash": ">=1.0.0"     # 1.0.0 or newer
},
"devDependencies": {
  "gulp": "^3.9.1"
},
"peerDependencies": {
  "grunt": "0.4.2"        # suppose that your-package only work with 0.4.2
},
Because in case of your-package can not work with lodash 2.0.0 due to removed APIs (you can not test in time when lodash 2.0.0 release), your-package in bob-project will brocken.
If you using peerDependencies, after manually installed lodash 2.0.0 and realize that issues, Bob can rollback to lodash 1.0.0 to make your-package work. Fine!

# Preferences

# Peer Dependencies
# Understanding the npm dependency model
# Why use peer dependencies in npm for plugins?
# Semantic Versioning
# Specifics of npm's package.json handling
# Requiring modules in Node.js: Everything you need to know