ごぼうのブログgobo-cello

使われなくなったコードを、Knip で検出する

2026-08-17

tech

Knip とは

Knip は、JavaScript / TypeScript プロジェクトから「もう使われていないもの」を見つけてくれる lint ツールだ。エントリーポイントから import graph を辿り、そこから到達できないものを機械的に洗い出す。

# 対話式セットアップ
npm init @knip/config
 
# 手動セットアップ
npm install -D knip typescript @types/node
npm run knip

報告される issue には、次のような種類がある。

issue の種類検出対象
Unused filesどこからも参照されていないファイル
Unused exportsどこからも参照されていない export(enum member や namespace member も含む)
Unused dependenciespackage.json にはあるが、コード内で未使用の依存関係
Unlisted dependencies / binariesコードでは使っているが、package.json に未記載の依存関係・バイナリ
Unresolved imports解決できない import 指定子
Duplicate exports同じものを複数の名前で export している状態
Circular dependenciesファイル間の循環参照(デフォルトは警告レベル)

entry と project という2つの概念

Knip の設定は entryproject という2つの概念を中心に組み立てられている。

設定役割
entryKnip が import graph を辿り始める起点(起動ファイル、CLI、設定ファイル、生成スクリプトなど)
project解析対象のスコープ

entry は、Knip が import graph を辿り始める起点となるファイルである。project は、解析対象となる source ファイルのスコープを定義する。Knip は project のスコープの中で entry を起点に import graph を辿り、到達できなかったものを Unused として報告する。

2つのモード:production / default

Knip は、entryproject の解析対象を production mode と default mode の2つで切り替えられる。

  • production mode: ビルド成果物や配布物に含まれ、本番環境で実際に実行されるコードだけに対象を絞って解析する
  • default mode: production mode の対象に加えて、テストコードなど開発時に存在するコードも含めて解析する
flowchart TB
  subgraph defaultMode["default mode"]
    direction TB
    test["テストコード"]
    subgraph productionMode["production mode"]
      code["本番コード"]
    end
  end

production mode の利点は、テストからは参照されているが本番では使用されていないコードを dead code として検出できることにある。

実例

次のような、呼び出し元 parent.ts が削除されたのに、呼び出される側 child.ts が消し忘れられたケースを考える。

src/
├── parent.ts        # このファイルを削除した
├── child.ts         # 本来であればこのファイルは未使用になるはず
└── child.test.ts

child.test.ts からの import があるので、default mode では child.ts は reachable と判定されてしまう。

flowchart LR
  Parent["parent.ts(削除済み)"] -.->|"かつての呼び出し"| Child["child()\n本番では未使用"]
  Test["child.test.ts"] -->|"import"| Child

  classDef unreachable stroke-dasharray: 5 5
  class Parent,Child unreachable

一方 production mode においては、child.ts は本番コードとしてはどこからも呼び出されておらず、dead code として検出できる。

default mode は開発経路も含めた全体の dead code を、production mode は本番で実際に実行されるコードに閉じた dead code を検出する。CI では両方を回して、それぞれの役割を分担させる。

設定ファイルの書き方

設定は、プロジェクトルートの knip.ts に書く。

knip.jsknip.json も設定ファイルとして使える。

!patternpattern!

entry / project の対象から除外したい pattern には、先頭に ! を付けて書く。

entry / project の対象を production mode だけに絞りたい pattern には、末尾に ! を付けて書く。

patternproduction modedefault mode
pattern対象対象
!pattern対象外対象外
pattern!対象対象外
!pattern!対象外対象

実例

次のような構造を考える。

.
├── dist/                # ビルド成果物
└── src/
    ├── main.ts          # entry
    ├── *.ts
    ├── *.test.ts        # test entry
    └── test-helpers/

このとき、knip.ts は次のように書ける。

knip.ts
export default {
  entry: [
    'src/main.ts',
    '!src/**/*.test.ts!'
  ],
  project: [
    'src/**/*.ts',
    '!src/**/*.test.ts!',
    '!src/test-helpers/**!',
    '!dist/**'
  ],
}

ビルド成果物である dist/ は Knip で解析する必要がないので、'!dist/**' のように設定し、解析から除外する。

テストファイルである src/**/*.test.ts は、default mode のみの解析対象とするため、entryproject の両方に '!src/**/*.test.ts!' を設定する。entry 側だけでは、production mode でもテストファイルが project の対象に残ったままになり、どの entry からも到達できないファイルとして誤って Unused files 扱いされてしまう。project 側にも同じ pattern を設定することで、テストファイルを default mode では解析対象としつつ、production mode の対象からは丸ごと外す、という意図を表せる。

なお、アプリケーション開発において pattern! を単独で使う場面は少ない。ライブラリ開発の場合は、ライブラリの公開エントリを src/index.ts! のように設定する。

plugin が entry を自動で見つけてくれる

ここまでは entry / project を手で書く前提で見てきたが、実際には全部を手で書く必要はない。Knip は zero config、つまり設定ファイルなしでも動くことを目指している。package.json の依存関係を見て対応する plugin を自動的に有効化し、そのツール固有の設定ファイルや規約を理解した上で、実行経路に応じた entry を自動で足してくれる。

plugin認識するもの
TypeScripttsconfig.jsoninclude / paths
ESLintconfig ファイルと plugin の依存関係
Next.jspages / app ディレクトリの規約に基づく entry
Vitest / Jestテストファイルと setup ファイルの entry
Storybookstory ファイルの entry
Playwrighte2e テストの entry
GitHub Actionsworkflow が参照するスクリプト

対応 plugin は100 以上あり、依存関係を追加すれば大半は設定なしで拾われる。構成が一般的なプロジェクトなら、knip.ts を書かなくても十分なことも多い。一覧は公式ドキュメントの Plugins ページで確認できる。

References