Understand exactly why a dependency exists in your project — and whether you can remove it.
why-package answers the questions you actually have about your node_modules:
- Where is this package imported? Which of my files pull it in.
- Why is it installed at all? The dependency chain from your app to the package.
- How big is it? Install size on disk + minified/gzip size from Bundlephobia.
- Do I even use it? Which exports you import — and which you don't.
- Can I remove it? Honest, conservative recommendations.
It works with npm, pnpm, and yarn (classic and berry), is fast on huge repos, and degrades gracefully offline.
$ pnpm dlx why-package @tanstack/react-query
@tanstack/react-query v5.59.0
declared in dependencies
Imported by
├─ src/features/users/UserList.tsx
└─ src/hooks/useUsers.ts
Dependency chain
your-app
└─ @tanstack/react-query@5.59.0
Package size
Install size (disk): 3.1 MB
Minified: 2.4 MB
Gzipped: 45 KB
Dependencies: 3
Used exports
- useMutation
- useQuery
Unused exports
- QueryClientProvider
- useInfiniteQuery
Recommendation
Only 2 of 38 exports are used.
Selective imports or a lighter alternative could reduce footprint.
Run it instantly with no install:
pnpm dlx why-package react
# or: npx why-package reactOr install it globally:
pnpm add -g why-package
# npm install -g why-package
# yarn global add why-packageRequires Node.js 20+.
why-package axiosShows where it's imported, the dependency chain, size, used/unused exports, and a recommendation.
If a package isn't a direct dependency, why-package traces the chain that pulled
it in:
$ why-package ansi-styles
ansi-styles v4.3.0
transitive dependency
Imported by
No source files import this package directly.
Dependency chain
Not directly installed.
your-app
└─ chalk@4.1.2
└─ ansi-styles@4.3.0
$ why-package --unused
Unused dependencies
❌ lodash
❌ moment
❌ uuid
3 of 24 dependencies appear unused.
devDependencies and @types/* are excluded — verify before removing.
Cross-references your declared dependencies against what your source actually
imports.
$ why-package --duplicates
Duplicate versions
lodash
├─ 4.17.21
└─ 4.17.15
react
├─ 19.0.0
└─ 18.3.1
$ why-package react --graph
Dependency graph
your-app
├─ react@19.0.0
└─ @testing-library/react@16.0.0
└─ react@19.0.0
Run with no arguments to pick a package from a searchable list:
why-package? Which package do you want to explain? › react
react
axios
lodash
zod
Every command supports --json for scripting and CI:
why-package react --json
why-package --unused --json
why-package --duplicates --json| Option | Description |
|---|---|
--unused |
List declared dependencies that are never imported |
--duplicates |
List packages installed at multiple versions |
--graph |
Show the dependency graph for <package> |
-C, --cwd <dir> |
Directory to analyze (default: current directory) |
--json |
Output machine-readable JSON |
--no-cache |
Disable the on-disk scan cache |
--no-bundle |
Skip the Bundlephobia size lookup (faster / offline) |
--no-color |
Disable colored output |
-V, --version |
Print the version |
-h, --help |
Show help |
- Source scan. Globs your first-party
.js,.jsx,.ts,.tsx,.mjs, and.cjsfiles (ignoringnode_modules,dist,build,coverage,.next,.turbo, and friends) and parses each with ts-morph to extractimport,require(), dynamicimport(), and re-export references. - Lockfile graph. Reads your
package-lock.json,pnpm-lock.yaml, oryarn.lockand normalizes it into a uniform dependency graph to trace chains and detect duplicate versions. - Exports. Resolves the installed package and reads its TypeScript declarations (or ESM entry) to determine which exports you aren't using.
- Size. Combines on-disk install size with Bundlephobia for minified/gzip numbers.
Results are cached per-file by modification time under
node_modules/.cache/why-package, so repeat runs are near-instant.
| Manager | Lockfile | Notes |
|---|---|---|
| npm | package-lock.json |
lockfile v1, v2, v3 |
| pnpm | pnpm-lock.yaml |
v5/v6 inline + v9 snapshots |
| yarn | yarn.lock |
classic (v1) and berry (v2+) |
- Bounded-concurrency file reads and a quick keyword pre-filter before parsing.
- Mtime-based on-disk cache means only changed files are re-parsed.
- Heavy directories are never walked.
Designed to stay responsive on repositories with 100k+ files.
why-package favors being honest over being confident:
- Unused dependencies only checks runtime
dependencies(notdevDependenciesor@types/*, which are usually consumed by tooling or the type system). Packages used via config, CLIs, or plugins can show as unused — always verify before removing. - Unused exports is suppressed when a package is imported as a default or
namespace (e.g.
import _ from 'lodash'), because member access can't be tracked statically. - Bundle size comes from Bundlephobia and is skipped gracefully when offline
(
--no-bundleto always skip).
Contributions are welcome! See CONTRIBUTING.md to get started.
MIT © why-package contributors