コードを綺麗に保ちたい？それhuskyでできるよ！

## これはなに？

上長がしれっと導入してくれたhuskyがとんでもなくコードを綺麗に保てているので自分の中に落とし込む目的で記事にしてみました。

この記事がhuksyを導入する方の後押しになれば幸いです。

## この記事のゴール

以下の理解が得られることを本記事のゴールとします

- huskyを使用してコミット前に自動でlinterが実行されること

## 問題

普段からチームのメンバーとしてコードを書いているエンジニアの方は分かると思いますが、

コードを綺麗に保つことって難しいじゃないですか？

そのための努力として、以下のことをやっている方もいると思います。

- PR作成時に必ずlinterを通す
- Prettier等のコード整形ツールを利用する

上記のような静的解析ツールは自動でコードの汚い部分を指摘してくれるのでとても便利ですが、PRを出すたびに実行するのってなんというか、、

面倒ですよね？

**それ、実はhuskyが勝手にやってくれるんです！**

## huskyでなにができるのか

百聞は一見に如かずということで、まずはhuskyでできることを物語風に示してみます。

:::note info
**登場人物**
Eng A: エンジニアAさん
Eng B: エンジニアBさん
:::

### — huskyが存在しない世界線 —

Eng A「いやー今日も良い仕事した」

Eng A「さっそくプルリク作ってレビューもらおっと」

〜〜〜

Eng A「LGTMもらったのでマージっと。」

Eng A「今日も自分お疲れやで。」


**別日**

Eng B「ん？lint通そうと思ったらエラー出たぞ、なんでだ？」

Eng B「あれ、これAさんが実装してくれた箇所だ」

Eng B「Aさーん、ここlint通したの？」

Eng A「あっ、すません、lint実行し忘れてました。。」

### — huskyがある世界線 —

Eng A「いやー今日も良い仕事した」

Eng A「さっそくプルリク作ってレビューもらおっと」

Eng A「ん？commitしようと思ったら失敗したぞ、なんでや」

Eng A「ははーん、使ってもいないファイルをimportしてたからか」

Eng A「すぐ気づくワイ、さすがやな」

Eng B「（いや事前に知らせてくれるhuskyのおかげやろ…）」

このように、レビューをもらう前に必ず行う、testだったりlintだったりを代わりにやってくれるのがhuskyです。

**当初はこの部分をGithub Actionsで自動化していたのですが、エラーを吐いた場合のコミットし直しだったり、CIの結果を待つ時間自体が非効率であったためhuskyを使うようになりました。**

## 手順

では実際にプロジェクトに導入する手順を説明していきます。

### 開発環境

- npm v8.3.0
- Next.js
- TypeScript
- ESLint, Prettier

### 0. 開発環境の準備

今回の記事では説明を省略させていただきます。

別の記事で書いたのでそちらを参考にしていただけたらと思います。

https://qiita.com/mu-suke08/items/28fefe92b113c8f1c25d

### 1. huskyのインストール

まずはお決まりのhuskyをインストールします。

```bash
$ npm i -D husky
```

### 2. huskyの初期準備

```json:package.json
{
  // ~省略~
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  // ~省略~
}
```

次に、huskyを以下のコマンドで有効にします。

```bash
$ npx husky install
```

有効にすると以下のようなディレクトリが作成されることが確認できると思います。

```bash
.husky
└ _
  ├ .gitignore
  └ husky.sh
```

### 3. lint-stagedのインストール

```bash
$ npm i -D lint-staged
```

### 4. hooksの作成

```bash
$ npx husky add .husky/pre-commit "npx lint-staged"
```

上記を実行することで以下の階層に`pre-commmit`ファイルが作成されます。

```bash
.husky
├ pre-commit
└ _
  ├ .gitignore
  └ husky.sh
```

作成したpre-commitファイルは`git commit`する際に行う処理を記載します。

今回のケースだと、lint-stagedというコマンドを実行したいので以下のように記載します。

```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
```

### 4. lint-stagedの設定

`package.json`で以下のように設定します。

```json:package.json
{
  // ~省略~
  "lint-staged": {
    "src/**/*.{ts,tsx}": "npm run {任意のコマンド}"
  },
  // ~省略~
}
```

### 5. 動作確認

筆者の場合は以下のように設定して実行してみました。

```json:package.json
{
  // ~省略~
  "scripts": {
    "lint:precommit": "eslint 'src/**/*.{ts,tsx}' --max-warnings 0",
    "fmt:precommit": "prettier -l './**/*.{js,jsx,ts,tsx,json,css,scss}'",
  },
  "lint-staged": {
    "src/**/*.{ts,tsx}": "npm run lint:precommit",
    "src/**/*.{js,jsx,ts,tsx,json,css,scss}": "npm run fmt:precommit"
  },
  // ~省略~
}
```

上記では`git commit`時に以下の処理をするように記載しています

- src以下にあるts, tsxファイルを全てlintに通す
- 全てのjs, jsx, ts, tsx, css, scssファイルをPrettierを用いてコード整形をする

わざと不要なインポートファイルを仕込ませてコミットしてみたところ以下のように表示されました。

```bash:pre-commitの実行ログ
[STARTED] Preparing lint-staged...
[SUCCESS] Preparing lint-staged...
[STARTED] Running tasks for staged files...
[STARTED] package.json — 1 file
[STARTED] src/**/*.{ts,tsx} — 1 file
[STARTED] src/**/*.{js,jsx,ts,tsx,json,css,scss} — 1 file
[STARTED] npm run lint:precommit
[STARTED] npm run fmt:precommit
[SUCCESS] npm run fmt:precommit
[SUCCESS] src/**/*.{js,jsx,ts,tsx,json,css,scss} — 1 file
[FAILED] npm run lint:precommit [FAILED]
[FAILED] npm run lint:precommit [FAILED]
[SUCCESS] Running tasks for staged files...
[STARTED] Applying modifications from tasks...
[SKIPPED] Skipped because of errors from tasks.
[STARTED] Reverting to original state because of errors...
[SUCCESS] Reverting to original state because of errors...
[STARTED] Cleaning up temporary files...
[SUCCESS] Cleaning up temporary files...
✖ npm run lint:precommit:
ESLint found too many warnings (maximum: 0).
> nextjs-sample-app@0.1.0 lint:precommit
> eslint 'src/**/*.{ts,tsx}' --max-warnings 0 "/path-to-your-project/src/pages/login.tsx"
/path-to-your-project/src/pages/login.tsx
  12:3  warning  'Slider' is defined but never used  @typescript-eslint/no-unused-vars
✖ 1 problem (0 errors, 1 warning)
husky - pre-commit hook exited with code 1 (error)
```

無事、エラーを吐いてコミット前にlintやprettierを実行できています。

## 注意点

lint-stagedは名前の通り、ステージングされたファイルだけを確認しています。

なので導入以前からあったlinterを通っていないファイルはステージングされない限り、確認されません。

そこの確認は今まで通り、`npm run lint`等をして確認する必要があります。

## 余談

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

> Run linters against staged git files and don't let 💩 slip into your code base!
ステージングされたgitファイルに対してlinterを実行し、あなたのコードベースに💩を漏らさないようにしましょう！
> 

コードの汚い部分を💩と表現しているのがなんか面白いなと思いました。

ではまた！

## 参考記事

https://fwywd.com/tech/husky-setup

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