以前から husky と lint-staged を使ってコミット時にリンターを実行していたのですが、husky
のバージョンがv7に上がってから、設定方法が少し変わり、pakcage.json
だけで完結しなくなったので、改めて設定方法を紹介します。
インストールと初期設定
自力でゼロから設定するのは結構難しいですが、husky
とlint-staged
をインストールして、サンプル初期設定を作成してくれる便利なコマンドが提供されていますので、これを実行します。
npx mrm@2 lint-staged
実行結果は以下の通りです。
-
package.json
に以下が追加される-
husky
の初期化スクリプトを実行する設定が"scripts": { "prepare": "husky install" },
=>
npm install
などを実行する際に自動的にhusky install
が実行されるようになる -
husky
とlint-staged
の依存関係"devDependencies": { "husky": ">=6", "lint-staged": ">=10", },
-
lint-staged
のサンプル設定"lint-staged": { "*.{js,ts}": "eslint --cache --fix", "*.{css,scss}": "stylelint --fix", "*.{js,ts,css,md}": "prettier --write" }
=> GIT でステージング済みのファイルとマッチする場合に、右側のコマンドが実行される設定になっています
=> このままだとリントだけでなく、フォーマットまで実行されるので、個人的にはやりすぎな気はします
=>yarn lint-staged
で直接lint-staged
を実行することもできます
-
-
husky
の各種ファイルが生成される- フォルダ構成
.husky ├── _ │ ├── .gitignore │ └── husky.sh # git管理外 └── pre-commit # pre-commit hook の定義
-
pre-commit
の中身はただのシェル#!/bin/sh . "$(dirname "$0")/_/husky.sh" npx lint-staged
- フォルダ構成
-
.git/config
の設定が変更される[core] hooksPath = .husky
=> Git Hook のシェルが格納されるフォルダが先ほど作成された
.husky
フォルダに変更されています
上記設定により、コミット時にlint-staged
の定義に従って、リント+フォーマットが自動実行されるようになりました。
lint-stagedを直接実行
husky
を使わず、lint-staged
を直接実行してみます。
README.md
をステージングして試してみました。
git add README.md
yarn lint-staged
yarn run v1.22.17
$ /Users/xxx/xxx/xxx/node_modules/.bin/lint-staged
# 実行中
✔ Preparing...
❯ Running tasks...
↓ No staged files match *.{js,ts} [SKIPPED]
↓ No staged files match *.{css,scss} [SKIPPED]
❯ Running tasks for *.{js,ts,scss,md}
⠙ prettier --write
◼ Applying modifications...
◼ Cleaning up...
# 実行後
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
✨ Done in 1.99s.
lint-staged
の三つの定義のうち、*.{js,ts}
と*.{css,scss}
にはマッチしなかったのでスキップされていますが、
*.{js,ts,scss,md}
にはマッチしているので、prettier --write
が実行されていることがわかります。
husky経由でlint-stagedを実行
次に、README.md
をそのままコミットして、lint-staged
が実行されるかを試してみました。
全くlint-staged
を実行した時と全く同じログが表示されて、lint-staged
が実行され、今回は問題がひとつもなかったのでそのままコミット完了しました。
git add README.md
git commit -m "change readme"
# 実行中
✔ Preparing...
❯ Running tasks...
↓ No staged files match *.{js,ts} [SKIPPED]
↓ No staged files match *.{css,scss} [SKIPPED]
❯ Running tasks for *.{js,ts,scss,md}
⠙ prettier --write
◼ Applying modifications...
◼ Cleaning up...
# 実行後
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[master 43032fd] change readme
1 file changed, 9 insertions(+), 2 deletions(-)
次に、リントエラーが発生するケースで試してみます。
src/jest/foo.ts
に以下のように、array-callback-return エラーが発生するケースを実装しました。
['a', 'b', 'c'].reduce((memo, item, index) => {
memo[item] = index;
}, {});
これをステージングしてコミットすると、以下のようにリントエラーが発生して、コミットが失敗しました。
git add src/jest/foo.ts
git commit -m "add array-callback-return case"
✔ Preparing...
⚠ Running tasks...
❯ Running tasks for *.{js,ts}
✖ yarn eslint --cache --fix [FAILED]
↓ No staged files match *.{css,scss} [SKIPPED]
✔ Running tasks for *.{js,ts,scss,md}
↓ Skipped because of errors from tasks. [SKIPPED]
✔ Reverting to original state because of errors...
✔ Cleaning up...
✖ eslint --cache --fix:
error Command failed with exit code 1.
yarn run v1.22.17
$ eslint --cache --fix /Users/xxx/xxx/xxx/src/jest/foo.ts
/Users/xxx/xxx/xxx/src/jest/foo.ts
3:44 error Array.prototype.reduce() expects a return value from arrow function array-callback-return
✖ 1 problem (1 error, 0 warnings)
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
husky - pre-commit hook exited with code 1 (error)
ここまでの検証で、設定としてはうまく動いていることが確認できました。
あとは、実運用に即したかたちに設定を調整していきたいと思います。
最終的な設定
リントやフォーマットは、CI で実行したいケースもあると思いますので、まずは yarn スクリプトとして定義したいと思います。
自分の場合は、package.json
に以下のような形で定義しました。
"scripts": {
// リント
"lint": "yarn run lint:prettier && yarn run lint:es && yarn run lint:style ",
"lint:prettier": "prettier --check --loglevel=warn '**/*.{js,ts,scss,md}'",
"lint:es": "eslint --max-warnings=0 '**/*.{js,ts}'",
"lint:style": "stylelint '**/*.{css,scss}'",
// フォーマット
"format": "yarn run format:prettier && yarn run format:es && yarn run format:style",
"format:prettier": "prettier --check --write --loglevel=warn '**/*.{js,ts,scss,md}'",
"format:es": "eslint --max-warnings=0 --fix '**/*.{js,ts}'",
"format:style": "stylelint --fix '**/*.{css,scss}'"
},
=> prettier --check
およびeslint --max-warnings=0
オプションにより、ワーニングがあればexit code
が0
以外になるように設定してます。
その上で、lint-staged
からは、yarn スクリプトを呼び出す形で設定しました。
"lint-staged": {
"*.{js,ts}": "yarn lint:es",
"*.{css,scss}": "yarn lint:style",
"*.{js,ts,scss,md}": "yarn lint:prettier"
}
その他
SourceTreeでコミットする際の注意点
SourceTree などでコミットする場合、以下のようなエラーが発生することがあります。
.husky/pre-commit: line 4: npx: command not found
こちらは、ISSUE が報告されており、対応方法が公式ページに紹介されています。
原因はSourceTree から実行される場合、ログインシェルではないのでnpx
にパスが通ってないためです。
~/.huskyrc
を作成し、この中でnpx
にパスを通してあげればOKです。自分の場合はnvm
でnode
をインストールしているので、公式に紹介されている通りに設定すれば大丈夫でした。
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
list-stagedの条件マッチング
lint-staged
のマッチングにはmicromatchが使われているので、書き方がわからない時はここを参照しましょう。
例えば、src
と__tests__
フォルダの.js
と.ts
ファイルだけリントを実行したい場合は、以下のような設定になります。
"lint-staged": {
"(src|__tests__)/**/*.(js|ts)": "yarn lint:es"
}
なぜ v7から設定方法が変わったか
husky
の作者がこちらで解説してくれてます。
https://blog.typicode.com/husky-git-hooks-javascript-config/
要約すると、以下のような感じだと思います。(パーっと読んで意訳してるので間違ってるかも)
- 元々は全ての
git-hook
で.huskyrc.js
を呼び出して処理していたが、これだと不要なケースでもnode
が起動されていた。(昔の Git だと必要な場合だけ起動することはできなかった) - Git のバージョンが
2.9
でcore.hooksPath
が導入され、.git/hooks/
以外のフォルダを指定できるようになった -
/.husky
配下に必要なgit-hook
用のシェル(例えばpre-commit
)を作成することで、必要なケースだけシェルを実行できるようになった
ただし、内部的には効率的になったかもしれませんが、専用フォルダを作ってそこにgit-hook
用のファイルを配置することには反対意見もあったようです。やはり、package.json
だけで設定が完成する方が嬉しいですしね。