Writing A Plugin
Introduction
In this tutorial we are going to write a new plugin for Knip. We’ll be using “Cool Linter” as the example tool we create the plugin for.
This document also serves as a reference to each of the exported values.
Scaffold a new plugin
The easiest way to create a new plugin is to use the create-plugin
script:
This adds source files, and a test and fixtures to get you started. The main source file contains comments for some guidance as well.
If there’s a plugin similar to what you need, by all means, copy from that plugin implementation, tests and fixtures.
Exports
This section describes each exported value that the generator has pre-defined at
src/plugins/cool-linter/index.ts
. In many cases, writing a plugin is much like
filling in the blanks. Everything that is not used or empty can be removed.
NAME
The name of the plugin to display in the list of plugins and in debug output.
ENABLERS
An array of strings and/or regular expressions that should match one or more
dependencies so the isEnabled
function can determine whether the plugin should
be enabled or not. This is often a single package name, for example:
isEnabled
This function can be fairly straightforward with the hasDependency
helper:
This will check whether a match is found in the dependencies
or
devDependencies
in package.json
. When the dependency is listed in
package.json
, the plugin will be enabled.
Notes
In some cases, you might want to check for something else, such as the presence
of a file or a value in package.json
. You can implement any (async
) function
and return a boolean. Here is the function signature for
IsPluginEnabledCallback
.
CONFIG_FILE_PATTERNS
The Plugins page describes config
files. Their default value is what we
define as CONFIG_FILE_PATTERNS
here in the plugin.
This means we need to define and export this variable, so Knip can find the configuration file:
And here’s how we can define this config file pattern from the plugin:
For each configuration file with a match in CONFIG_FILE_PATTERNS
, the
findDependencies
function will be invoked with the file path as the first
argument. There should usually be just one match (per workspace).
Note
Configuration files may end with .js
or .ts
, such as
cool-linter.config.js
. The default export of these files will be handled by
the findDependencies
function we will define in our plugin (the plugin loads
the file dynamically). Since these files may also require
or import
dependencies, Knip also automatically adds them to ENTRY_FILE_PATTERNS
(for static analysis). Also see Plugins → Bringing It All Together for an
example.
findDependencies
The findDependencies
function should do three things:
- Load the provided configuration file.
- Find dependencies referenced in this configuration.
- Return an array of the dependencies.
Let’s look at an example. Say you’re using the Cool Linter tool in your project. Running Knip results in some false positives:
This is incorrect, since you have cool-linter.config.json
that references
those dependencies!
This is where our new plugin comes in. Knip will look for
cool-linter.config.json
, and the exported findDependencies
function will be
invoked with the full path to the file.
Notes
- Knip provides the
load
helper to load most JavaScript, TypeScript, JSON and YAML files. - The exported function should be wrapped with
timerify
, so Knip can gather metrics when runningknip --performance
. By default it just returns the function without any overhead.
ENTRY_FILE_PATTERNS
Entry files are added to the set of entry
files of the source code. This means
that their imports and exports will be resolved, recursively. Plugins include
various types of entry files:
- Plugins related to test frameworks should include files such as
*.spec.js
. - Plugins for frameworks such as Next.js or Svelte should include files like
pages/**/*.ts
orroutes/**/*.svelte
. - Another example is Storybook which includes entry files like
**/*.stories.js
. - The Next.js plugin does not need
CONFIG_FILE_PATTERNS
withfindPluginDependencies
. Yet it does havenext.config.{js,ts}
inENTRY_FILE_PATTERNS
, since that file mayrequire
orimport
dependencies.
In production mode, these files are not included. They are included only in the default mode.
Cool Linter does not require such files, so we can remove them from our plugin.
PRODUCTION_ENTRY_FILE_PATTERNS
Most files targeted by plugins are files related to test and development, such
as test and configuration files. They usually depend on devDependencies
.
However, some plugins target production files, such as Next.js, Gatsby and
Remix. Here’s a shortened example from the Remix plugin:
In production mode, these files are included (while ENTRY_FILE_PATTERNS
are not). They’re also included in the default mode.
Cool Linter does not require this export, so we can delete this from our plugin.
PROJECT_FILE_PATTERNS
Sometimes the source files targeted with project
patterns may not include the
files related to the tool of the plugin. For instance, Storybook files are in a
.storybook
directory, which may not be found by the default glob patterns. So
here they can be explicitly added, regardless of the user’s project
files
configuration.
Most plugins don’t need to set this, since the default configuration for
project
already covers these files.
Cool Linter does not require this export, so we can delete this from our plugin.
Tests
Let’s update the tests to verify our plugin implementation is working correctly.
-
Let’s save the example
cool-linter.config.json
in the fixtures directory. Create the file in your IDE, and save it atfixtures/plugins/cool-linter/cool-linter.config.json
. -
Update the test:
This verifies the dependencies in
cool-linter.config.json
are correctly returned to the Knip program. -
Run the test:
If all went well, the test passes and you created a new plugin for Knip! 🆕 🎉
Documentation
The documentation website takes care of generating the plugin list and the individual plugin pages.
Wrapping Up
Thanks for reading. If you have been following this guide to create a new plugin, this might be the right time to open a pull request!
ISC License © 2024 Lars Kappert