Knipで行うコードメンテナンス 不要なコードの把握と片付け方

JavaScript/TypeScript向けの不要なコードを検出するツールKnipを取り上げ、デモプロジェクトで実際の出力を確認しながら、Knipが「未使用」をどう判断するのか、報告を信頼できる状態にする設定と、コーディングエージェントとの組み合わせ方までを説明します。

発行

著者 中村 享介 モダンフロントエンド・エンジニア
Knipで行うコードメンテナンス シリーズの記事一覧
無料公開中
公開翌日4時まで

はじめに

機能の削除やリファクタリングを繰り返したリポジトリには、もう使われていないコードが少しずつたまっていきます。どこからもimportされないファイル、呼び出されなくなったexport、package.jsonに残ったままの依存パッケージ。これらはアプリケーションの動作を壊さないため、意識して探さない限り見つかりません。

Knip(クニップ)は、JavaScript/TypeScriptプロジェクトを解析して、使われていないファイル・export・依存関係を報告するツールです。この記事では、小さなデモプロジェクトで実際の出力を確認しながら、Knipが「未使用」をどう判断するのか、報告を信頼できる状態にするには何を設定すべきかを説明します。併せて、コーディングエージェントと組み合わせた使い方も紹介します。

なお、この記事はKnip 6.29.0、Node.js 24.16.0で検証しています。

使われていないコードも保守対象

使われていないコードが残っていても、アプリケーションは動きます。壊れないのだから急いで消す必要はない、と考えたくなります。

では、何が問題なのでしょうか。それは、コードが存在する限り、読む人の判断対象になり続けることです。リポジトリを読む人からは、そのコードが本当に不要なのか、特定の条件でだけ使われるのかを簡単には区別できません。

たとえば、古い機能のために作られたファイルが残っていたとします。事情を知らない人がそれを読めば、新しい実装でも参考とすべきコードに見えるでしょう。使われていない依存パッケージも、package.jsonにある限り、脆弱性情報やアップデートの確認対象になります。検索、レビュー、依存関係の更新。どの作業でも、不要なコードは候補に入り込んできます。

AIを使った開発では、この影響が強く現れます。コーディングエージェントは、関連しそうなファイルを検索・読解しながら、限られたコンテキストの中で実装を判断します。不要なコードが検索結果に混ざれば、その分だけトークンを消費します。使われなくなった古いAPIを現在の設計方針だと誤解すれば、変更内容そのものにも影響するでしょう。

参照のグラフから未使用を探す

使われていないコードは、参照関係をたどれば見つけられます。Knipはまず、アプリケーションやツールの起点となるentryファイルを特定します。そこからimportをたどってファイル同士のつながりを調べ、プロジェクト全体のモジュールグラフを構築します。

このグラフの上で、どのentryからも到達できないファイルは未使用ファイルになります。ほかのファイルから参照されないexportは未使用export、到達可能なコードから使われないパッケージは未使用依存です。仕組みの詳細は公式ドキュメントのHow Knip worksにまとまっています。

未使用が判定される仕組み

entry
  └─ import
      ├─ 使用中のファイル
      │   ├─ 使用中のexport
      │   └─ 使用中の依存パッケージ
      └─ 使用中のファイル

どのentryからも到達できない
  └─ 未使用ファイル

つまりKnipのいう「未使用」とは、このグラフの上で参照を確認できなかった、という意味です。ですから、実行時に文字列からパスを組み立てる動的importや、フレームワーク独自の規約で読み込まれるファイルは、自動ではグラフに入らないことがあります。

Knipは多くのフレームワークや開発ツールに対応したプラグインを備えていますが、プロジェクト固有の読み込み方まで、すべて推測できるわけではありません。そのため、Knipの報告は削除の指示ではなく、確認すべき候補の一覧として扱います。

なお、Knipが対象とするのはファイル間の参照関係です。同じファイル内の未使用変数や未使用importは、ESLintoxlintといったリンターの領域です。筆者はoxlintと組み合わせて、ファイルの中はリンター、ファイルの間はKnipという分担で使っています。

プロジェクトに追加せずに試す

ここからは、意図的に不要なコードを含めたデモプロジェクトで、実際の出力を確認します。

デモの取得と実行

git clone https://github.com/codegrid/2026-knip.git
cd 2026-knip
npm ci
npm start

デモは、環境変数で言語を切り替える小さなJavaScriptプログラムです。npm startを実行すると、次のように表示されます。

npm startの出力

こんにちは、CodeGrid!

ソースコードの構成は次のとおりです。

デモのディレクトリ構成

src/
├── debug.js
├── greeting.js
├── index.js
└── locales/
    └── ja.js

個々のファイルの参照を確認しておきます。

src/index.jsでは、環境変数からロケール名を取得し、対応するファイルを動的にimportしています。

動的import(src/index.js)

import { createGreeting } from './greeting.js';

const locale = process.env.LOCALE ?? 'ja';
const { greetingPrefix } = await import(`./locales/${locale}.js`);

console.log(createGreeting(greetingPrefix, 'CodeGrid'));

src/greeting.jsには、使用中の関数に加えて、以前の実装を想定した使われていない関数があります。

使用関数と未使用関数の混在(src/greeting.js)

export function createGreeting(prefix, name) {
  return `${prefix}、${name}!`;
}

export function createLegacyGreeting(name) {
  return `Welcome, ${name}.`;
}

src/debug.jsは、ターミナルの文字を装飾するpicocolorsを使っていますが、どのファイルからも読み込まれていません。

未使用ファイル(src/debug.js)

import pc from 'picocolors';

export function debug(message) {
  console.log(pc.dim(`[debug] ${message}`));
}

Knipはプロジェクトの依存に追加しなくても、npxで実行できます。試すだけならpackage.jsonを変更する必要はなく、継続的に使うと決めた段階で開発依存("devDependencies")へ追加すればよいでしょう。

npxでKnipを実行

npx knip

デモでは次のように、未使用と判定されたものが報告されます。

設定前のKnipの報告

Unused files (2)
src/debug.js
src/locales/ja.js
Unused dependencies (1)
picocolors  package.json:13:6
Unused exports (1)
createLegacyGreeting  function  src/greeting.js:5:17

さきほど見たようにsrc/debug.jsはどこからもimportされていないため、未使用ファイルです。picocolorssrc/debug.jsの中で使われていますが、そのファイル自体がentryから到達できないため、未使用依存として報告されます。到達可能なsrc/greeting.jsにあるcreateLegacyGreetingは、どこからもimportされていないので未使用exportです。

問題はsrc/locales/ja.jsです。このファイルはnpm startで実際に使われ、「こんにちは」の出力元になっています。それでも未使用と報告されたのは、importするパスが実行時に組み立てられていて、Knipが読み込み先のファイルを特定できなかったためです。

誤検出はKnipへの情報不足を示す

実際は使われているファイルが未使用として報告されると、誤検出として無視したくなります。しかし、この報告はKnipのグラフに情報が足りていないというシグナルです。放置すると、本当に不要なファイルが同じ一覧に現れたとき、誤検出と区別できなくなります。

動的に読み込まれるロケールファイルを解析の起点として伝えるため、プロジェクトのルートにknip.jsonを作成します。ここに起点となるファイルを設定すると、そのファイルは「ほかから参照されているか」を確かめる対象からは外れます。

この仕組みを利用して、動的importで追いきれなかったsrc/locales/ja.jsを設定すれば、未使用とは判定されなくなります。

起点の設定(knip.json)

{
  "entry": ["src/locales/*.js"]
}

ここで1つ注意があります。設定ファイルにentryを書くと、src/index.jsなどに一致するデフォルトのentryパターンは、追加ではなく上書きされます。それでもこのデモでsrc/index.jsが起点として扱われるのは、package.jsonstartスクリプト(node src/index.js)からKnipが参照を検出するためです。こうした自動検出が働かない起点を持つプロジェクトでは、既存の起点も合わせてentryに列挙します。

もう一度実行すると、実際に不要な候補だけが残ります。

entry設定後のKnipの報告

Unused files (1)
src/debug.js
Unused dependencies (1)
picocolors  package.json:13:6
Unused exports (1)
createLegacyGreeting  function  src/greeting.js:5:17

entryは一致するファイルすべてを使用中の起点と宣言する設定でもあります。将来ロケールファイルのどれかが不要になっても、Knipはそれを未使用ファイルとして報告しません。

検出ファイルの設定

設定の中心になるのはentryprojectです。entryは参照をたどり始める起点、projectは未使用かどうかを調べるファイルの範囲を表します。デフォルトの挙動で足りないところだけを書くのが基本です。

依存パッケージ側にも、同じ種類の誤検出があります。設定ファイル経由でしか参照されないパッケージです。たとえば筆者は、記事の原稿を管理するリポジトリでMarkdownの校正にtextlintを使っています。そこでnpx knipを実行すると、次のように報告されます。

設定ファイル経由の参照が誤検出される例

Unused devDependencies (2)
textlint-rule-preset-ja-technical-writing  package.json:4:6
textlint-rule-prh                          package.json:5:6

npm scriptsから実行されるtextlint本体は使用中と判定される一方、ルールパッケージへの参照は.textlintrc.jsonの中にしかありません。Knip 6.29.0にはtextlint向けのプラグインがないため、この参照はグラフに入らないのです。このようにプラグインで補えない参照は、ignoreDependenciesで明示的に除外します。

依存の除外(knip.json)

{
  "ignoreDependencies": [
    "textlint-rule-preset-ja-technical-writing",
    "textlint-rule-prh"
  ]
}

対処の順序も決めておきましょう。最初にentryprojectの不足を疑い、それでも表現できない例外だけをignoreDependenciesignoreに移します。予想外の報告をすべてignoreへ入れてしまうと、設定不足と本物の未使用コードを区別する手段がなくなります。

困ったときの対処は、公式ドキュメントのHandling Issuesが参考になります。

自動修正は設定が整ってから使う

報告される内容に納得できたら、Auto-fix機能で修正を自動化できます。

未使用コードの自動修正

npx knip --fix

デモで実行すると、picocolorspackage.jsonから削除され、createLegacyGreetingからexportキーワードが外れます。未使用ファイルの削除まで許可する場合は、--allow-remove-filesを付けます。

ファイルの削除まで許可する

npx knip --fix --allow-remove-files

自動修正はGitの作業ツリーをきれいにしてから実行し、差分をレビューします。依存を削除したときは、パッケージマネージャーでのlockfileの更新も忘れずに。設定が固まったら、CIへの組み込みも検討するとよいでしょう。Knipは報告があると終了コード1で終わるため、新しい不要コードの混入をプルリクエストの段階で検知できます。

補足:終了コードとは

終了コードは、コマンドが終了するときに返す数字で、処理の成否を機械に伝えます。慣習として0は正常終了、0以外(1など)は異常ありを意味します。CIはこの値を見て、0ならチェック通過、0以外なら失敗と判断します。Knipは未使用コードを報告すると1、何もなければ0を返します。

AIには探索より判断を任せる

冒頭で、AIを使った開発では不要なコードの影響が大きくなると書きました。その削除作業は、どう進めるのがよいのでしょうか。

Knipのないプロジェクトで、コーディングエージェントに「このリポジトリから不要なコードを探して削除して」と依頼すると、エージェントは多数のファイルを読み込んで、参照の有無を推測することになります。参照関係の探索は、設定を整えたKnipなら機械的に、正確にこなせる仕事です。Knipが使えるなら、依頼は「Knipを使って不要なコードを探して削除して」という一言で足ります。エージェントはnpx knipを実行し、報告が誤検出でないかを確かめ、不要と判断したコードを削除して、テストやLintの再実行まで進めてくれます。knip.jsonが設定済みのプロジェクトなら、開発中に見つけた不要なコードを、変更のついでに片付けてくれることも期待できるでしょう。

Knipには「将来のために残している」「外部から使われる公開APIである」といった設計上の意図まではわかりません。探索はKnipが担い、候補の意図や削除範囲の判断をAIと人間が引き受けます。この分担なら、リポジトリ全体を繰り返しAIに読ませるより、コンテキストとトークンの消費を抑えられます。

また、Knip自体の設定づくりもAIに任せられる作業です。筆者の場合、entryignoreDependenciesはコーディングエージェントに書かせて、レビューだけをしています。設定ファイルの用意に手間がかからなくなったことで、導入のハードルは以前より下がっています。

まとめ

Knipは、entryから構築したモジュールグラフを根拠に、未使用のファイル・export・依存を報告します。動的importや設定ファイル経由の参照はグラフに載らないことがあるため、誤検出を設定不足のシグナルとして拾い、entryignoreDependenciesで補っていきます。この積み重ねが、報告を信頼できるものにします。

まずは手元のプロジェクトでnpx knipを実行してみましょう。不要なコードとは無縁だと思っていたプロジェクトでも、いくつか候補が見つかるのではないでしょうか。見つかった候補を1つずつ判断していけば、それがそのままKnipの設定を整える作業になります。