npm Dependency Handbook for You

Subscribe to my newsletter and never miss my upcoming articles

Introduction

Node Package Manager(npm) is the online software repository helps in publishing and managing 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 contains a special file called, package.json. This file consists of several metadata required for a package. This is the file where a package owner describes the name of the package, 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 am going to explain the different ways of managing dependencies and how are they different from each other.

But wait, What is a Dependency?

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

In the world of re-usability and open-source mindset, it is very rare that an app will be built without any dependencies included.

📦 dependencies

dependencies are specified as a plain JSON object in the package.json file. Each dependencies are a key-value pair where, 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 of the packages that are 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"
 }

In many occasions, 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 package.json file that helps in differentiating the development time vs run time dependencies. Declaring dependencies as devDependencies helps in followings,

  • Clearly differentiate the packages(or libraries) you want to bundle as a must dependencies and what you need for your own 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

Lets assume, you are developing a react component and you wish to make it available as a 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, a warning will be thrown 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

This helps in bundling one or more packages within your package. bundledDependencies helps defining an array of packages that 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 which 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 to work on every environment and you want a fallback mechanism when they are not installed. This is how you can define optionalDependencies in package.json file.

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

Conclusion

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

I have seen few mistakes done by the developers like,

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

Hope this article gives you a basic understanding of several dependency management ways available with 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 useful to you, please Like/Share so that, it reaches others as well. To get e-mail notification on my latest posts, please subscribe to my blog by hitting the Subscribe button at the top of the page.

No Comments Yet