iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

Knipでプロジェクトの不要コードを検出してみた

はじめに

こんにちは、保田です。 普段の業務ではClaude Codeを活用した開発を行っています。

AIコーディングによってコードの生成スピードは上がる一方で、不要なコードの蓄積を防ぐことが重要になっていると感じています。 使われなくなった関数やexportされたままの型定義、package.jsonに残った未使用の依存関係など、動作には影響しないものの放置するとコードベースが徐々に肥大化していってしまいます。

こうした不要コードをプロジェクト全体から検出・削除したいと思い、今回はKnipを試してみたので、その機能と使い方を紹介します。

https://knip.dev/

不要コードが蓄積する問題

開発が進めば進むほど、不要なコードは自然と増えていくと思います。例えば、以下のようなケースが考えられます。

  • 古い型定義がexportされたままになっている
  • 依存パッケージを試しにインストール後に、結局使わなかった
  • なにかしらの機能を削除後に関連するユーティリティファイルが残った

AIコーディングでは以前と比べてコード生成の頻度が上がるため、不要コードの発生頻度も高くなっていると感じます。 AIエージェントに機能追加を依頼すると、汎用的なヘルパー関数をexport付きで生成することがあります。 あとで使うかもしれないという意図で作られた関数が、結局一度も使われないまま残ることもあります。 さらに、複数回の修正を重ねるうちに元の関数が不要になっても、exportだけが残り続けるケースなどもあるかと思います。

不要コードが蓄積することによって何が問題になるかというと、以下のような点があると思います。

  • コードベースが肥大化していく
  • コードレビューのノイズになる
  • 誰かが誤って不要コードを呼び出してしまうリスクがある

こうした問題に対して、プロジェクト全体から一括で不要コードを検出・削除するツールがKnipです。

Knipとは

KnipはJavaScript、TypeScriptプロジェクトの不要なファイル・依存関係・exportを検出し、自動修正するツールです。

調べてみたところ、名前の由来はオランダ語でハサミを意味しているそうです。 不要なコードをカットすることから、ロゴもハサミのデザインになっているみたいです。

主な特徴は以下の通りです。

  • 特別な設定が不要(基本的にはデフォルト設定のまま使用可能)
  • プラグインによって、ESLint、Vitestなどを自動で検出してくれる
  • --fixで不要コードを一括削除も可能

検出の仕組み

Knipはエントリポイント(index.ts等)を起点に、importを再帰的に辿ってプロジェクト全体のモジュールグラフを構築します。 このグラフに含まれないファイルやexportを未使用と判定する仕組みとなっています。

ESLintにno-unused-varsという設定もありますが、こちらはファイル単位の解析になるため、exportされている変数は「使われている」と見なされますが、Knipはモジュールグラフ全体を見ているため、どこからもimportされていないexportを未使用として検出することができます。

検出対象

Knipが検出できる不要コードの主な対象は以下のとおりです。

  • 未使用ファイル
  • 未使用依存関係
  • 未使用export
  • 未使用の型export
  • 未使用enumメンバー

詳細は公式ドキュメントを参照してください。 https://knip.dev/reference/issue-types

導入方法

インストール

以下のコマンドでKnipの設定ファイル作成とインストールが一度に完了します。

npm init @knip/config

このコマンドを実行すると、以下の処理が自動で行われます。

  • knipパッケージがdevDependenciesに追加される
  • package.json"knip": "knip"スクリプトが追加される
  • knip.json(設定ファイル)が作成される

初回実行

npm run knip

Knipがプロジェクトを解析し、未使用のファイル・依存関係・exportを一覧で出力してくれます。 出力が多い場合は--max-show-issuesで表示数を制限すると見やすくなりました。例えば、最大5件以上は表示しないようにするには以下のようにします。

npm run knip -- --max-show-issues 5

設定ファイル

entryとproject

Knipの設定にはentryprojectという2つの概念があります。

  • entry: Knipがモジュールグラフの探索を開始する起点ファイル。ここからimportを辿って依存関係を解決している
  • project: 未使用ファイルのチェック対象範囲。この範囲に含まれるファイルのうち、entryから辿れなかったものが「未使用ファイル」として報告される

デフォルトではentryindex.tssrc/index.tsmain.tscli.ts等が、project**/*.{js,ts,...}が設定されています。 さらにプラグインが自動でentryを追加するため(後述)、多くのプロジェクトでは設定なしで動作するようです。

プラグインによる自動検出

Knipの大きな特徴は、package.jsonの依存関係やプロジェクトの構成ファイル(vite.config.ts等)から使用ツールを自動検出し、適切なエントリポイントを追加してくれることです。

例えば、vitestが依存に含まれていれば、Vitestプラグインが自動で有効になり、**/*.{test,spec}.tsがエントリファイルとして追加されます。テストファイルを手動でentryに追加する必要はありません。

プラグインは設定ファイルの中身も解析します。例えば、vite.config.tsplugins: [react()]と書かれていれば、@vitejs/plugin-reactが使用されていると認識し、未使用依存関係として誤検知しないようになっています。

プラグインは100種類以上用意されており、Vite、Next.js、ESLint、Storybook、Playwright等の主要なツールをカバーしているようです。

カスタム設定が必要なケース

ほとんどのプロジェクトではプラグインの自動検出により設定なしで動作することが多いですが、 デフォルトでカバーされないエントリポイントがある場合はカスタム設定が必要になります。

例えば、Viteで構築したChrome拡張のプロジェクトでは、コンテンツスクリプトとバックグラウンドスクリプトの2つのエントリポイントがあります。

src/
├── index.tsx              ← コンテンツスクリプト(デフォルトのentryで検出される)
├── backgrounds/
│   └── index.ts           ← バックグラウンドスクリプト(デフォルトでは検出されない)
├── components/
├── utils/
└── constants/
    └── data/*.json        ← データファイル(ソースコードではない)

デフォルトのentryではsrc/index.tsxは検出されますが、src/backgrounds/index.tsは検出されません。そのため、バックグラウンドスクリプトから辿れるコードが未使用として誤検知されてしまいます。 以下はReact + Vitest + ViteでChrome拡張を開発しているプロジェクトの場合の例です。

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

各設定についてそれぞれ解説します。

  • entryには、デフォルトでは検出されないバックグラウンドスクリプトの追加をしています
  • projectには、チェック対象をsrc/**/*.{ts,tsx}types/**/*.tsに絞っているため、constants/data/*.jsonのようなデータファイルは対象に含まれません

主なCLIオプション

よく使うフラグを整理します。

オプション 説明
--fix 検出した問題を自動修正する
--fix --allow-remove-files ファイルの削除も許可して自動修正する
--fix-type <type> 特定の種類のみ修正する(exports, dependencies, types, files
--fix --format 修正後にフォーマッターを実行する
--production 本番コードのみを対象に解析する(テスト・設定ファイルを除外)
--include <type> 特定の種類のみレポートする
--exclude <type> 特定の種類をレポートから除外する
--reporter json JSON形式で出力する
--reporter compact コンパクトな形式で出力する
--cache キャッシュを有効にする(連続実行が10〜40%高速化)
--watch ファイル変更を監視して継続的にレポートする

--fix-typeの使い分け

すべてを一度に修正するのではなく、種類を指定して段階的に修正することもできます。

# exportの修正だけを行う
npx knip --fix-type exports,types

# 依存関係の修正だけを行う
npx knip --fix-type dependencies

# ファイル削除を含むすべての修正を行う
npx knip --fix --allow-remove-files

VS Code拡張機能

VS Code向けの拡張機能も提供されているので使用してみました。 未使用のexportがエディター上に赤い波線で表示されるようになったり、ホバーでどこからimportされているかを一覧で確認できるようになったりと、開発中に不要コードを把握できるようになるので、便利だと感じました。

https://marketplace.visualstudio.com/items?itemName=webpro.vscode-knip

実際のプロジェクトで使ってみた結果

実際に業務で開発しているプロジェクトに対してKnipを使用してみました。

検出結果

カテゴリ 件数
未使用ファイル 20
未使用dependencies 10
未使用devDependencies 6
未リスト依存(Unlisted) 2
未使用export(関数・定数) 46
未使用型定義 61
未使用enumメンバー 8
重複export(named + default) 10

除外設定がされていないものもまだ含まれているため、誤検知も含まれている可能性がありますが、全体的に不要コードが多く蓄積していたことがわかりました。

検出された問題の傾向

実際に検出された問題を見てみて感じた傾向を紹介します。

ビルドツール移行時の残骸

webpackからViteに移行した際に、Babel関連パッケージ(@babel/core@babel/preset-env等)がpackage.jsonに残ったままでした。 ビルドは正常に動作するため気づきにくいですが、npm installのたびに不要なパッケージがインストールされ続けていたことになります。

未使用の型定義が最多

最も多かったのが未使用の型定義でした。 型定義は実行時に影響しないため、不要になっても気づきにくい傾向があります。 また、型定義ファイルごと未使用になっているケースもありました。

誤検知について

Knipの精度は高いですが、エントリポイントの設定や、プロジェクトの構成によっては誤検知もあるかと思いますので必ず検出結果を確認し、問題ない項目だけを修正するのが安全だと感じました。

Claude Codeにデッドコードの洗い出しをしてもらうのとどう違うのか?

Claude Codeにデッドコードを探してもらえばいいのでは?と思うこともあるかもしれません。 Claude Codeには一度に読み込めるコード量にも上限はありますし、プロンプトや文脈によって毎回結果が変わるので、どうしてもブレがあります。 実際に使用されているコードを未使用とすることもあるかもしれません。

実際に試してみてもKnipより検出範囲が狭い結果になりました。また、毎回同じ結果になるわけでもありません。 Knipで機械的に検出できるものはKnipに任せ、Claude Codeはそれでは拾えないコードの意味を理解した上での判断に活用する、という使い分けが効果的だと思っています。

まとめ

AIコーディングによってコードの生成スピードが上がった分、不要コードの蓄積も以前より速くなっています。 定期的にKnipを実行してコードベースを整理する習慣をつけておくと、プロジェクトの健全性を保ちやすくなると感じました。

導入も簡単で不要コードの検出・削除を組み込むこともできるので、 不要コードの蓄積が気になっている方はぜひ試してみてください。

ここまで読んでくださってありがとうございます!

弊社ではエンジニアを募集しております!少しでもご興味がありましたら、カジュアル面談でお話ししましょう!

iimon採用サイト / Wantedly