10分で理解: react アプリ開発環境を準備 (TypeScript, commitlint, husky, Sourcetree)

# 概要
react アプリを TypeScipt で開発する際の定番の設定をまとめておく。

- TypeScript で記述
- eslint で構文チェック（クォーテーションマークの統一、import 文のソート）
- jest でユニットテスト
- commitlint で commit log が [semantic versioning](https://semver.org/) に準拠しているかチェックし、エラーが出たときには commit 中止
- commit 前に eslint, jest を自動実行し、エラーが出たときには commit 中止
- Sourcetree からも上記のフックが正しく実行されるように細工

この環境設定を行なった git repo はこちら。https://gitlab.com/ken.miyasita/react-app-on-gitlab-pages/-/tree/main

## 全体像

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/598e3db5-e0a4-058e-2858-b20c232caa89.png">

## react アプリケーションの雛形を準備

参照: [Create React App](https://reactjs.org/docs/create-a-new-react-app.html) 

```bash
$ node --version
v15.14.0
$ npx create-react-app react-app-on-gitlab-pages --template typescript
$ cd react-app-on-gitlab-pages
$ yarn start
```
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/f580eb7f-6de7-c677-0bfe-42b0155f4636.png">

## eslint

参照
* [Getting Started with ESLint - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/user-guide/getting-started)
* [Getting Started - Linting your TypeScript Codebase](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md)
* [eslint-plugin-simple-import-sort](https://github.com/lydell/eslint-plugin-simple-import-sort#readme)

lint ルールをいくつか設定しておく。

* 文字列は、全てシングルクオートマークにする
* import 文は、いくつかのカテゴリに分け、それぞれのカテゴリー内は import 対象のパス名でソートする

```bash
$ yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-simple-import-sort --dev
```

```json:.eslintrc.json
{
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    "parser": "@typescript-eslint/parser",
    "plugins": [
        "@typescript-eslint",
        "simple-import-sort"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "rules": {
        "quotes": [
            "error",
            "single"
        ],
        "simple-import-sort/imports": "error",
        "simple-import-sort/exports": "error"
    }
}
```

```json:package.json
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx"
  },
```

## jest

参照：[Running Tests | Create React App](https://create-react-app.dev/docs/running-tests)

Create react app が自動生成する package.json の script 定義は以下のようになっており、
環境変数 `CI` が設定されているか否かで `test` の挙動が変化するようになっている。

```json:package.json
  "scripts": {
    "test": "react-scripts test",
  },
```

- `CI` が定義されていないときには watch モードで動作。ソースコードが変更されるたびに、変更されたソースコードに関連するテストのみ実行される。
- `CI` が定義されているときには、全テストを実行。[Predefined variables reference - GitLab](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) によると GitLab CI/CD 環境でも `CI=true` が設定されている。
- もしくは `yarn test --watchAll=false` として起動すれば、全テストを実行。この機能は後で commit 時に test を実行するために利用する。

## commitlint を husky を使って起動する

参照：
* [commitlint - Lint commit messages](https://commitlint.js.org/#/)
* [commitlint - Lint commit messages > Guide: Local setup](https://commitlint.js.org/#/./guides-local-setup?id=guides-local-setup)

commit log を [semantic versioning](https://semver.org/) に準拠した形に維持するために commitlint を導入する。

```bash
$ yarn add @commitlint/{cli,config-conventional} --dev
$ echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
$ yarn add husky --dev
$ npm set-script prepare "husky install"
$ yarn prepare
$ yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
```

semantic versioning に適合しない commit log を指定して commit しようとしてもエラーとなることが確認できる。

```bash
$ git commit -m'foo:aaa'
yarn run v1.22.11
$ /Users/kmiyashita/gitlab/react-app-on-gitlab-pages/node_modules/.bin/commitlint --edit .git/COMMIT_EDITMSG
⧗   input: foo:aaa
✖   subject may not be empty [subject-empty]
✖   type may not be empty [type-empty]

✖   found 2 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
husky - commit-msg hook exited with code 1 (error)
```

## commit 時に lint, jest を起動する

せっかく husky を導入したので、commit 時には lint, jest が必ずパスすることを確認することにする。

```bash
$ yarn husky add .husky/pre-commit 'yarn lint && yarn test --watchAll=false'
```

確かに、lint と jest が実行されることを確認できた。

```bash
$ git commit -m 'feat: run lint and jest at every commit'
yarn run v1.22.11
$ eslint . --ext .ts,.tsx
✨  Done in 0.58s.
yarn run v1.22.11
$ react-scripts test --watchAll=false
 PASS  src/App.test.tsx
  ✓ renders learn react link (14 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.006 s
Ran all test suites.
✨  Done in 1.56s.
yarn run v1.22.11
$ /Users/kmiyashita/gitlab/react-app-on-gitlab-pages/node_modules/.bin/commitlint --edit .git/COMMIT_EDITMSG
✨  Done in 0.21s.
[main 0a560aa] feat: run lint and jest at every commit
 2 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100755 .husky/pre-commit
```

ただ、開発中の feature branch では lint, jest が一時的にパスしない状態でも commit をして状態保存をしながら開発し、最後に interactive rebase で commit log を整理してから main branch にマージすることも多々あると予想される。それを考えると常に lint, jest がパスしないと commit できないというルールは厳しすぎるかもしれないので、結局以下のように main branch でのみこのルールを発動するようにした。

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

branch="$(git rev-parse --abbrev-ref HEAD)"

if [ "$branch" = "main" ]; then
    yarn lint && yarn test --watchAll=false
fi
```

## Sourcetree から commit したときにも commit hook を動作させる

参考：
* [Launching a macOS application with a custom path](https://korban.net/posts/2019-07-23-launching-mac-os-application-with-custom-path/)
* [Can I change the application icon of an Automator script?](https://apple.stackexchange.com/questions/369/can-i-change-the-application-icon-of-an-automator-script)

ここまでで Terminal から git 操作した時の挙動はうまく設定できたが、Sourcetree から commit をしようとするとうまく動作しない。以下のように yarn が見つからないというエラーが出る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/5657b5d0-b871-d7a1-57fb-258bb3f3dd09.png)

これは、Sourcetree のように Dock / Finder から起動されたアプリにとっては PATH 変数がデフォルト設定のままになっており、自分で `yarn` 等をインストールしたフォルダーを `.zshrc` 等で PATH 変数に追加していてもそれが反映されないことが原因である。

この問題を回避するためには、Automator を利用して下図のように `.zshrc` 等を読み込んでから Sourcetree を起動すれば良い。ある意味、shell script を Dock / Finder に置いていると考えられる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/83519050-938d-4217-ba40-0f8d0ffeb40f.png)

このようにして PATH 設定をした状態の Sourcetree を用いると、下図のように commit 時に lint, jest が起動される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/95cb04ba-4365-59d5-56f2-111428dadc4a.png)