NodeJS, What’s the difference between dependencies, devDependencies and peerDependencies?

NodeJS, What’s the difference between dependencies, devDependencies and peerDependencies?

Summary of important behavior differences:

dependencies are installed on both:

  • npm install from a directory that contains package.json
  • npm install $package on any other directory

devDependencies are:

  • also installed on npm install on a directory that contains package.json, unless you pass the --production flag.
  • not installed on npm install "$package" on any other directory, unless you give it the --dev option.
  • are not installed transitively.

peerDependencies:

  • peerDependencies are automatically installed unless an upstream dependency conflict is present that cannot be automatically resolved

Transitivity :

  • 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.

devDependencies

Source: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#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 contains package.json, and run:

npm install

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) and devDependency 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:

npm install "$package"

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 to true, possibly from the command line as:

npm install "$package" --dev

The option is false by default since this is a much less common case.

peerDependencies

Source: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependencies

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 and dependency2 both depend on dependency3 at different versions the project tree will look like:

root/node_modules/  
                 |  
                 +- dependency1/node_modules/  
                 |                          |  
                 |                          +- dependency3 v1.0/  
                 |  
                 |  
                 +- dependency2/node_modules/  
                                            |  
                                            +- dependency3 v2.0/

Plugins, however, are packages that normally don’t require the other package, which is called the host in this context. Instead:

  • Plugins are required by the host
  • Plugins offer a standard interface that the host expects to find
  • Only the host will be called directly by the user, so there must be a single version of it.

E.g. if dependency1 and dependency2 peer depend on dependency3, the project tree will look like this:

root/node_modules/  
                 |  
                 +- dependency1/  
                 |  
                 +- dependency2/  
                 |  
                 +- dependency3 v1.0/

This happens even though you never mention dependency3 in your package.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 a peer-dependency
  • the only require('grunt') is under tests/: 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 a grunt.loadNpmTasks('grunt-contrib-uglify') line, but it's grunt that the user will call directly.

This would not work then if each plugin required a different Grunt version.

peerDependenciesMeta

Source: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependenciesmeta

When a user installs your package, npm will emit warnings if packages specified in peerDependencies are not already installed. The peerDependenciesMeta field serves to provide npm more information on how your peer dependencies are to be used. Specifically, it allows peer dependencies to be marked as optional.

For example:

{
  "name": "tea-latte",
  "version": "1.3.5",
  "peerDependencies": {
    "tea": "2.x",
    "soy-milk": "1.2"
  },
  "peerDependenciesMeta": {
    "soy-milk": {
      "optional": true
    }
  }
}

Marking a peer dependency as optional ensures npm will not emit a warning if the soy-milk package is not installed on the host. This allows you to integrate and interact with a variety of host packages without requiring all of them to be installed.

bundledDependencies

Source: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bundleddependencies

This defines an array of package names that will be bundled when publishing the package.

In cases where you need to preserve npm packages locally or have them available through a single file download, you can bundle the packages in a tarball file by specifying the package names in the bundledDependencies array and executing npm pack.

For example, If we define a package.json like:

{     
  "name": "awesome-web-framework",     
  "version": "1.0.0",     
  "bundledDependencies": [       
    "renderized",       
    "super-streams"     
  ]   
}

we can obtain the awesome-web-framework-1.0.0.tgz file by running npm pack. This file contains the dependencies rendered and super-streams which can be installed in a new project by executing npm install awesome-web-framework-1.0.0.tgz. Note that the package names do not include any versions, as that information is specified in dependencies.

If this is spelled "bundleDependencies", then that is also honored.

optionalDependencies

Source: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#optionaldependencies

If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install, then you may put it in the optionalDependencies object. This is a map of package name to version or URL, just like the dependencies object. The difference is that build failures do not cause the installation to fail. Running npm install --no-optional will prevent these dependencies from being installed.

It is still your program’s responsibility to handle the lack of the dependency. For example, something like this:

try {     
  var foo = require('foo')     
  var fooVersion = require('foo/package.json').version   
} catch (er) {     
  foo = null   
}   
if ( notGoodFooVersion(fooVersion) ) {     
  foo = null   
}  

// .. then later in your program ..  

if (foo) {     
  foo.doFooThings()   
}

Entries in optionalDependencies will override entries of the same name in dependencies, so it's usually best to only put them in one place.

CONCLUSION

I hope now is all clear.

Thank you for reading 🙏.

Have a nice day

Did you find this article valuable?

Support ion by becoming a sponsor. Any amount is appreciated!