🐕

huskyとlint-stagedでいい感じにコードを整える備忘録

2023/06/02に公開

huskyとlint-stagedを組み合わせて、git commit直前でlinter、prettierを走らせてますが、huskyがv7になってから導入方法が変わった模様。
huskyとlint-stagedのそれぞれの特徴とかも含めて備忘録として残しとく。

huskyとlint-staged

husky自体はGit Hooksを管理していて、コミットやプッシュなどのアクションが発生した時に特定のスクリプトを実行できます。

git initした際にそのディレクトリ内に.gitディレクトリが作成されるが、辿っていくと.git/hooks/pre-commit.sampleという構成になっており、hooks内のものが対象のアクションになります。

lint-stagedはGitのステージに移動したファイル(git addしたファイル)を対象として、lintやprettierを実行することができます。

セットアップ

ドキュメント通りだけど、以下の順にコマンドを実行。

husky

  1. huskyをローカルインストール
npm i -D husky
  1. Git Hooksの有効化
npx husky install
  1. npm installをトリガーとしてhusky installを実行するスクリプトを用意する

例えば、新しくプロジェクトに入った人がgit cloneして最初にnpm installする際に、同時にhusky installも叩かれることによりローカルでGit Hooksのアクションを有効化できます。

npm pkg set scripts.prepare="husky install"

https://docs.npmjs.com/cli/v8/commands/npm-install

実行すると、package.jsonのscriptにprepareが追加される

package.json
{
...
  "scripts": {
    "prepare": "husky install" 
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  }
}
  1. npm run prepareの実行

husky installがまだ実行されていないので、npm run prepareを実行。

npm run prepare

実行すると、.huskyディレクトリが作成され、Git Hooksの参照先の変更、参照されるシェルスクリプトの作成を行います。
既存のGit Hooks(.git/hooks)に修正をかけるわけでなく、既に設定されているGit Hooksをローカルマシンに適用することができます。

つまり、Git Hooksのトリガーとなる操作が実行されると、git/hooksの代わりに.huskyが参照されるようになる。

こちらを参考にさせてもらいました。より詳しくはこちらをどうぞ。
https://soudai-s.com/how-to-set-up-husky-v7-with-lint-staged

  1. Git Hooksをトリガーとして実行するコマンドを追加

.husky配下にpre-commitというファイル名に(今回はlint-stagedを実行したいので)npx lint-stagedというコマンドを追加します。
これで、pre-commit(コミット直前)をトリガーとしてnpx lint-stagedというコマンドが実行されるわけです。

もちろん、このまま実行しても動かないので、次はlint-stagedの設定を行います。

npx husky add .husky/pre-commit "npx lint-staged"

https://github.com/typicode/husky

lint-staged

  1. lint-stagedをローカルインストール

lint-stagedとついでにESlintとprettierもインストール。

npm i -D lint-staged eslint prettier

以下を実行して、eslintとprettierの設定ファイルを作成。

npm init @eslint/config
echo {}> .prettierrc.json

TypeScriptプロジェクトの場合、こちらも必要になると思うので、こちらもインストール。

npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier
  • @typescript-eslint/parser
    ESLintがTypeScriptをlint できるようにするESLintパーサー
  • @typescript-eslint/eslint-plugin
    TypeScriptコードベースにlintルールを提供するESLintプラグイン
  • eslint-config-prettier
    prettier が整形したコードに対して ESLint がエラーを出力しないようにするプラグイン
  1. lint-stagedの設定

lint-stagedの設定方法はpackage.jsonにlint-stagedの設定するか、.lintstagedrcという設定ファイルを作成する方法などありますが、今回はpackage.jsonに設定。

package.json
{
  "script": {
    "prepare": "husky install"
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
  "lint-staged": {
    "*.{ts, tsx}": "eslint --fix",
    "*": "prettier --write"
  }
}

この設定をすることで、git addでステージに上ったファイルを対象にgit commit直前にeslintとprettierを走らせることができます。

https://github.com/okonet/lint-staged

モノレポのプロジェクトの場合

例えば、モノレポのプロジェクトでバックエンドとフロントのディレクトリが分かれている場合(例: /project/server/project/front)、少し工夫が必要。

モノレポなので、.gitディレクトリは/projectにあるが、package.jsonは/server/front配下にそれぞれある、というパターンになるかと。
その場合、prepareコマンドの中身やpre-commitファイルの中身を変更する必要があります。

  1. /server/frontの各ディレクトリでprepareスクリプトをセット
    /project/frontから/project(.gitがあるディレクトリ)に移動してhuskyをfront配下にインストール。
npm pkg set scripts.prepare="cd .. && husky install front/.husky"

インストール後、npm run prepareを実行して、front配下に.huskyディレクトリが作成されることを確認。

  1. pre-commitファイルの修正
    以下のコマンドを実行し、npx lint-stagedというコマンドをpre-commitファイルに追加。
npx husky add .husky/pre-commit "npx lint-staged"

ただし、lint-stagedコマンドは、package.jsonがあるディレクトリで行う必要があるので、cd frontコマンドを追加。

pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd front
npx lint-staged

これでserverfrontの各ディレクトリ配下にあるファイルでステージ移動したファイルを対象にgit commitすると、lint-stagedで設定してあるeslintとprettierが動く感じです。

このように、モノレポの場合はprepareスクリプトやpre-commitファイルにディレクトリ移動のコマンドを追加する必要があるという。

設定方法が変わった背景・理由

従来は.huskyrc.js.git/hooksを使ってhooksを動かしていたようで、前者はユーザーが実行したいhooksを定義して、後者はhuskyがhooksを(できるだけ多く?可能な限り?)インストールしていたらしいです。
ユーザーが.huskyrc.jsを自由に追加したり更新かけると、自動で変更内容を反映してくれるのは良いけど、一方で何もしなくてもnodeが起動してしまうとのこと。

例えば、.huskyrc.jsにhooksとしてcommit-msgを追加して変更が反映しても、.git/hooksにはそのhooksが追加されてないので差分が発生して、その差分を埋めるためのボイラープレートが必要になってしまう(的なことを言ってるんだと思います)。

ところが、Gitがバージョン2.9からcore.hooksPathを導入し、.git/hooksを使わずに別のディレクトリを使うように指示することができるようになりました。
これによって、husky installコマンドの実行によって、.git/hooksを使わず.husky/を使うようになったとのことです。

↑解釈間違っていたら教えてください。。

おわりに

huskyもlint-stagedも今まで使ってはいましたが、そこまで知らなかったので調べてアウトプットするいい機会でした。そして、zennの初記事でした。

参考

https://rinoguchi.net/2021/12/husky-and-lint-staged.html

https://blog.typicode.com/husky-git-hooks-javascript-config/

https://zuma-lab.com/posts/eslint-prettier-settings

https://github.com/typicode/husky

https://github.com/okonet/lint-staged

Discussion