STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

不要なコードをクリーンするために Knip 導入

STORES 予約 の開発をしている菊池です。このブログでは Knip 導入について書きます。 Knip は JavaScript や TypeScript のプロジェクトのコードをお片付けするためのツールです。 Knip はオランダ語で「ハサミ」という意味のようで、コードを削減する意味ではわかりやすいネーミングです。

knip.dev

ちなみに ts-prune というツールもあったのですが、メンテナンスモードとなり Knip を勧めている状況となっています。 ts-prune は README に書いてある通り、多くのユースケースをサポートする中、複雑性が増し、コアシステムの変更や機能追加が気軽にできなくなったのが背景としてあるようです。

GitHub - nadeesha/ts-prune: Find unused exports in a typescript project. 🛀

背景

Backend では Ruby on Rails、フロントエンドでは一部を除いて Next.js / React / TypeScript などを用いて開発しています。 サービスとして10年以上続いており、このフロントエンドの構成ができたのは2021年2月あたりになります。ブログを執筆した時点までは3年10ヶ月経ちます。 コミットは15000コミットもあり、開発に関わる人数も50人と多いものです( Contributors の数で見ただけで現在進行形では、14,5人です)

開発者が入れ替わるのと複数のプロジェクト、複数のチームで contribute するのもあって「経緯がわからないが残っているコード」はしばしばあり、これは個人的にはいらなかったとしても目につけばそれは認知負荷(課題外在性負荷)になると考えています。 そのため不要だったら消すというのをできるだけやっていきたいと思い、 Knip を入れることにしました。

導入

npm package なので knip のサイトの getting-started を見てシンプルに始められます。

https://knip.dev/overview/getting-started

そこからプロジェクトの個別の設定を knip.json で記述します。一部を取り除いていますが、下記が STORES 予約 での設定です。

{
  "entry": ["src/**.ts"],
  "project": ["src/**"],
  "ignore": [
    "package.json", "babel.config.js", ".github/**",
  ],
  "vitest": false,
  "rules": {
    "unlisted": "off", // メインの package に含まれてインストールされるものを使うのは意図しているものなので off
  }
}

主に ignore, rules の部分が主な個別の部分ですが、いくつかその理由を説明すると、

  • build 時に使用している dependencies まで検知されてしまうため、 package.json babel.config.js などを ignore している
  • Vitest で使用している Plugin の影響で Knip のコマンドが実行できないので、 vitest: false にしてオフにしている
  • unlisted: off の部分はコメントがある通りで、例えば Next.js の styled-jsx/css を使っていたりすると検知されてしまう

json にコメントが書ける通り、 jsonc でも記述できたりするので、 off にした場合の経緯を記述できて非常に便利です。

普段の開発での活用

導入しただけでは使われないので、定常的な開発の中で活かせるようにします。 GitHub の PR 上でそれに気づき、開発者が消すようにします。

  • GitHub に対してのコメントで Knip が出すレポートを確認できる
  • レポートを元に開発者が、その要否を判断する
  • 不要であれば、エディタを使うもしくは Knip のコマンドのオプションの --fix を使ってコードを削除する

--fix というのは、検知したものを消すオプションなのですが、 PR 上で自動で消すというのはしていません。 開発者の意図しないコミットが積まれたり、「この後使うメソッドなので消されると困る」と言ったことが発生するためです。

PR 上に自動でコメントするために、 GitHub Actions を使います。以下がその yaml です。

name: check-knip.yml

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v4

      - name: setup node
        uses: actions/setup-node@v3
        with:
          node-version-file: '.node-version'
          cache: 'yarn'

      - name: node modules cache
        uses: actions/cache@v3
        id: node-modules-cache
        with:
          path: './node_modules'
          key: ${{ runner.os }}-build-${{ hashFiles('./.node-version') }}-${{ hashFiles('./yarn.lock') }}

      - if: ${{ steps.setup-node.outputs.node-modules-cache-hit != 'true' }}
        name: Install dependencies
        run: yarn --frozen-lockfile

      - name: knip
        id: knip-check
        run: yarn run knip:check > /tmp/knip-report
        continue-on-error: true

      - name: Create PR comment
        uses: actions/github-script@v6
        if: steps.knip-check.outcome == 'failure'
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const fs = require('fs')
            const knip_report = fs.readFileSync('/tmp/knip-report', 'utf8')

            const output = `
            Knip により検知されました。下記の詳細から確認して、不要であれば対応してください。
            なお \`yarn knip:fix\` というコマンドも使えます。
            <details><summary>Knip Report📖</summary>

            ${knip_report}

            </details>`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

「 CI が error で止まると、開発中事前に準備しようとしたメソッドなども検知されて開発が進まなくなる」というフィードバックをチーム内でもらったので、 continue-on-error: true を入れています。

GitHub へのコメント

yarn knip:fix というのは knip --fix --allow-remove-files が実体のコマンドです。 --fix は上述した通りのオプションですが、 --allow-remove-files はファイル自体が不要であれば削除するオプションです。

Knip を実行してみる

GitHub 上では、差分だけでなくプロジェクト全体に対する検知がされるので、現状の不要なものを削除してみます。 GitHub Actions を追加される前に適用することで、最初から検知されるのを防ぎます。

3400行ほどのコードを消すことができました。

おわりに

Knip を普段から使うための内容を記述してきました。まだ一時的に ignore しているファイルや検知してほしくない対象などが検知されてしまうことがあるので設定などは今後も改善の余地はありそうですが、大幅にコードを減らせ今後も開発に役立てそうです。

設定ファイルなどは単にコピペできる内容ではありませんが、ぜひ参考にしてみてください。