今回やること
この記事では、Husky + lint-staged のpre-commit構成を、Lefthookへ移行する流れを整理します。
pre-commit hookは毎日の開発で何度も動くため、数秒の改善でも体感しやすいです。
CI/CDを組めるだけでなく、ローカルの開発体験まで速くできると、実務でも評価されやすくなります。
前提のHusky構成
よくある構成です。
npm install -D husky lint-staged
npx husky init
.husky/pre-commit:
npx lint-staged
package.json:
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
}
この構成は分かりやすく、最初の導入には向いています。ただし、プロジェクトが大きくなると、npx、Node.jsプロセス起動、直列実行、対象ファイルの扱いで遅く感じることがあります。
Lefthookを入れる
Lefthookを開発依存に追加します。
npm install -D lefthook
初期化します。
npx lefthook install
lefthook.yml を作ります。
pre-commit:
parallel: true
commands:
eslint:
glob: "*.{ts,tsx,js,jsx}"
run: npx eslint --fix {staged_files}
stage_fixed: true
prettier:
glob: "*.{ts,tsx,js,jsx,json,md,css}"
run: npx prettier --write {staged_files}
stage_fixed: true
parallel: true により、独立したcommandを並列に動かせます。
package managerに合わせる
pnpmを使っているなら、npx より pnpm exec に寄せます。
pre-commit:
parallel: true
commands:
eslint:
glob: "*.{ts,tsx,js,jsx}"
run: pnpm exec eslint --fix {staged_files}
stage_fixed: true
prettier:
glob: "*.{ts,tsx,js,jsx,json,md,css}"
run: pnpm exec prettier --write {staged_files}
stage_fixed: true
npmなら npx、pnpmなら pnpm exec、bunなら bunx など、プロジェクトのpackage managerに揃えます。
変更ファイルだけ対象にする
pre-commitでは、全ファイルlintではなく、staged filesだけを対象にします。
run: pnpm exec eslint --fix {staged_files}
これにより、変更していない大量のファイルを毎回チェックせずに済みます。
全体チェックはPR CIで行います。
| 場所 | 対象 |
|---|---|
| pre-commit | staged files |
| PR CI | 全体のlint / typecheck / test |
| main CI | build / deploy / 重い検証 |
typecheckをpre-commitに入れすぎない
TypeScriptの tsc --noEmit は、変更ファイルだけで完結しにくく、時間もかかりやすいです。
pre-commitに入れると、commitのたびに待ち時間が増えます。
おすすめ:
pre-commit:
format
lint staged files
pre-push or PR CI:
typecheck
test
build
typecheckは重要ですが、実行場所を選びます。
実行時間を測る
移行前後で時間を測ります。
Husky側:
time git commit -m "test"
Lefthook側:
time git commit -m "test"
実際にはcommitを作りたくない場合、テスト用ブランチや小さな変更で確認します。
READMEやPRには、次のように書けます。
## pre-commit高速化
Before:
- Husky + lint-staged
- eslint / prettier を直列気味に実行
- 体感 8-10秒
After:
- Lefthookへ移行
- staged files対象
- eslint / prettierを並列実行
- 体感 3-5秒
数値は必ず自分の環境で測ったものを書きます。
Huskyを削除する
移行後に問題なければ、Husky関連を削除します。
npm uninstall husky lint-staged
.husky/ ディレクトリも不要なら削除します。
ただし、チーム開発では一気に消す前に、READMEやセットアップ手順を更新します。
確認すること:
- 新規clone後に
lefthook installされるか - package.jsonのprepare scriptが必要か
- CIではhookに依存していないか
- メンバーの環境で同じように動くか
CIではhookに依存しない
pre-commit hookは、ローカルの補助です。CIの代わりではありません。
開発者は --no-verify でhookを飛ばせます。環境差でhookが動かないこともあります。
そのため、PR CIでも必ずチェックします。
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
ローカルhookは早くフィードバックするため、CIは最終確認のため、と役割を分けます。
よくある詰まり
| 症状 | 原因 |
|---|---|
| hookが動かない | lefthook install していない |
| 修正されたファイルがstageされない | stage_fixed: true がない |
| ファイル名に空白があると失敗 | コマンドの渡し方を確認 |
| typecheckが遅い | pre-commitではなくPR CIへ移す |
| Windowsで動かない | shell依存の書き方を避ける |
移行時は、MacだけでなくWindowsやCI環境も意識します。
まとめ
Husky + lint-staged は導入しやすい構成です。一方、pre-commitの速度や並列実行を重視するなら、Lefthookへの移行は有力です。
- LefthookはGo製で起動が軽い
- commandを並列実行しやすい
- staged filesだけを対象にできる
- typecheckや重いtestはpre-commitに詰め込みすぎない
- 移行前後の時間を測る
- CIでも同じ品質チェックを必ず行う
「hookを入れた」だけでなく、「速くして開発体験を改善した」と説明できる状態を目指します。