Skip to content

Plugins

This page describes why Knip uses plugins and the difference between config and entry files.

Knip has an extensive and growing list of built-in plugins. Currently it’s not possible to add custom plugins, but feel free to request a plugin or even write a plugin so others can benefit too.

Enabled

Plugins are enabled if the related package is listed in the list of dependencies in package.json. For instance, if astro is listed in dependencies or devDependencies, then the Astro plugin is enabled.

Configuration files

Knip uses entry files as starting points to scan your source code and resolve other internal files and external dependencies. The dependency graph can be statically resolved through the require and import statements in those source files. However, configuration files reference external dependencies in various ways. Knip uses a plugin for each tool to parse configuration files and find those dependencies.

In this example we look at Knip’s ESLint plugin. The default config file patterns include .eslintrc.json. Here’s a minimal example:

.eslintrc.json
{
"extends": ["airbnb", "prettier"],
"plugins": ["@typescript-eslint"]
}

Configuration files like this don’t import or require anything, but they do require the referenced dependencies to be installed.

In this case, the plugin will return the eslint-config-airbnb, eslint-config-prettier and @typescript-eslint/eslint-plugin dependencies, so Knip knows they should be listed in package.json.

Some tools allow configuration to be stored in package.json, that’s why some of the relevant plugins contain package.json in the list of config files.

Entry files

Many plugins have default entry files configured. When the plugin is enabled, Knip will add entry files as configured by the plugin to resolve used files and dependencies.

For example, if next is listed as a dependency in package.json, the Next.js plugin will automatically add multiple patterns as entry files, such as pages/**/*.{js,jsx,ts,tsx}. If vitest is listed, the Vitest plugin adds **/*.{test,test-d,spec}.ts as entry file patterns. Most plugins have entry files configured, so you don’t have to.

It’s mostly plugins for meta frameworks and test runners that have entry files configured.

For example, let’s say your Playwright configuration contains the following:

playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: 'integration',
testMatch: ['**/*-test.ts'],
};
export default config;

The Playwright plugin will read this configuration file and return those entry patterns (integration/**/*-test.ts). Knip will then not use the default entry patterns.

You can still override this behavior in your Knip configuration:

knip.json
{
"playwright": {
"entry": "src/**/*.integration.ts"
}
}

This should not be necessary though. Please consider opening a pull request or a bug report if any plugin is not behaving as expected.

Entry files from config files

Entry files are part of plugin configuration (as described in the previous section). Yet plugins can also return additional entry files after parsing configuration files. Below are some examples of configuration files parsed by plugins to return additional entry files. The goal of these examples is to give you an idea about the various ways Knip and its plugins try to find entry files so you don’t need to configure them yourself.

Angular

The Angular plugin parses the Angular configuration file. Here’s a fragment:

angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"projects": {
"knip-angular-example": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/knip-angular-example",
"main": "src/main.ts"
}
}
}
}
}
}

This will result in src/main.ts being added as an entry file (and @angular-devkit/build-angular as a referenced dependency).

GitHub Actions

This plugin parses workflow YAML files. This fragment contains three run scripts:

.github/workflows/deploy.yml
jobs:
integration:
runs-on: ubuntu-latest
steps:
- run: npm install
- run: node scripts/build.js
- run: node --loader tsx scripts/deploy.ts

From these scripts, the scripts/build.js and scripts/deploy.ts files will be added as entry files by the GitHub Actions plugin.

Read more about this in Script Parser.

webpack

Let’s take a look at this example webpack configuration file:

webpack.config.js
module.exports = env => {
return {
entry: {
main: './src/app.ts',
vendor: './src/vendor.ts',
},
module: {
rules: [
{
test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp)$/i,
use: 'base64-inline-loader',
},
],
},
};
};

The webpack plugin will parse this and add ./src/app.ts and ./src/vendor.ts as entry files. It will also add base64-inline-loader as a referenced dependency.

Bringing it all together

Sometimes a configuration file is a JavaScript or TypeScript file that imports dependencies, but also contains configuration that needs to be parsed by a plugin to find additional dependencies.

Let’s take a look at this example Vite configuration file:

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(async ({ mode, command }) => {
return {
plugins: [react()],
test: {
setupFiles: ['./setup-tests.ts'],
environment: 'happy-dom',
coverage: {
provider: 'c8',
},
},
};
});

This file imports vite and @vitejs/plugin-react directly, but also indirectly references the happy-dom and @vitest/coverage-c8 packages.

The Vite plugin of Knip will dynamically load this configuration file and parse the exported configuration. But it’s not aware of the vite and @vitejs/plugin-react imports. This is why such config files are also automatically added as entry files for Knip to statically resolve the import and require statements.

Additionally, ./setup-tests.ts will be added as an entry file.

ISC License © 2024 Lars Kappert