Unused package.json dependencies are packages still declared in your manifest that no file in the project actually imports. They inflate install times, enlarge the dependency tree your tooling has to resolve, and can end up in the compiled bundle — slowing down page loads for no benefit.
TL;DR: I built whereisdep, a C++ CLI that searches a set of directories for where each declared dependency is actually used, so you can find the ones that are not.
The problem
I faced issue recently regarding having a lot of javascript packages in the package.json file (~1k lines) that are not used in the project and they all are included in the final compiled js file which effects the performance of page loading negatively.
I wanted to search the packages that are not used in the project but it was not that easy to search package by package individually, i found some interesting tools such as "depcheck" but doesn't give me the result that i want, so I decided to build my own tool (whereisdep)
Why unused package.json dependencies are worth removing
It is tempting to treat a stale dependency as harmless — it just sits there. In a large manifest the cost adds up in several separate ways:
- Install and CI time. Every declared package is fetched and resolved on a cold install, whether or not a single line of code imports it. On a thousand-line manifest this is measured in minutes per pipeline run.
- Bundle size. Modern bundlers tree-shake well, but only for modules that are statically analysable. A package pulled in through a side-effecting import, a dynamic
require, or a barrel file can survive into the output. - Supply chain surface. Each dependency is code you have agreed to trust, plus everything it trusts. Removing an unused one removes a whole subtree of that exposure at zero cost to your application.
- Upgrade friction. Dependency bumps, audit warnings, and peer-conflict resolution all get slower and noisier when a meaningful share of the tree is dead weight.
Why static analysis alone is not enough
Tools in this space work by parsing imports and comparing them against the manifest. That is the right idea, but it produces false positives on the things a parser cannot see: packages invoked only from npm scripts, build-time plugins named as strings in a config file, type-only packages, framework peer dependencies, and anything loaded dynamically at runtime.
This is why the output of any such tool is a starting point rather than a delete list. Searching for where a name actually appears across the tree — configs and scripts included, not just import statements — gives you the context to make that call yourself.
Building it in C++
I was confuse between using JS, Rust or C++ but at the end I used C++.

Tool On GitHub: https://github.com/khaledalam/whereisdep
Frequently asked questions
Is it safe to delete every dependency a tool flags as unused?
No. Treat the list as candidates. Remove them in small batches, and run a full build and test suite after each batch — packages referenced only from config files or npm scripts are the ones that commonly break.
Do unused dependencies always increase bundle size?
Not always. If nothing imports a package, a well-configured bundler will leave it out of the output entirely. The install time, audit noise, and supply-chain arguments for removing it still apply.
What about devDependencies?
They never reach the production bundle, so the page-load argument does not apply. They do still affect install time and CI duration, which is reason enough to keep that section tidy.
For more of my open source tooling write-ups, see how I built and published SoftCropper.
Comments