この記事は弁護士ドットコム Advent Calendar 2024の 8 日目の記事です。
クラウドサイン事業本部でエンジニアをやっている田邉です。
みなさんの js、ts プロジェクトには、なぜ export しているのかわからないコードや、なんのためにインストールしているのかわからないモジュールはありませんか?
それらが不要だとしたらこの年末に大掃除するチャンスです!
※ 本記事の内容は既存コードに対する大幅な変更が発生する可能性があります。タイトルには反しますが、年末に実施する場合は十分に注意して実行してください。
私の関わっている事業のプロジェクトでは Next.js を利用しているフロントエンドで以下のような課題を抱えていました。
- 使われていないコンポーネントや hook、メソッドや type が溜まってしまう
- 不要な export が発生してしまう
- vanilla-extract を利用しており、スタイルを別ファイルに記載する方法をとっていることで使われていないスタイリングが発生しやすい
これらを解消するために knip を利用することを決めました。
本ドキュメントでは knip について説明し、実際にどう利用したのかを紹介いたします。
knip の仕組み
knip では entry に設定されたファイルを起点に、import しているモジュール(ファイル)を辿ってツリーに組み込まれたファイルを resolved file としてマークしていきます。
knip で検出されたファイルツリーの一部を表示させてみると以下のようになりました。
apps/・・・/CreatedAgendasListModal/CreatedAgendasListModalCard.tsx:CreatedAgendasCard └─ apps/・・・/CreatedAgendasListModal/CreatedAgendasListModal.tsx ✓ apps/・・・/CreatedAgendasListModal/CreatedAgendasListModal.tsx:CreatedAgendasListModal └─ apps/・・・/CreatedAgendasListModal/index.ts └─ apps/・・・/MeetingHeader/MeetingHeader.tsx ✓ apps/・・・/CreatedAgendasListModal/CreatedAgendasListModalCard.css.ts:createdAgendasCard └─ apps/・・・/CreatedAgendasListModal/CreatedAgendasListModalCard.tsx ✓
上記の例からは
CreatedAgendasListModalCard.tsx
に含まれるCreatedAgendasCard
というメソッドがCreatedAgendasListModal.tsx
というファイルから呼び出されているCreatedAgendasListModal.tsx
に含まれるCreatedAgendasListModal
というメソッドがCreatedAgendasListModal/index.ts
を経由してMeetingHeader.tsx
から呼び出されている- ・・・
というツリー構成を読み取ることができます。
knip では、このようにして検出したファイルを resolved としてマークしていき、最終的にどこでも利用されていないファイルを以下のような式で検出するようです。
unused files = project files - (entry files + resolved files)
https://knip.dev/guides/configuring-project-files
knip の使い方
検出できるもの
knip が検出するのは不使用のファイルだけではなく、未使用のモジュールなども含まれます。
以下に 2024 年 12 月時点で knip で検出できるものの一部を列挙します。
- 使用されていないファイル
- dependencies に含まれるにも関わらず使用されていないモジュール
- devDependencies に含まれるにも関わらず使用されていないモジュール
- 参照されていない peerDependencies
- コード内で利用されているにも関わらず package.json に記載されていない(明示的にインストールされていない)モジュール
- import されるモジュールが見つからない
- 不必要な export や export type
- export された enum や class の中で使用されていないメンバー
https://knip.dev/reference/issue-types
基本的な使い方
では具体的にどのようにしてプロジェクトで利用するかを説明します。
公式サイトに Knip has good defaults and aims for “zero config”. Here’s a simplified version of the default configuration
とあるように、knip は zero config を目指しており、基本的には設定ファイルなしで利用できます。
これは knip の plugin という仕組みのおかげで、たとえば package.json の dependency もしくは devDependency に next
を発見すると、knip は自動で以下のような設定を追加します。
{ "next": { "entry": [ "next.config.{js,ts,cjs,mjs}", "{instrumentation,middleware}.{js,ts}", "app/global-error.{js,jsx,ts,tsx}", "app/**/{error,layout,loading,not-found,page,template,default}.{js,jsx,ts,tsx}", "app/**/route.{js,jsx,ts,tsx}", "app/{manifest,sitemap,robots}.{js,ts}", "app/**/{icon,apple-icon}.{js,jsx,ts,tsx}", "app/**/{opengraph,twitter}-image.{js,jsx,ts,tsx}", "pages/**/*.{js,jsx,ts,tsx}", "src/{instrumentation,middleware}.{js,ts}", "src/app/global-error.{js,jsx,ts,tsx}", "src/app/**/{error,layout,loading,not-found,page,template,default}.{js,jsx,ts,tsx}", "src/app/**/route.{js,jsx,ts,tsx}", "src/app/{manifest,sitemap,robots}.{js,ts}", "src/app/**/{icon,apple-icon}.{js,jsx,ts,tsx}", "src/app/**/{opengraph,twitter}-image.{js,jsx,ts,tsx}", "src/pages/**/*.{js,jsx,ts,tsx}" ] } }
こうした plugin を他にも多数揃えているおかげで、基本的には設定ファイルなしでの利用が可能となっています。
ただし、特定のディレクトリでは knip によるチェックを回避したいケースや、後で記載するように ESLint などのライブラリの設定に特殊なファイルを利用するケースなど、設定ファイルを要するシーンは少なくない印象です。
以下に示す例では
- 特定のワークスペースで index.js を ESLint の設定ファイルとして認識させる
- 特定の依存関係における検出を無視する
といった設定をしています。
import type { KnipConfig } from 'knip' const config: KnipConfig = { ignoreDependencies: ['lerna'], workspaces: { 'packages/eslint-config': { eslint: { config: 'index.js', }, }, }, } export default config
設定ファイルは json や ts など、多くの形式をサポートしています。 詳しくは以下を参照してください。
https://knip.dev/overview/configuration#location
設定ファイルの記法については以下を参照してください。
https://knip.dev/guides/configuring-project-files
それでは実際に実行した際にどのように結果が表示されるか、以下に例を示します。
なお実際のプロジェクトで試した結果ではないのでご注意ください。
% yarn knip Unused files (1) workers/src/tracer.ts Unused dependencies (2) pino-pretty apps/bff/package.json yjs apps/app/package.json Unused devDependencies (1) ts-loader apps/bff/package.json Unlisted dependencies (1) fastify apps/bff/src/app.module.ts Unused exported types (1) Task type workers/src/database/types.ts:92:13 Unused exported enum members (3) UNSPECIFIED AgendaStatus apps/bff/src/modules/agenda/agenda.model.ts:112:3 OPEN AgendaStatus apps/bff/src/modules/agenda/agenda.model.ts:113:3 CLOSED AgendaStatus apps/bff/src/modules/agenda/agenda.model.ts:114:3
公式にも記載されているように package.json の scripts に knip コマンドを設定しておきましょう。
私のプロジェクトでは yarn を利用していたので、上記の例のように yarn knip
コマンドで実行できました。
"scripts": { "knip": "knip", // ・・・ }
CLI での実行
検出する対象については CLI のフラグを利用することでも制御できます。
https://knip.dev/reference/cli#--include
たとえば使用していないファイルと dependency のみ検出したい場合は以下のコマンドを実行します。
knip --include files,dependencies
検出するタイプを表すフラグは以下を参照してください。
https://knip.dev/reference/issue-types
プロジェクトでの knip 運用
本章では実際のプロジェクトで、どのように運用してきたかを説明します。
内容説明の前にプロジェクトについて以下のような特徴があることを共有しておきます。
- yarn workspace を利用したモノレポ環境であること
- リポジトリには GitLab を利用しており、CI でフロントエンドの自動テストなどを実行していること
workspace 下で利用する
まず考慮しなければならないのは workspace を利用していることです。
yarn workspace などを利用中の場合、knip をワークスペースのルートでインストールし、設定ファイルに以下のように記載します。
{ "workspaces": { "apps/app": { "ignore": ["src/graphql/generated/**", "public/**", "src/mock/**"], "ignoreDependencies": [ // ・・・ ] } }, // ・・・ }
上記のように記載することでワークスペースごとに enrty や ignore などの設定などが可能となります。
また knip をワークスペースのルートで実行するのではなく、たとえば apps/app で scripts 定義し、実行した場合を考えます。
"scripts": { "knip": "knip" }
この場合、knip 自体が Unlisted binaries
としてマークされるはずです。
なぜかというと、knip コマンドを実行するためのバイナリは knip パッケージに含まれますが、knip パッケージ自体はワークスペースルートの node_modules に含まれるため、apps/app の package.json から見ると同一階層の node_modules にはないと判断してしまうためです。
これは knip パッケージだけの問題ではなく、たとえば ESLint の設定ファイルなどに含まれる eslint-config-prettier
なども Unused dependencies
としてマークされるでしょう。
したがって、ワークスペースを利用している場合はルートで knip を実行するように、上記のような設定を施しておくことをおすすめします。
auto-fix を利用する
knip では --fix
というフラグをつけることで問題点の自動修正をしてくれます。
https://knip.dev/features/auto-fix#flags
さらに --allow-remove-files
フラグをつけることで不使用とマークされたファイルの自動削除までやってくれます。
私のプロジェクトでは package.json の scripts に以下の 2 つのコマンドを用意しました。
"scripts": { "knip": "knip", "knip:fix": "knip --fix --allow-remove-files" }
CI に組み込む
knip によるチェックを CI で実行することで、不要なファイルや無駄な export が発生した時点でそれに気づくことができます。
弊社では GitLab を利用しているため、.gitlab-ci.yml
にワークスペースルートの scripts に定義した knip コマンドを実行するような処理を記載しました。
production モードをうまく使う
production モードでは実行時にテストコードや storybook など、本番環境で利用しないファイルは除外されます。
これにより、通常実行では検出されなかったテストコードのための export などはエラーとして検出されるようになります。
production モードの具体的な挙動は以下のとおりです。
Only entry and project patterns suffixed with ! Only production entry file patterns exported by plugins (such as Next.js and Remix) Only the start and postinstall scripts Ignore exports with the @internal tag
https://knip.dev/features/production-mode
テストコードのためだけの export は本番環境では無駄な export になってしまうので、types.ts などの別ファイルに切り出すか、本当にそのコンポーネントでのみ利用するような場合は二重定義にはなってしまいますがテストコードでも同じ定義を持つようにするなどしたほうが良いでしょう。
こうした気づきを得るためにも、scripts に定義するのはアリだと思います。
一方で、たとえば Next.js プロジェクトでは next.config.js でのみ利用されるモジュールは Unused としてマークされてしまうことに注意です。
Unused dependencies (2) @vanilla-extract/next-plugin package.json next-videos package.json
const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin') const withVanillaExtract = createVanillaExtractPlugin() const withVideos = require('next-videos') /** @type {import('next').NextConfig} */ const nextConfig = {・・・} module.exports = withVanillaExtract(withVideos(nextConfig))
ちょっと詰まったとこ
プロジェクトでは graphql-codegen-typescript-mock-data というライブラリを利用して、GraphQL スキーマからテストデータを自動生成できるようにしているのですが、GraphQL Codegen の設定ファイルである codegen.ts に以下のように記載していると unused としてマークされてしまいました。
'./src/graphql/generated/mock.ts': { plugins: [ { 'typescript-mock-data': { typesFile: './graphql.ts', terminateCircularRelationships: true, prefix: 'createMock', enumValues: 'upper-case#upperCase', }, }, { add: { content: '/* eslint-disable */', }, }, ], },
Unused devDependencies (1) graphql-codegen-typescript-mock-data apps/app/package.json Unlisted dependencies (1) @graphql-codegen/typescript-mock-data apps/app/codegen.ts
GraphQL Codegen は knip のプラグインにあり、graphql-codegen-typescript-mock-data
も GraphQL Codegen の公式プラグインに含まれるので不思議だったのですが、コードを見ると codegen-
という文字列を含んでいなければならないようだということがわかりました。
https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/graphql-codegen/index.ts
そこでプラグインの指定方法を以下のようにフルネーム指定してみたところ、knip に認識されるようになり、問題を解消できました。
'./src/graphql/generated/mock.ts': { plugins: [ { 'graphql-codegen-typescript-mock-data': {
他にも
冒頭で表示したような knip が検出したツリー構成の可視化をしたければ trace
フラグを利用できます。
https://knip.dev/guides/troubleshooting#trace
ワークスペースがちゃんと指定できているか、プラグインの検出が問題ないかなど、knip の報告の理由を知りたければ debug
フラグを利用すると良いでしょう。
https://knip.dev/guides/troubleshooting#debug
またプロジェクトでは利用しませんでしたが、レポーター機能などもあるようなので気になる方は調べてみてください。
まとめ
たくさん書いてきましたが、私の携わっているプロジェクトでは knip はフロントエンドの無駄なコード検出にはなくてはならない存在になりました。
導入したいと思ってくれる方がいれば幸いですが、年末ですので auto-fix する際はくれぐれも慎重に、そして自己責任でお願いします。
良いお年を!