Skip to content
/ knip Public
forked from webpro-nl/knip

Find unused files, dependencies and exports in your JS/TS project

License

Notifications You must be signed in to change notification settings

zoontek/knip

 
 

Repository files navigation

✂️ Knip

Knip finds unused files, dependencies and exports in your JavaScript and TypeScript projects. Less code and dependencies leads to improved performance, less maintenance and easier refactorings.

export const myVar = true;

This is where ESLint stops: it handles files in isolation, so it does not know whether myVar is used somewhere else. This is where Knip starts: it lints the project as a whole, and finds unused exports, files and dependencies.

It's only human to forget removing things that you no longer use. But how do you find out? Where do you start finding things that can be removed?

The dots don't connect themselves. This is where Knip comes in:

  • Finds unused files, dependencies and exports
  • Finds used dependencies not listed in package.json
  • Built-in support for workspaces (monorepos)
  • Growing list of built-in plugins
  • Use compilers to include other file types (e.g. .mdx, .vue, .svelte)
  • Finds binaries and dependencies in npm scripts, and a lot more locations
  • Finds unused members of classes and enums
  • Finds duplicate exports
  • Supports any combination of JavaScript and TypeScript
  • Features multiple reporters and supports custom reporters
  • Run Knip as part of your CI environment to detect issues and prevent regressions

Knip shines in both small and large projects. It's a fresh take on keeping your projects clean & tidy!

An orange cow with scissors, Van Gogh style “An orange cow with scissors, Van Gogh style” - generated with OpenAI

Installation

npm install -D knip

Knip supports LTS versions of Node.js, and currently requires at least Node.js v16.17 or v18.6.

Configuration

Knip has good defaults and you can run it without any configuration. Here's the default:

{
  "entry": ["index.{js,ts}", "src/index.{js,ts}"],
  "project": ["**/*.{js,ts}"]
}

Well, almost, this is the full list of default extensions: js, mjs, cjs, jsx, ts, mts, cts and tsx.

Entry Files

Knip looks for entry files at the default locations above, but also in other places:

  • The main, bin and exports fields of package.json.
  • Plugins such as for Next.js, Remix, Gatsby or Svelte define entry files so you don't have to.
  • The scripts in package.json may also provide entry files that Knip can use.
  • Knip does this for each workspace it finds.

In other words, Knip looks in many places and you may not need much configuration. In a perfectly boring world where everything is according to defaults you don't even need a knip.json file.

Larger projects tend to have more things customized, and therefore probably get more out of Knip with a configuration file. Let's say you are using .ts files excusively and have all source files only in the src directory:

{
  "$schema": "https://unpkg.com/knip@2/schema.json",
  "entry": ["src/index.ts"],
  "project": ["src/**/*.ts"]
}

The entry files target the starting point(s) to resolve the rest of the imported code. The project files should contain all files to match against the files resolved from the entry files, including potentially unused files.

Places where Knip looks for configuration (ordered by priority):

  • knip.json
  • knip.jsonc
  • .knip.json
  • .knip.jsonc
  • knip.ts
  • knip.js
  • package.json#knip

So you can use a dynamic knip.ts with TypeScript if you prefer:

import type { KnipConfig } from 'knip';

const config: KnipConfig = {
  entry: ['src/index.ts'],
  project: ['src/**/*.ts'],
};

export default config;

Then run the checks with npx knip. Or first add this script to package.json:

{
  "scripts": {
    "knip": "knip"
  }
}

Use npm run knip to analyze the project and output unused files, dependencies and exports. Knip works just fine with yarn or pnpm as well.

Using workspaces in a monorepo? Please see workspaces for more details about configuring them.

Command-line options

$ npx knip --help
✂️  Find unused files, dependencies and exports in your JavaScript and TypeScript projects

Usage: knip [options]

Options:
  -c, --config [file]      Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)
  -t, --tsConfig [file]    TypeScript configuration path (default: tsconfig.json)
  --production             Analyze only production source files (e.g. no tests, devDependencies, exported types)
  --strict                 Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
  --workspace              Analyze a single workspace (default: analyze all configured workspaces)
  --no-gitignore           Don't use .gitignore
  --include                Report only provided issue type(s), can be comma-separated or repeated (1)
  --exclude                Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
  --dependencies           Shortcut for --include dependencies,unlisted,unresolved
  --exports                Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates
  --no-progress            Don't show dynamic progress updates
  --reporter               Select reporter: symbols, compact, codeowners, json (default: symbols)
  --reporter-options       Pass extra options to the reporter (as JSON string, see example)
  --no-exit-code           Always exit with code zero (0)
  --max-issues             Maximum number of issues before non-zero exit code (default: 0)
  --debug                  Show debug output
  --debug-file-filter      Filter for files in debug output (regex as string)
  --performance            Measure count and running time of expensive functions and display stats table
  --h, --help              Print this help text
  --V, version             Print version

(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates

Examples:

$ knip
$ knip --production
$ knip --workspace packages/client --include files,dependencies
$ knip -c ./config/knip.json --reporter compact
$ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
$ knip --debug --debug-file-filter '(specific|particular)-module'

More documentation and bug reports: https://github.com/webpro/knip

Screenshots

Here's an example run using the default reporter:

example output of exported values and types

This example shows more output related to unused and unlisted dependencies:

example output of dependencies

Reading the report

The report contains the following types of issues:

  • Unused files: did not find references to this file
  • Unused dependencies: did not find references to this dependency
  • Unused devDependencies: did not find references to this dependency
  • Unlisted dependencies: used dependencies, but not listed in package.json (1)
  • Unresolved imports: import specifiers that could not be resolved
  • Unused exports: did not find references to this exported variable
  • Unused exports in namespaces: did not find direct references to this exported variable (2)
  • Unused exported types: did not find references to this exported type
  • Unused exported types in namespaces: did not find direct references to this exported variable (2)
  • Unused exported enum members: did not find references to this member of the exported enum
  • Unused exported class members: did not find references to this member of the exported class
  • Duplicate exports: the same thing is exported more than once

When an issue type has zero issues, it is not shown.

(1) If an unlisted dependency is prefixed with bin: it means a binary is missing. This often equals the package name, but not always (e.g. tsc of typescript or webpack from webpack-cli).

(2) The variable or type is not referenced directly, and has become a member of a namespace. Knip can't find a reference to it, so you can probably remove it.

Output filters

You can --include or --exclude any of the types to slice & dice the report to your needs. Alternatively, they can be added to the configuration (e.g. "exclude": ["dependencies"]).

Use --include to report only specific issue types (the following example commands do the same):

knip --include files --include dependencies
knip --include files,dependencies

Use --exclude to ignore reports you're not interested in:

knip --include files --exclude classMembers,enumMembers

Use --dependencies or --exports as shortcuts to combine groups of related types.

Still not happy with the results? Getting too much output/false positives? The FAQ may be useful. Feel free to open an issue and I'm happy to look into it. Also see the next section on how to ignore certain false positives:

Ignore

There are a few ways to tell Knip to ignore certain packages, binaries, dependencies and workspaces. Some examples:

{
  "ignore": ["**/*.d.ts", "**/fixtures"],
  "ignoreBinaries": ["zip", "docker-compose"],
  "ignoreDependencies": ["hidden-package"],
  "ignoreWorkspaces": ["packages/ignore", "packages/examples/**"]
}

These can also be configured per workspace (except for ignoreWorkspaces).

Now what?

This is the fun part! Knip, knip, knip ✂️

As always, make sure to backup files or use Git before deleting files or making changes. Run tests to verify results.

  • Unused files can be removed.
  • Unused dependencies can be removed from package.json.
  • Unlisted dependencies should be added to package.json.
  • Unresolved imports should be reviewed.
  • Unused exports and types: remove the export keyword in front of unused exports. Then you can see whether the variable or type is used within the same file. If this is not the case, it can be removed.
  • Duplicate exports can be removed so they're exported only once.

🔁 Repeat the process to reveal new unused files and exports. Sometimes it's so liberating to remove things!

Workspaces (monorepos)

Workspaces and monorepos are handled out-of-the-box by Knip. Every workspace is part of the analysis.

Here's an example knip.json configuration with some custom entry and project patterns:

{
  "workspaces": {
    ".": {
      "entry": "scripts/*.js",
      "project": "scripts/**/*.js"
    },
    "packages/*": {
      "entry": "{index,cli}.ts",
      "project": "**/*.ts"
    },
    "packages/my-lib": {
      "entry": "main.js"
    }
  }
}

It might be useful to run Knip first with no or little configuration to see where it needs custom entry and/or project files. The default configuration of each workspace is the same as for a regular project.

Workspaces are sometimes also referred to as packages in a monorepo. Knip uses the term workspaces exclusively to indicate the directories that have a package.json.

Root workspaces must be named "." under workspaces (like in the example).

Knip supports workspaces as defined in three possible locations:

  • In the workspaces array in package.json (npm, Yarn, Lerna).
  • In the workspaces.packages array in package.json (legacy).
  • In the packages array in pnpm-workspace.yaml (pnpm).

Extra "workspaces" not configured as a workspace in the root package.json can be configured as well, Knip is happy to analyze unused dependencies and exports from any directory with a package.json.

Here's some example output when running Knip in a workspace:

example output in workspaces

Use --debug to get more verbose output.

Use ignoreBinaries and ignoreDependencies at the root of knip.json for global effect, or inside any workspace config for local effect.

Plugins

Plugins tell Knip where to look for configuration and entry files, and if necessary have a custom dependency finder. Knip plugins are automatically activated, you don't need to install or configure anything.

To explain what they do, here's a quick example from a .eslintrc.json configuration file (for ESLint):

{
  "extends": ["airbnb"],
  "plugins": ["prettier"]
}

Knip's ESLint plugin reads .eslintrc.json and will return eslint-config-airbnb and eslint-plugin-prettier from this example to Knip, so it can tell you whether package.json is out of sync. In a nutshell, this is how plugins work. This is especially useful over time when such configuration files change (and they will)!

Knip contains a growing list of plugins:

Plugins are automatically activated. Each plugin is automatically enabled based on simple heuristics. Most of them check whether one of a few dependencies are listed in package.json. Once enabled, they add a set of config files for itself and/or entry files for Knip to analyze.

  • config files are given to the plugin's dependency finder
  • entry files are given to Knip to include with the analysis of the rest of the source code

See each plugin's documentation for its default values.

config

Plugins usually include config files. They are handled by the plugin's custom dependency finder, which returns all dependencies referenced in the files it is given. Knip handles the rest to determine which of those dependencies are unused or missing.

entry

Other configuration files use require or import statements to use dependencies, so they don't need special handing and can be analyzed like any other source file. That's why these configuration files are also used as entry files.

Override plugin configuration

Usually no custom configuration is required for plugins, but if your project uses custom file locations then Knip allows to override any defaults. Let's take Cypress for example. By default it uses cypress.config.js, but your project uses config/cypress.js. Also, the default pattern for test files is cypress/e2e/**/*.cy.js, but your project has them at e2e-tests/*.spec.ts. Here's how to configure this:

{
  "cypress": {
    "entry": ["config/cypress.js", "e2e-tests/*.spec.js"]
  }
}

Multi-project repositories

Some repositories have a single package.json, but consist of multiple projects with potentially lots of configuration files (such as the Nx "intregrated repo" style). Let's assume some of these projects are apps and have their own Cypress configuration and test files. In that case, we could configure the Cypress plugin like this:

{
  "cypress": {
    "entry": ["apps/**/cypress.config.ts", "apps/**/cypress/e2e/*.spec.ts"]
  }
}

Disable a plugin

In case a plugin causes issues, it can be disabled by using false as its value (e.g. "webpack": false).

Create a new plugin

Getting false positives because a plugin is missing? Want to help out? Please read more at writing a plugin. This guide also contains more details if you want to learn more about plugins and why they are useful.

Compilers

Knip v2 introduces compilers which allows to include files that are not JavaScript or TypeScript in the process of finding unused or missing dependencies. For instance, .mdx, .vue and .svelte files come to mind.

Currently this is only supported by using knip.js or knip.ts. Provide a compilers object in the configuration where each key represents the extension and the value is a function that takes the contents of these files as input and returns JavaScript or TypeScript as output. Here is an example that compiles .mdx files to JavaScript so these files and their imports and exports become part of the analyis:

import { compileSync } from 'mdx-js/mdx';

export default {
  compilers: {
    mdx: compileSync,
  },
};

Read Compilers for more details and examples.

Production Mode

The default mode for Knip is holistic and targets all project code, including configuration files and tests. Test files usually import production files. This prevents the production files or its exports from being reported as unused, while sometimes both of them can be removed. This is why Knip has a "production mode".

To tell Knip what is production code, add an exclamation mark behind each pattern! that is meant for production and use the --production flag. Here's an example:

{
  "entry": ["src/index.ts!", "build/script.js"],
  "project": ["src/**/*.ts!", "build/*.js"]
}

Here's what's included in production mode analysis:

  • Only entry and project patterns suffixed with !.
  • Only production entry file patterns exported for plugins (such as Next.js and Gatsby).
  • Only the start and postinstall scripts (e.g. not the test or other npm scripts in package.json).
  • Only unused exports, nsExports and classMembers are reported (not types, nsTypes, enumMembers).

Strict

Additionally, the --strict flag can be used to:

  • Consider dependencies (not devDependencies) when finding unused or unlisted dependencies.
  • Include peerDependencies when finding unused or unlisted dependencies.
  • Ignore type-only imports (import type {}).
  • Verify each workspace is self-contained: have their own dependencies (and not use packages of ancestor workspaces).

Plugins

Plugins also have this distinction. For instance, Next.js entry files for pages (pages/**/*.tsx) and Remix routes (app/routes/**/*.tsx) are production code, while Jest and Storybook entry files (e.g. *.spec.ts or *.stories.js) are not. All of this is handled automatically by Knip and its plugins.

Paths

Tools like TypeScript, Webpack and Babel support import aliases in various ways. Knip automatically includes compilerOptions.paths from the TypeScript configuration, but does not (yet) automatically find other types of import aliases. They can be configured manually:

{
  "$schema": "https://unpkg.com/knip@2/schema.json",
  "paths": {
    "@lib": ["./lib/index.ts"],
    "@lib/*": ["./lib/*"]
  }
}

Each workspace can also have its own paths configured. Note that Knip paths follow the TypeScript semantics:

  • Path values is an array of relative paths.
  • Paths without an * are exact matches.

Reporters

Knip provides the following built-in reporters:

  • codeowners
  • compact
  • json
  • symbol

Custom Reporters

When the provided built-in reporters are not sufficient, a custom reporter can be implemented.

Pass --reporter ./my-reporter from the command-line. The data can then be used to write issues to stdout, a JSON or CSV file, or sent to a service.

Find more details and ideas in custom reporters.

Public exports

Sometimes a file that's not an entry file has one or more exports that are public, and should not be reported as unused. Such variables and types can be marked with the JSDoc @public tag:

/**
 * Merge two objects.
 *
 * @public
 */

export const merge = function () {};

/** @public */
export const split = function () {};

Knip does not report public exports and types as unused.

FAQ

Really, another unused file/dependency/export finder?

There are already some great packages available if you want to find unused dependencies OR unused exports.

I love the Unix philosophy ("do one thing well"). But in this case I believe it's efficient to handle multiple concerns in a single tool. When building a dependency graph of the project, an abstract syntax tree for each file, and traversing all of this, why not collect the various issues in one go?

How do I handle too many output/false positives?

Too many unused files

When the list of unused files is too long, this means the gap between the set of entry and the set of project files needs tweaking. The gap can be narrowed down by increasing the entry files or reducing the project files, for instance by ignoring specific directories that are not related to the source code imported by the entry files.

Too many unused dependencies

Dependencies that are only imported in unused files are also marked as unused. So a long list of unused files would be good to remedy first.

When unused dependencies are related to dependencies having a Knip plugin, maybe the config and/or entry files for that dependency are at custom locations. The default values are at the plugin's documentation, and can be overridden to match the custom location(s).

When the dependencies don't have a Knip plugin yet, please file an issue or create a new plugin.

Too many unused exports

Unused exports of entry files are not reported. When exports of other files are marked as unused, because they are meant to be used by consumers of the library, there are a few options:

  1. Add the containing file to the entry array in the configuration.
  2. Re-export from an existing entry file.
  3. Mark the exported value or type using the JSDoc @public tag.

How to start using Knip in CI while having too many issues to sort out?

Eventually this type of QA only really works when it's tied to an automated workflow. But with too many issues to resolve this might not be feasible right away, especially in existing larger codebase. Here are a few options that may help in the meantime:

  • Use --no-exit-code for exit code 0 in CI.
  • Use --include (or --exclude) to report only the issue types that have little or no errors.
  • Use separate Knip commands to analyze e.g. only --dependencies or --exports.
  • Use ignore patterns to filter out the most problematic areas.

Comparison

This table is an ongoing comparison. Based on their docs (please report any mistakes):

Feature knip depcheck unimported ts-unused-exports ts-prune
Unused files - - -
Unused dependencies - -
Unlisted dependencies - -
Plugins - -
Compilers - - - -
Unused exports - -
Unused class members - - - -
Unused enum members - - - -
Duplicate exports - -
Search namespaces - -
Custom reporters - - - -
JavaScript support - -
Configure entry files
Workspaces - -
ESLint plugin available - - - -

✅ = Supported, ❌ = Not supported, - = Out of scope

Migrating from other tools

depcheck

The following commands are similar:

depcheck
knip --dependencies

unimported

The following commands are similar:

unimported
knip --production --dependencies --include files

Also see production mode.

ts-unused-exports

The following commands are similar:

ts-unused-exports
knip --include exports,types,nsExports,nsTypes
knip --exports  # Adds unused enum and class members

ts-prune

The following commands are similar:

ts-prune
knip --include exports,types
knip --exports  # Adds unused exports/types in namespaces and unused enum/class members

Projects using Knip

Many thanks to some of the early adopters of Knip:

Knip?!

Knip is Dutch for a "cut". A Dutch expression is "to be geknipt for something", which means to be perfectly suited for the job. I'm motivated to make knip perfectly suited for the job of cutting projects to perfection! ✂️

Contributors

Special thanks to the wonderful people who have contributed to this project:

Contributors

About

Find unused files, dependencies and exports in your JS/TS project

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.2%
  • JavaScript 0.8%