# Delete Code with Knip

One of my favorite things to do as a software developer is delete code. Nothing is more satisfying than deleting hundreds of thousands of lines of code that are no longer needed in your application. Even if it's only 5 lines of code, I still get all warm and fuzzy from it.

One of the benefits of removing code is you no longer have to maintain it. It's just gone. You'll never ask yourself, "Is this code still needed?" if it doesn't exist. Have you ever opened up your codebase and found some code written way too long ago and wondered if it was safe to remove? It's kind of like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698356850427/19808252-eabc-4efe-9467-e1b98aad7337.jpeg align="center")

> "Not sure if it's safe to remove this bit, so we'll leave it there just in case."  
> \- some developer

Now the magic question is, how do we find code to remove? Insert Knip

## What is Knip?

Now normally when you are browsing the <s>Twitter</s> 𝕏 developer crowd you typically end up with a bunch of hot takes, arguments over whose framework sucks, blah blah blah. But for every 50 hot takes, occasionally you will land on something useful posted on 𝕏. That's where I first heard about [**Knip**](https://github.com/webpro/knip). The creators describe it as:

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

Now this isn't the first tool of its kind. Many different languages and frameworks have similar tools to help you find unused code. The trick is helping **Knip** know more about your project so it can help you find all those magical lines of code just waiting to be deleted.

# An Existing Complex Project

For the rest of this article, I'll talk about the steps I've taken on a project at work to get it up and running using **Knip**. Due to the private nature of the project, I'll be focusing more on **Knip** and less on the decisions that were made on the project itself.

## Initial Setup and Run

First thing first, let's install it as a dev dependency, add it as a script in our package.json and just run it and see what happens.

Install Knip:

```powershell
npm install knip --save-dev
```

Updated our `package.json` scripts:

```json
{
  "scripts": {
    "knip": "knip"
  }
}
```

Now let's run it and see what happens:

```powershell
npm run knip
```

Now if you have any sizable project, once this finishes turning the numbers, you'll likely get a giant list of unused things to delete. Our project had so many things that my terminal in vscode wouldn't even show me the whole thing. It was massive.

## False Positives & Default Configuration

Skimming the list of what has been reported by **Knip**, I learned quickly that a huge amount of the findings were NOT safe to delete. In fact, tons of what was shown there were things I knew were being used everywhere. Let's check out the [default configuration](https://github.com/webpro/knip#default-configuration) for **Knip**.

A simplified example of their defaults from their documentation is:

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

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is a simplification of their defaults. There's a lot more to them that you can read <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/webpro/knip#entry-files" style="pointer-events: none">here</a>. They try very hard to automatically detect what frameworks and libraries you're using to help out. They have a very cool <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/webpro/knip#plugins" style="pointer-events: none">plugin system</a> that helps which we'll talk about later in this article.</div>
</div>

Our project at work is built with [Remix](https://remix.run/). If you've used that framework before you'll immediately know that Remix has several entry files: "root.tsx", "entry.client.tsx" and "entry.server.tsx" files along with every single route file.

Fortunately, **Knip** has several plugins that get automatically detected for us and used to make it smarter for understanding your code. It even has a [plugin for Remix](https://github.com/webpro/knip/tree/main/src/plugins/remix).

Their Remix plugin will be enabled when it finds any dependencies or dev dependencies that include `@remix-run` in the name. If it detects Remix, it will add a whole load of entry files:

```json
{
  "remix": {
    "entry": [
      "remix.config.js",
      "remix.init/index.js",
      "app/root.tsx",
      "app/entry.{client,server}.{js,jsx,ts,tsx}",
      "app/routes/**/*.{js,ts,tsx}",
      "server.{js,ts}"
    ]
  }
}
```

This is fantastic if your project doesn't modify any of the default conventions in Remix, it should just work. But what do you do if you've heavily modified your Remix routing conventions? Well, you're in luck because our project does exactly that.

## Disabling Built-In Plugins

Our project at work customizes how routing works in Remix because of its scale and product requirements. Typically with Remix, you'll see an "app" folder that contains other folders like "components", "routes", etc. We've customized our routing to be split into logical modules that our product team has defined. You can think of these are features customers can turn on and off depending on what they pay for. We also have a few modules that are dedicated to authentication, core sections that all customers get, and even shared components that are used across multiple modules.

So an example of our file structure may look roughly like this:

```plaintext
app
├── auth
│   ├── helpers
│   ├── routes
│   └── services
├── core
│   ├── components
│   ├── helpers
│   └── routes
├── module-1
│   ├── components
│   ├── helpers
│   ├── routes
│   └── utils
├── module-2
│   ├── components
│   └── routes
└── shared
    └── compoents
```

As you as see, the Remix plugin for Knip isn't exactly what we want, so we need to disable it and configure new entry points for our application. We'll create a `knip.json` file in the root to use as our config. We'll also add a `"$schema"` value at the top to help vscode give us some intellisense.

```json
{
  "$schema": "https://unpkg.com/knip@2/schema.json",
  "remix": false,
  "entry": [
    "server.js",
	"remix.config.js",
	"app/root.tsx",
	"app/entry.{client,server}.tsx",
	"app/{auth,core,module-1,module-2}/routes/**/{index,_layout}.{ts,tsx}",
	"app/{auth,core,module-1,module-2}/routes/*.{ts,tsx}",
	"app/core/routes/__global-layout.tsx"
  ],
  "project": [
    "remix.config.js", 
    "server.js", 
    "module-routes.js", 
    "app/**/*.{tsx,ts}"
  ],
}
```

Let's walk through each section and give a quick summary of what we're doing here.

---

```json
  "remix": false,
```

This disables their built-in Remix plugin.

---

```json
  "entry": [
    "server.js",
	"remix.config.js",
	"app/root.tsx",
	"app/entry.{client,server}.tsx",
	"app/core/routes/__global-layout.tsx"
	"app/{auth,core,module-1,module-2}/routes/*.{ts,tsx}",
	"app/{auth,core,module-1,module-2}/routes/**/{index,_layout}.{ts,tsx}",
  ],
```

Here we've defined our new entry files. Most of this is similar to the Remix plugin until we get to the section about our modules and their routes. The other files should be easy to understand so I'll talk about the last 3.

`app/core/routes/__global-layout.tsx`

We have a global layout that has the primary navigation for our application that most modules use. We have configurations where an individual module can opt out of the global layout if they want to build some other kind of navigation or perhaps be used as an unauthenticated section.

`app/{auth,core,module-1,module-2}/routes/*.{ts,tsx}`

This entry includes any `ts` or `tsx` files that are in the root of our routes folder.

`app/{auth,core,module-1,module-2}/routes/**/{index,_layout}.{ts,tsx}`

This one is a bit more hairy without some context. To try not to bore you, I'll try to make this quick, we're using [Kiliman](https://github.com/kiliman)'s [remix-flat-routes](https://github.com/kiliman/remix-flat-routes) for our routing. We love being able to co-locate components, hooks, utilities, and any other files we need that are only used for a specific route. This helps us not bloat our other folders with code that is only used for a single page. Remix v2 by default uses a flat routes convention but we've opted into using Kiliman's flat routes library because of its ability to have "Nested folders" while still enjoying the flat files convention and co-locating our code.

Example using default flat routes with Remix:

```plaintext
routes
├── admin
├── admin._index
├── admin.users._index
├── admin.users.$userId._index
├── admin.users.$userId.edit
├── admin.users.create
├── admin.settings._index
├── some-route
├── some-route._index
├── some-route.nested-item._index
├── some-other
└── some-other._index
```

Imagine having other 50+ routes. This is where the "Nested folders" come in. You can simply add group routes in another folder and as long as it's suffixed with "+" it won't be treated as a flat route but a grouping of flat routes.

Example using remix-flat-routes with "Nested folders":

```plaintext
routes
├── admin+
│   ├── _index
│   ├── users._index
│   ├── users.$userId._index
│   ├── users.$userId.edit
│   ├── users.create
│   ├── settings._index
│   └── _layout.tsx
├── some-route+
│   ├── _index
│   ├── nested-item._index
│   └── _layout.tsx
└── some-other+
    ├── _index
    └── _layout.tsx
```

Now when I'm looking at our routes, I'm greeted with fewer folders that group my routes in logical ways.

---

```json
  "project": [
    "remix.config.js", 
    "server.js", 
    "module-routes.js", 
    "app/**/*.{tsx,ts}"
  ],
```

Our project root has lots of different folders. Several of those folders are strictly for our QA team for them to write automation that is separate from our application. So we're defining all the files we want Knip to check. We've got 3 root files then everything else is every file under our "app" directory.

Let's run Knip again and see if our list is any better. Spoiler alert, it's not yet. We have a MASSIVE list still, many falling under `Unused exported types` or `Unused exported types in namespaces`.

## Unused Exported Types

397 unused exported types. That's a lot of "unused" exported types. Let's first talk about what this is, why my project has so many of them, and what we can do about it.

### What is an unused exported type?

Hopefully, the phrase speaks for itself, but it is a TypeScript `type` or `interface` that is being exported, but is never imported into another file.

```typescript
export type FavoriteColor = 'red' | 'blue' | 'green'

export interface Person {
  name: string
  age: number
  favoriteColor: FavoriteColor
}

/** 
 * Some other code in the same file, that may or may not reference 
 * our `FavoriteColor` or `Person` types. Note no other files 
 * reference these 2 types.
*/
```

If nothing in your code base is importing these types, then they will be listed by knip.

### Why does my project have so many of these?

Our project has a pattern for how we create React components. Here's an example below.

```typescript
export interface ButtonProps {
  // button props here
}

export const Button = (props: ButtonProps) => {
  // return button here
}
```

When we make a component, we export the component itself and that gets used throughout our application. Along with each component, we define an interface with its properties. We also export this type, in cases where another component that consumes it, wants to use its prop type.

After you've built hundreds of components like this, it makes sense why knip has such a large list of unused exports. Now what to do about it?

### What are my options to remove these unused exported types?

The obvious answer is to stop exporting it. Boom problem solved. But that wasn't a solution that I was happy with because I'm a stubborn developer and I exported the type for a reason. That reason was "just in case" a consumer wanted to use it.

So now what? Luckily there are some [configuration options](https://github.com/webpro/knip#ignore-exports-used-in-file) we can use with Knip.

```json
{
  "ignoreExportsUsedInFile": {
    "interface": true,
	"type": true,
	"enum": true
  },
}
```

You can either set `ignoreExportsUsedInFile` for everything by setting that property to `true` or you can opt into certain things being ignored. As you can see from the example above. We've decided to ignore exports used in files for interfaces, types and enums.

# Conclusion

We've made a lot of progress in getting Knip up and going on a fairly large project. We still have some more work to go but we're already starting to see some benefits and finding lots of unused code. You know what unused code means... I get to delete it!
