depcheck – Linting dependency usage

In my previous post, I wrote about choosing dependencies wisely. The NPM ecosystem is known for its small packages with a lot of dependencies. You can quickly lose track about your dependency usage. Do you have all dependencies configured correctly? Are all of your dependencies still in use? I want to present you depcheck, a small utility to keep track of your dependency usage.

When working with dependencies from NPM, you can lose track about two cases: Either you still have a dependency declared in your package.json that you aren’t using anymore, or never used. Or you are using a package that you don’t depend on.

The first case can happen quickly: You replaced and existing implementation with a new one that doesn’t require the dependency anymore. Or you simply remove the feature that the used package. Or you just lost track about which dependency you actually use while implementing a new feature. At first, an unused dependency causes no harm. But they can become a maintenance problem. More dependencies increase the complexity of your application. Even an unused dependency can contain security vulnerabilities that might cost time fixing. This is even a problem with automated dependency upgrades via Dependabot, but could be avoided if the dependency didn’t exist at all. Each dependency increases the install time of your project. This might be negligible on your local device, but due to many builds it can quickly sum up in your CI environment.

Missing dependencies can be a risk too. A missing dependency can happen if you are importing a package that another package brings as an indirect dependency. This can happen quickly via code completion in your IDE — If it’s suggested, then it’s available, right? While the code runs fine, this can turn out as a problem later. What if the package that brings the dependency is updated? It could update the package that you depend on to a newer version that contains a breaking change. Or it could stop bringing the dependency at all. Or you could stop using the package that brings the dependency. In these cases, your code breaks and it might not be that obvious why it broke.

A solution to this problem is depcheck. A small CLI utility that can help you with keeping track of your dependencies.

Depcheck is a tool for analyzing the dependencies in a project to see: how each dependency is used, which dependencies are useless, and which dependencies are missing from package.json.

depcheck repository on GitHub

depcheck is easy to setup — you might not even need to install it. Simply run it from your package’s root folder via npx:

% npx depcheck

Unused dependencies
* redux
* stylis
Unused devDependencies
* @types/jest
* canvas
* prettier-plugin-organize-imports
Missing dependencies
* readline: ./src/data/Feed.ts

The output is simple and self-explaining. depcheck is analyzing your code and detects every package you import. It has support for JavaScript, TypeScript, and some other languages. You might wonder what happens if you have dependency that you aren’t using in your code directly. One example are eslint plugins. depcheck has built-in support for some popular tools and detects their plugins, like eslint, prettier , or webpack so that you don’t have to worry about false positives for them.

However, there are some edge cases where depcheck isn’t able to recognize whether a dependency is in use or not. For example, the typings for jest, which are automatically configure in your tsconfig are not detected as such. Another example is the canvas package, which is a peer dependency of jsdom and only imported if it’s installed. But these corner cases, or false positives, are something you can configure for your project. depcheck has a CLI argument that you can use to configure a list of dependencies to ignore. Besides that, there are some more configuration options available. Next, I want to show how I prefer to use depcheck.

My Setup

My depcheck setup includes three features:

  • A local NPM script that you can run manually,
  • a husky/lint-staged check,
  • and a CI step.

Let’s have a look at the NPM script first. I like to install depcheck as a devDependency, so that I don’t have to install it every time I want to run it. Adding a script is also useful to configure common CLI arguments that you want to pass every time, like an ignore list for false positives. Sometimes it’s also necessary to exclude output folders from the check. Output folders can lead to false positives, for example if they contain stale builds that reference packages that aren’t available in your package.json anymore. If you prefer running it via npx, depcheck also supports a configuration file format.

  "devDependencies": {
    "depcheck": "^1.4.2",
  }
  "scripts": {
    "depcheck": "depcheck --ignores=@types/jest,netlify-cli,prettier-plugin-organize-imports,…",
  },

I always prefer to fail early. In the best case you get feedback before you create a PR and the CI pipeline fails. This can be accomplished using pre-commit-hooks. Nice tools for that are husky and lint-staged. husky takes care that your pre-commit-hook is registered in your local Git working copy when installing NPM packages. lint-staged takes care to run the right check, depending on the changed files. husky can be setup with some simple steps, afterwards you can call lint-staged from your pre-commit-hook. For this setup I run depcheck every time the package.json or another source file is changed. You need to run it in bash, as lint-staged would otherwise pass all changed filenames to depcheck, but it doesn’t need them. This is the lint-staged configuration I add to my package.json:

  "lint-staged": {
    "{package.json,js,jsx,ts,tsx}": [
      "bash -c \"yarn depcheck\""
    ]
  }

Integrating depcheck with your CI is easy. In my case I’m using GitHub Actions. If you already have the NPM script, you can simply execute it. That way you can catch PRs that accidentally introduce unused dependencies or forgot to remove old ones quickly.

- name: depcheck
  run: yarn depcheck

With depcheck, you won’t pollute you project with unused dependencies anymore. And it also saves you from missing dependencies. Now that you have your dependencies under control, start building something cool!