Handpick で lintDependencies や testDependencies を管理しよう!

React + TypeScript 構成などで巨大な Node.js プロジェクトを構築していると面倒になってくるのが、devDependencies に積まれた開発用 npm パッケージの量。

大量の ESLint Config や Prettier 関連のツールが並び、テストのために Jest や Playwright などがインストールされている。

これらは Lint のため、Unit Test や E2E テストのために必要なツールであり、package.json で管理したいのは山々なのだが、いかんせん物量が多いと npm install に時間がかかるようになる。

また、CI サーバを導入していて、feature ブランチであっても Lint やテストは Git Push に応じて CI サーバで回してしまう場合もあるだろう。そうすると、開発者のローカルマシンに大量の Linter や Testing ツールを導入する必要は、必ずしも必要でなかったりもする。

そんなワケで、devDependencies 内にある Lint 用のパッケージ、テスト用のパッケージなどを切り出して、devDependencies を軽くしたい、という要件が挙がった。

そこで調べたところ、Handpick という npm パッケージでコレを実現できそうなのでやってみることにした。

検証プロジェクト

Handpick の挙動を確認するためのサンプルプロジェクトを以下に用意した。

Vite で react-ts プロジェクトを立ち上げ、Linter として ESLint 系のパッケージを用意した。最初は --save-devdevDependencies に追加して動作確認したが、一通り確認が終わったら、次のように package.jsonlintDependencies という項目を増やしてそちらにパッケージ群を記載するようにした。

{
  // …中略…
  "dependencies": {
    "react": "18.3.1",
    "react-dom": "18.3.1"
  },
  "devDependencies": {
    "@types/react": "18.3.11",
    "@types/react-dom": "18.3.0",
    "@vitejs/plugin-react": "4.3.2",
    "typescript": "5.6.3",
    "vite": "5.4.8"
  },
  "lintDependencies": {
    "@eslint/js": "9.12.0",
    "eslint": "9.12.0",
    "eslint-plugin-react": "7.37.1",
    "eslint-plugin-react-hooks": "5.1.0-rc-fb9a90fa48-20240614",
    "eslint-plugin-react-refresh": "0.4.12",
    "globals": "15.11.0",
    "typescript-eslint": "8.8.1"
  }
}

名称は lintDependencies としたが、この名前は任意に決められる。ユニットテスト系の依存パッケージを増やしたければ testDependencies、E2E テストを別けて書きたければ e2eDependencies などと名前を付けてやると、依存パッケージの宣言だと分かりやすいであろう。

パッケージ数が少ないものの、こうして別けてみると見通しが良くて分かりやすくなった。

というのが一目瞭然である。

Handpick で個別にインストールしてみる

さて、Handpick というツールは、一時的に package.jsondependencies 部分を書き換えることで動作する仕組みになっている。npm の他に yarnpnpm にも対応しており、内部的にはこれらのインストールコマンドを直接実行する形になっている。

まずは Handpick をグローバルインストールしよう。

$ npm install --global handpick

そして $ handpick とオプションなしで実行すると、dependenciesdevDependencies がインストールされる挙動になっている。

それでは lintDependencies をインストールする場合はどうするかというと、次のように --target オプションを指定する。

$ handpick --target=lintDependencies

すると、コマンド実行中だけ、package.jsondependencies の内容が lintDependencies のモノになり、前述の検証プロジェクトでいうと ESLint 類だけインストールされる形となる。なお、この時 dependenciesdevDependencies のパッケージはインストールされない。本当に lintDependencies に書かれたモノだけがインストールされる動きとなる。

使用するツールによっては dependenciesdevDependencies と、それにプラスして lintDependencies もインストールしたい、という場合が多いと思うので、そういう時は以下のように3つとも --target オプションに指定してやることで対応できる。

$ handpick --target=dependencies --target=devDependencies --target=lintDependencies

コレは素晴らしい~

そんなワケで、Handpick というツールを使うと、devDependencies にひとまとまりになってしまう Linter や Testing ツール類を、lintDependenciestestDependencies として切り出して管理ができる。

コレにより npm install の所要時間を削減でき、開発者は今までどおり npm install で開発に必要なツールだけインストールできる。

そして CI サーバなどでは handpick コマンドを用いることで lintDependencies などを追加でインストールしてやることで、CI サーバ上では Lint や Test が行えるようになる。もちろん、ローカルで開発者が handpick コマンドを使えば同じことができる。

これらを同じ単一の package.json で管理できるので、とても分かりやすく見通しが良い。

ESLint 系のパッケージが肥大化してしまっているプロジェクトではかなりオススメだ。