はじめに
これまで、Gitフックの管理ツールは husky を使用していましたが、近年 Lefthook も注目を集めています。
今回は、両者の特徴・違いを整理し、プロジェクトに応じた使い分けの指針を調べてみました。
huskyとは
まず、husky とはどのようなものでしょうか。
husky は、Node.jsプロジェクト向けのGitフック管理ツールです。
.husky/ ディレクトリにフック用のシェルスクリプトを配置することでフックを可能にしています。
特徴
| 項目 | 内容 |
|---|---|
| 言語 | JavaScript(Node.js必須) |
| 設定形式 | シェルスクリプト(.husky/pre-commit など) |
| 実行方式 | 逐次実行 |
| インストールサイズ | 小(依存少) |
| コミュニティ | 非常に大きい |
ユースケース
- Node.js / フロントエンドのみのプロジェクト
- lint-stagedと組み合わせたステージファイルへのlint実行
- チーム全員がNode.js環境を持っていることが前提のプロジェクト
Lefthookとは
Lefthook は、Go製のGitフック管理ツールです。
lefthook.yml にYAML形式で設定を記述し、単一バイナリで動作するため言語に依存しません。並列実行やフィルタリングなど高機能な設定が可能です。
特徴
| 項目 | 内容 |
|---|---|
| 言語 | Go(Node.js不要) |
| 設定形式 | YAML(lefthook.yml) |
| 実行方式 | 並列実行対応 |
| インストールサイズ | バイナリ1つ(軽量・高速) |
| コミュニティ | 成長中 |
ユースケース
- 複数言語が混在するモノレポ
- Gitフックの処理を高速化したいプロジェクト
- Node.js環境に依存したくないバックエンドプロジェクト
導入手順
まずはそれぞれのインストール手順を見ていきましょう。
husky
インストール
npm install --save-dev husky
npx husky init
init コマンドにより .husky/ ディレクトリと pre-commit のサンプルが生成されます。
設定
package.json に prepare スクリプトを追加します(init 実行時に自動追加されます)。
{
"scripts": {
"prepare": "husky"
}
}
Lefthook
インストール
npm経由でインストールする場合:
npm install --save-dev @evilmartians/lefthook
npx lefthook install
Homebrewの場合:
brew install lefthook
lefthook install
設定
プロジェクトルートに lefthook.yml を作成します。
pre-commit:
parallel: true
commands:
lint:
glob: "*.{ts,tsx,js,jsx}"
run: npx eslint {staged_files}
typecheck:
run: npx tsc --noEmit
実行例
次に実際の使用方法を比較します。
huskyの基本的な使い方
.husky/pre-commit に実行したいコマンドを記述します。
npx lint-staged
lint-staged と組み合わせてステージされたファイルだけにlintを実行する構成が一般的です。
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
Lefthookの基本的な使い方
lefthook.yml だけで完結します。{staged_files} プレースホルダーでステージファイルを参照できます。
pre-commit:
parallel: true
commands:
eslint:
glob: "*.{ts,tsx}"
run: npx eslint --fix {staged_files}
prettier:
glob: "*.{ts,tsx,css}"
run: npx prettier --write {staged_files}
commit-msg:
commands:
commitlint:
run: npx commitlint --edit {1}
応用例:Lefthookのスキップ設定
特定のコマンドをCI環境でスキップしたい場合は、環境変数で制御できます。
pre-commit:
commands:
lint:
skip:
- ref: main
run: npx eslint {staged_files}
または実行時に個別スキップ:
LEFTHOOK_EXCLUDE=lint git commit -m "skip lint"
速度比較
LefthookはHuskyに比べ、高速であるとされています。
それには大きく2つの理由があるようです。
1. Go製シングルバイナリ
huskyはNode.jsランタイムに依存するため、フック実行のたびにNode.jsの起動コストが発生します。一方、LefthookはGoでコンパイルされたシングルバイナリであり、ランタイムの起動オーバーヘッドがありません。
2. コマンドの並列実行
huskyはデフォルトでコマンドを逐次実行します。
Lefthookは parallel: true を設定するだけでコマンドを並列実行でき、複数のlintやチェックを同時に走らせることができます。
pre-commit:
parallel: true # ESLint・Prettier・型チェックを同時実行
commands:
eslint:
glob: "*.{ts,tsx}"
run: npx eslint {staged_files}
prettier:
glob: "*.{ts,tsx,css}"
run: npx prettier --write {staged_files}
typecheck:
run: npx tsc --noEmit
huskyで同等の並列実行を実現するには lint-staged や concurrently などの追加パッケージが必要です。
実測値について
現時点では公開されている詳細なベンチマーク数値は少ないものの、実際に計測された方の記事を見ると、明らかに速度に差があります。特にチェック項目が多いプロジェクトほど、並列実行の恩恵が大きくなるようです。
huskyとLefthookの比較まとめ
| 観点 | husky | Lefthook |
|---|---|---|
| 必要環境 | Node.js必須 | 不要(バイナリ単体) |
| 設定のシンプルさ | シェルスクリプトで直感的 | YAMLで一元管理 |
| 並列実行 | 非対応(lint-staged等で補完) | ネイティブ対応 |
| 実行速度 | 普通 | 高速(Go製 + 並列実行) |
| 言語非依存性 | 低い | 高い |
| エコシステム | 非常に充実 | 成長中 |
| モノレポ対応 | 設定が煩雑になりやすい | 得意 |
huskyからLefthookへの移行
ここではHuskyからLefthookの移行手順を見ていきます。
移行手順
# 1. huskyをアンインストール
npm uninstall husky
# 2. .huskyディレクトリを削除
rm -rf .husky
# 3. package.jsonのprepareスクリプトを削除
# "prepare": "husky" を削除する
# 4. Lefthookをインストール
npm install --save-dev @evilmartians/lefthook
# 5. 【重要】huskyが設定したhooksPathをリセット
git config --unset core.hooksPath
# 6. Lefthookをインストール(.git/hooksにフックを配置)
npx lefthook install
移行時の落とし穴:core.hooksPath の問題
huskyを npx husky init で初期化すると、.git/config に以下の設定が書き込まれます。
[core]
hooksPath = .husky/_
この設定が残ったまま lefthook install を実行すると、Lefthookのフックスクリプトが .husky/_ 以下に配置されてしまい、正常に動作しません。
必ず git config --unset core.hooksPath でリセットしてから lefthook install を実行してください。
lefthook-local.yml による個人設定の共存
Lefthookの便利な機能として、プロジェクト設定と個人設定を共存させる仕組みがあります。
lefthook-local.yml を作成すると、チームの lefthook.yml の設定を個人的にオーバーライドできます。
このファイルを .gitignore に追加しておけば、個人の設定がリポジトリに影響しません。
# 個人の開発環境でだけ typecheck をスキップする
pre-commit:
commands:
typecheck:
skip: true
lefthook-local.yml
huskyでは git commit --no-verify で全フックをスキップするしかありませんでしたが、Lefthookではコマンド単位での制御が可能です。
モノレポでの設定
Lefthookは root オプションでサブパッケージのディレクトリを指定できます。
pre-commit:
parallel: true
commands:
lint-frontend:
root: "packages/frontend/"
glob: "*.{ts,tsx}"
run: npx eslint {staged_files}
lint-backend:
root: "packages/backend/"
glob: "*.go"
run: golangci-lint run {staged_files}
huskyでモノレポに対応する場合は複数のシェルスクリプトと条件分岐が必要になりますが、Lefthookでは root オプションで簡潔に記述できます。
便利ですねぇ。
使い分けの指針
huskyを選ぶケース
- Node.js / フロントエンド専用のプロジェクト
- lint-staged との組み合わせがすでに定着している
- チームメンバーが husky に慣れており、移行コストをかけたくない
Lefthookを選ぶケース
- 複数言語が混在するプロジェクトやモノレポ
- Gitフックの実行速度を改善したい
- Node.jsに依存しないシンプルな構成にしたい
- 設定をYAMLで一元管理したい
どちらも活発にメンテナンスされており、機能面での差は縮まっているようです。
とは言え速度に差はあるので、新規プロジェクトであれば Lefthook を導入すればいいのかなぁと思います。
おわりに
個人開発などの小規模プロジェクトであればどちらでもよいのかもしれませんが、大規模、モノレポのプロジェクトの場合はLefthookを入れるのが無難なのかなと思いました。
とりあえず、新規で開発する際はLefthookを入れようと思います。
それでは。
参考文献