RemixNode's Blog

TwitterGitHub
The npm Dependency Handbook for You

The npm Dependency Handbook for You

Learn about npm dependencies, devDependencies, peerDependencies, bundledDependencies, and many more in this Handbook.

Introduction

Node Package Manager(npm) is the online software repository that helps publish and manage open-source node.js-based projects. It also provides a CLI (Command Line Interface) for package install/uninstall, version management, and dependency management.

All npm packages contain a particular file called package.json. This file consists of several metadata required for a package. It is the file where a package owner describes the package's name, version, dependencies, license information, code repository, etc. This file provides information to npm so that, npm repository can uniquely identify the package and manage the dependencies.

In this article, I will explain the different ways of managing dependencies and how they are different from each other.

But wait, What is Dependency?

Dependency is dependent on something else. In this case, depending on another package available as npm(publicly, privately, or locally).

We will rarely build an app without any dependencies in the world of re-usability and an open-source mindset.

📦 dependencies

dependencies are specified as a plain JSON object in the package.json file. Each dependency are a key-value pair where the key is the dependency name and value could be a string specifying either of,

  • The package version
  • A Git URL
  • A tarball file URL

You should specify only the must needed packages for the app to run in the dependencies object. It shouldn't include any packages mainly used for building, transpiling, and testing purposes.

Here is an example of dependencies declared with respective package versions,

"dependencies": {
    "moment": "^2.24.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.1.2",
    "recharts": "^1.8.5",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0"
  }

If you are looking for specifying a Git URL for package install, you can do so in the following format,

<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]

And then include as a dependency using the username/repo#branch-name format.

Here is an example,

"dependencies": {
  "cli": "atapas/horoscope-cli#multi-langs"
 }

You may want to build and test a package locally without publishing it. npm provides the utility command called npm pack to create a tarball for a package. You can use that tarball location as a URL to specify as a dependency value in another project.

"dependencies": {
   "react-timeline": "file:react-timeline-0.0.2.tgz"
 }

Notice, the value contains the file name of the tarball along with the file: identifier as a prefix.

📦 devDependencies

When you develop a package, you may not want the consumers(users) of the package to download the test, transpile, build, doc related dependencies. These are internal to your package, and consumers do not need them. For example, jasmine is a test dependency and babel is a dependency for transpiling advanced EcmaScript code to ES5 for backward compatibility.

devDependencies is another meta tag in the package.json file that helps in differentiating the development time vs. run time dependencies. Declaring dependencies as devDependencies helps in followings,

  • Differentiate the packages(or libraries) you want to bundle as a must dependency and what you need for your development, test, build.
  • If your organization follows the practice of registering the license of used packages, you do it only for dependencies, not for devDependencies.
  • Avoid downloading unnecessary packages. Whatever is declared in devDependencies, will not be downloaded if you specify a --production flag with npm install command.

npm install --production avoids installing dependencies declared in devDependencies.

Example of devDependencies declaration in package.json file.

"devDependencies": {
    "@babel/cli": "7.10.1",
    "@babel/core": "7.8.7",
    "@babel/plugin-proposal-class-properties": "7.8.3",
    "@babel/preset-react": "7.10.1",
    "babel-preset-es2015": "6.24.1",
    "jasmine": "2.0.0"
  }

📦 peerDependencies

Let's assume you are developing a react component and you wish to make it available as an npm package. You want a compatibility check for the react and react-dom package versions between your package and the consumer's app.

How do you do that? How do you make sure to express the compatibility of react and react-dom versions to be, say, 16.12.0?

That's when you need peerDependencies. You can declare the peerDependencies as another meta information in package.json file as,

"peerDependencies": {
    "react": "16.12.0",
    "react-dom": "16.12.0"
  },

In case of incompatibilities, it will throw a warning that the peerDependency is not installed. This warning is a great way to save the developers from getting into a situation called dependency hell.

📦 bundledDependencies

It helps in bundling one or more packages within your package. bundledDependencies helps define an array of packages you want to bundle directly while building your package.

Here is an example where your package my-great-package bundles two more packages, your-package and her-package.

{
  "name": "my-great-package",
  "version": "1.0.0",
  "bundledDependencies": [
    "your-package", "her-package"
  ]
}

You can mainly use it for these purposes:

  • You have a private npm package that is not available in the npm registry, and you want to bundle it with another package.
  • You want to bundle and distribute some files(docs) along with your package.

📦 optionalDependencies

As the name suggests, it is for optional dependencies. If these dependencies fail to install, npm or yarn will still say the install process was successful.

There could be dependencies that may not necessarily work in every environment, and you want a fallback mechanism when they are not installed. It is how you can define optionalDependencies in the package.json file.

"optionalDependencies": {
    "package-X": "^5.1.0"
  }

Conclusion

I want to conclude by mentioning that you may not use all the dependency management methods we have seen here, but you need to know them. Knowing the dependency management mechanisms will help you organize your project dependencies very well.

I have seen a few mistakes done by the developers like,

  • Mixing dependencies and devDependencies together. It may lead to issues like downloading unnecessary files.
  • In production build mode, not relying on the --production flag.

I hope this article gives you a basic understanding of several dependency management ways available with the npm ecosystem. Please refer to this for more details.

The cover image is an improvisation done on top of a Design by Freepik.


If it was helpful to you, please Like/Share so that it reaches others as well. To get e-mail notifications on my latest posts, please subscribe to my blog by hitting the Subscribe button at the top of the page.