Every long-lived JS/TS project I've worked on accumulates the same three kinds of rot:
-
Dependencies in
package.jsonthat nothing imports anymore. You addedmoment, migrated off it, and the line stayed. -
Exports nothing consumes. A function was public once, the last caller was deleted, the
exportis still there advertising an API with no users. - Whole files that fell out of the import graph but never got deleted, so every new engineer reads them trying to understand code that runs nowhere.
None of this breaks the build. That's exactly why it survives. The compiler is happy, the tests pass, and the dead weight compounds quietly until onboarding takes a week and your bundle ships code no user will ever execute.
The tool I've settled on for this is knip (by Lars Kappert). I run it on the enterprise codebase I maintain and on basically every other TS project I touch. One command:
$ npx knip
Unused files (2)
src/legacy/formatValue.ts
src/hooks/useLegacyModal.ts
Unused dependencies (3)
lodash package.json
moment package.json
@types/uuid package.json
Unused exports (5)
parseLegacy src/parse.ts:42:14
toLegacyDate src/date.ts:9:14
...
Files, dependencies, and exports in one pass, cross-referenced against the actual import graph. It's the first tool I've used that treats all three as the same problem, which they are: something is declared, nothing uses it, delete it.
The honest caveat
Knip is not zero-config on a real codebase. Anything resolved dynamically, runtime import(), plugin systems, framework entry points it doesn't recognize (Next.js pages, a CLI bin, config files loaded by string), gets flagged as unused when it isn't. You will get false positives on day one.
The fix is a knip.json that names your real entry points, and after that it's accurate. But budget an afternoon to tune it before you trust the output enough to delete on it. Anyone who tells you it's instant hasn't run it on a large app.
What I actually want
Here's the part that bugs me. This problem is not specific to JavaScript. Python pyproject.toml files rot the same way. Go modules accumulate unused requires. Rust Cargo.toml grows dependencies that no use references anymore.
Knip is genuinely good, and it's stuck being JS/TS-only because it leans on the TypeScript compiler's understanding of the module graph. I'd trade a lot for the same three-in-one report, unused files, unused deps, unused exports, in Python and everywhere else. There are partial tools (deptry, vulture, cargo-udeps), but nothing that does all three with knip's precision and single-command ergonomics.
If you write JS/TS and you haven't run knip on your repo, do it this week. If you're building tooling and looking for a real gap: this, for every other language.
Top comments (0)