Next.jsで使われていないコードをts-pruneで一掃し、CIで仕組み化する
今回はゲームエイトのNext.jsが使われているプロダクトにて、ts-pruneを用いて一切参照されていないデッドコードを全て洗い出し、削除してみました。
また、これ以降はデッドコードが発生しないようにGithub Actionsに組み込み、デッドコードが生まれたらCIがコケる仕組みを導入しました。
デッドコードになりがちなのは汎用的なUIパーツ
eslintではファイル単位で未使用の変数(コンポーネント、関数)を検知するno-unused-varなどがありますが、このルールは該当の変数がexportされていると、実際にはどこからも参照されていない場合でも、検知されなくなってしまいます。そのため、汎用的なUIパーツなど各ページから参照されるものは、そのパーツがどのページからも使われなくなった時に、削除するべきなのに見過ごしがちになる問題がありました。
同じ悩みをもつissueを見つけ、いくつかの代替案が書かれていましたが、その中でも簡単そうなts-pruneを導入してみました。
ts-pruneのインストールと実行
ドキュメントそのままですが、インストールします。
# npm
npm install ts-prune --save-dev
# yarn
yarn add -D ts-prune
package.jsonにスクリプトを登録します。
{
"scripts": {
"find-deadcode": "ts-prune"
}
}
実行してみます。
> yarn find-deadcode
yarn run v1.22.19
$ ts-prune
src/lib/$path.ts:187 - PagesPath
src/lib/$path.ts:605 - StaticPath
src/pages/_app.tsx:79 - default
src/pages/404.tsx:12 - default
src/pages/500.tsx:12 - default
src/pages/index.tsx:58 - getServerSideProps
(中略)
Done in 6.35s.
検知されたファイルのパスと行番号・関数名が出ますので、不要か検証して個別に修正していきます。自動修正オプションがあるものかと期待したのですが、Next.jsなどフレームワークの振る舞いによってgetServerSidePropsなど参照されなくても実際は必要なexportがあるので、自動修正は現実的ではないのでしょう。詳しくは後述します。
実情に合わせてts-pruneの出力を整える
オプションやgrepで出力を絞らない、生の出力のままだと、対応すべきでないファイルも多く検出されてしまいます。
1度だけの実行であれば出力を1行ずつ判断して対応不要ならスキップするだけでいいのですが、CIに組み込むことも視野に入れ、変更すべきファイルのみが出力されるようにしてみました。
先に結論ですが、該当のプロダクトの状況に合わせて調整された最終的なts-pruneの実行コードは以下のようになりました。
yarn find-deadcode --ignore 'src/lib/\$path.ts|src/types/api/models/landingPageContent/content.ts' | \
grep -v -E "src/(app|pages)/.* - (default|GET|dynamic)" | \
grep -v getServerSideProps
理由は以下のとおりです。
- 自動生成されているファイルは
// ts-prune-ignore-next
を手動で付与してignoreせず、--ignoreオプションでコマンドからファイル名を指定しました。(自動生成されるファイルは再出力で手動修正が消えてしまい手動修正を維持しにくい環境のため) -
Next.jsの仕様に基づいた修正
- 関数
getServerSideProps
は参照されてなくてもexportしていないといけない、かつファイルが増えるたびに// ts-prune-ignore-next
を付与するのは疲れるためgrepで除きました。 - 同じ理由でページを司るpages、app配下の
default
や、APIのとしてdynamic
,GET
など特定の関数もgrep正規表現で出力から取り除きました
- 関数
結果として// ts-prune-ignore-next
を付与したのはnext.config.js(.mjs)にのみになりました。ページ等と違ってファイルが増えることが予想されない、かつ自動生成でなく手動の編集が容易な類のファイルであれば、個別対応が簡潔で良いと思います。
そして無事に出力結果で残ったものは全て修正(≒削除)することができました。
Github Actionsに組み込んで仕組み化
次に↑のコードを、CIに以下のように組み込みました。
jobs:
ts-prune:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: ts-prune(デッドコードの除去)
shell: bash
run: |
yarn find-deadcode --ignore 'src/lib/\$path.ts|src/types/api/models/landingPageContent/content.ts' | \
grep -v -E "src/(app|pages)/.* - (default|GET|dynamic)" | \
grep -v getServerSideProps | \
(! grep -v -E "(ts-prune --ignore|Done in |yarn run)")
追加した点としては検知したらCIがコケるようエラーを吐かせるようにしたかったので、grepが1行も出力できなければエラー終了する挙動を活用しようと思いました。
そのため、前のコマンドに加え、ファイル一覧以外に表示される”Done in ◯.◯◯s.”等の余計な出力を除き、出力する行が0なら正常終了したいので!
で囲みそのgrepの終了コードを反転させました。
これにて意図通り何も検知しなければCIがパスし、検知するとCIがコケて記事冒頭の画像のよう対象の情報を表示するようになりました。
以上です。年末も近いので、皆さんのプロダクトでも大掃除するのはいかがでしょうか。
Discussion