pnpmモノリポジトリ (SvelteKit + Hono + 共有パッケージ) で knip v5.71.0 を lefthook pre-commit に導入したところ、~700件の警告 (48 unused files, 277 unused exports, 349 unused types, 7 duplicate exports, 14 unused deps) が発生。
調査の結果、SvelteKit特有の問題と実際のデッドコードが混在していることが判明した。
| カテゴリ | 原因 | 影響 |
|---|---|---|
| unused exports | knipのSvelte compilerがregexベースで、<script>内のimport文のみ抽出。Svelte 5のrunes ($state, $derived) やtemplate内の参照を追跡できない |
大量の誤検知 |
| unused types | TypeScript型のre-exportチェーンが途切れる。バレルexport経由の型が「未使用」と判定される | 同上 |
| barrel exports | index.ts でre-exportしたコンポーネントが、消費側では直接importしている場合にbarrelが「未使用ファイル」と判定 |
ファイル誤検知 |
| 問題 | 原因 | 修正 |
|---|---|---|
| "No Svelte config file found" 警告 | ルートの package.json にsvelteがあるためknipがルートでSvelteプラグインを起動 |
svelte: false をルートワークスペースに追加 |
| ルートのpnpmfile.cjs検出 | knipのデフォルトプロジェクトスキャン | ignore: ['pnpmfile.cjs'] |
.skip ディレクトリのunresolved imports |
無効化されたコードのimportが未解決 | ignore: ['**/*.skip/**'] |
大規模リファクタリング (@flops/uiパッケージ抽出、Svelte 5移行等) の残骸:
- @flops/uiに移行済みのローカルコピー: PositionBadge, CardSelector, HandSelector, RangeMiniGrid
- 実験的/デバッグコンポーネント: DebugPanel, ChipSourceDebugPanel, PwaInstallButton等
- 未使用のコンポーネントチェーン: GameSettingsPanel → GameTypeSelector → ToggleSwitch/PlayerCountSelector
- 空のbarrel export: showdown/index.ts, equity-standalone/index.ts
- リファクタリングの旧名alias: createHoldemUIAdapter → createUIAdapter, isEngineGameState → isExtendedGameState等
import type { KnipConfig } from 'knip';
export default {
ignoreUnresolved: [/^\.\/\$types$/],
ignore: ['pnpmfile.cjs', '**/*.skip/**'],
ignoreBinaries: ['gh'],
ignoreDependencies: ['power-assert', 'vitest'],
workspaces: {
'.': { entry: ['.claude/scripts/*.{js,bash}'], svelte: false },
'packages/*': {},
'apps/backend': { entry: ['src/workers/api.ts', 'scripts/*.ts'] },
'apps/hand': {
entry: ['scripts/*.{ts,mjs,js}'],
project: [
'src/**/*.{js,ts,svelte}',
'tests/**/*.{js,ts,svelte}',
'e2e/**/*.?(c|m)[jt]s?(x)',
'scripts/*.{ts,mjs,js}',
],
paths: {
$lib: ['src/lib'],
'$lib/*': ['src/lib/*'],
$app: ['node_modules/@sveltejs/kit/src/runtime/app'],
'$app/*': ['node_modules/@sveltejs/kit/src/runtime/app/*'],
},
svelte: { config: 'svelte.config.js' },
},
'apps/admin': {
project: ['src/**/*.{js,ts,svelte}'],
paths: {
$lib: ['src/lib'],
'$lib/*': ['src/lib/*'],
$app: ['node_modules/@sveltejs/kit/src/runtime/app'],
'$app/*': ['node_modules/@sveltejs/kit/src/runtime/app/*'],
},
svelte: { config: 'svelte.config.js' },
},
},
} satisfies KnipConfig;pre-commit:
parallel: true
jobs:
- name: biome
run: >-
git diff --cached --name-only --diff-filter=ACM
-- '*.ts' '*.js' '*.mjs' '*.cjs' '*.svelte'
| xargs -r pnpm biome check --no-errors-on-unmatched
- name: knip
run: pnpm knip --no-progress --exclude exports,types--exclude exports,types: SvelteKit環境では exports/types の誤検知が多すぎるため、pre-commitでは除外。files, dependencies, unresolved, duplicates のみチェック--diff-filter=ACM: 削除ファイルをbiomeに渡さない(IO errorになるため)xargs -r: ファイルがない場合にbiomeを実行しない(空引数防止)
knipのビルトインSvelteコンパイラ (node_modules/knip/dist/compilers/svelte.js) は:
<script>タグの中身をregexで抽出import文のみを取り出して依存グラフに追加- テンプレート内のコンポーネント参照は追跡しない
カスタムコンパイラ (svelte/compiler を使用) も試したが、Svelte 5のコンパイラはimportを内部参照に変換するため、knipが追跡できなくなる。ビルトインコンパイラの方がマシ。
node_modules/knip/dist/plugins/svelte/index.js が自動で以下をentry pointとして追加:
src/routes/**/+{page,server,page.server,error,layout,layout.server}{,@*}.{js,ts,svelte}src/hooks.{server,client}.{js,ts}src/params/*.{js,ts}
→ 手動でroute entriesを追加する必要はない
ルートワークスペースに設定すると、knipのSvelteプラグインがルートで起動しなくなる。ただし @sveltejs/kit 自体のconsole.warn ("No Svelte config file found") は抑制されない(knipの警告カウントには影響なし)。
$lib と $app のパスエイリアスは knip の paths 設定で解決可能。SvelteKitプラグインが $app を自動提供するが、明示的に設定した方が確実。
| メトリクス | Before | After |
|---|---|---|
| Unused files | 48 | 0 |
| Unused deps | 14 | 0 |
| Unresolved imports | 4 | 0 |
| Duplicate exports | 7 | 0 |
| pre-commit warnings | ~700 | 0 |
| 削除行数 | - | -10,700+ |
| 削除ファイル数 | - | 42 |
- ルートに
svelte: falseを設定 - 各ワークスペースの
pathsで$lib,$appを設定 -
svelte: { config: 'svelte.config.js' }を各SvelteKitアプリに設定 -
ignoreで.skipディレクトリや設定ファイルを除外 -
ignoreBinariesでCLIツール (gh等) を除外 - lefthookの
--exclude exports,typesでpre-commitの誤検知を防止 - lefthookの
--diff-filter=ACMで削除ファイルをbiomeに渡さない - 定期的に
pnpm knip(全チェック) を手動実行してデッドコードを棚卸し