開発効率の限界突破:Husky × lint-staged を極める「ゼロ待ち時間」設計論
多くの開発者が「コミット前にリンターを走らせる」という行為を、単なる品質担保の儀式だと誤解している。しかし、真のDevOpsアーキテクトにとって、これは「開発者の脳のコンテキストスイッチを最小化し、クリーンなコードのみをメインラインに流し込むための極めて高度なパイプライン」である。
全ファイルを走査して数分待たされるCIは、もはや悪である。今回は、ESLintとPrettierをHuskyとlint-stagedに統合し、メモリ消費を最適化しつつ、コミットフローを「爆速」にするための深淵な設定を解説する。
—
1. なぜ「全ファイル走査」は罪なのか:アーキテクチャの本質
大規模なプロジェクトにおいて、`npm run lint` を全ファイルに対して実行するのは、計算リソースとエンジニアの時間の無駄だ。Gitのステージング領域(Index)に存在するファイルのみを対象にすることで、解析コストはファイル数ではなく「変更の差分」に依存するようになる。
ここで重要になるのは、「いかにしてノードプロセスを使い回すか」である。毎回新しくプロセスを立ち上げると、TypeScriptの型チェック(`tsc`)が走る場合、毎回メモリ上の抽象構文木(AST)を再構築することになり、数秒のオーバーヘッドが発生する。
—
2. 最適化された `lint-staged` 設定の実装
単にコマンドを並べるのではなく、「並列実行」と「キャッシュの再利用」を意識した構成にせよ。
// .lintstagedrc.json
{
“.{js,ts,tsx}”: [
“eslint –fix –cache –cache-strategy content”,
“prettier –write”
],
“.{json,md,yml}”: [
“prettier –write”
]
}
- `–cache`: これが肝だ。ESLintは前回の解析結果をキャッシュし、変更がないファイルへの再処理をスキップする。
- `–cache-strategy content`: デフォルトのメタデータ比較ではなく、ファイルの中身のハッシュ値で判定させる。これにより、CI環境とローカルでファイル更新時刻がズレるリスクを排除する。
—
3. HuskyとDockerコンテナ環境の「壁」を突破する
Dockerコンテナ内で開発している場合、`husky install` でフックを生成しても、ホスト側のGitクライアントとコンテナ内のシェル環境の差異により、パスが通らずに失敗することが多い。
これを解決する唯一の正解は、「`.husky/pre-commit` スクリプト内でコンテナの状態を適切にハンドリングすること」だ。
!/usr/bin/env sh
. “$(dirname — “$0″)/_/husky.sh”
ホスト側で実行されているか、Dockerコンテナ内かを判定
if [ -f /.dockerenv ]; then
# コンテナ内の場合はそのまま実行
npx lint-staged
else
# ホスト側の場合はdocker execを使用してコンテナ内の lint-staged を叩く
docker compose exec -T app npx lint-staged
fi
-Tオプション(擬似端末を割り当てない)を忘れてはならない。これを入れないとCI環境でストリームエラーを引き起こす。
—
4. CI/CDパイプラインとの高度な連携:二重チェックの排除
ローカルで `lint-staged` を通したからといって、CIでのチェックを省いてはならない。しかし、CIでは「全ファイル走査」が依然として必要だ。ここで、「CI専用の最適化」を行う。
.github/workflows/ci.yml の抜粋
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
cache: ‘npm’ # キャッシュを活用し、依存関係の解決を爆速に
- run: npm ci
- name: Lint
# CIでは全ファイルを走査するが、–cacheを有効にしてGitHub Actionsのキャッシュ機能と統合する
run: npx eslint . –fix –cache –cache-strategy content
—
5. 伝説的アーキテクトからの「最後の一押し」
このシステムを構築する上で最も見落とされがちなのが、「Prettierの出力とESLintの自動修正の競合」だ。
ESLintの `eslint-config-prettier` を利用していないチームは、今すぐ導入せよ。ESLintはコードのロジックを、Prettierはコードの見た目を司る。両者がルールで衝突すると、`lint-staged` 内で無限ループに近い整形処理が発生し、IDEのプロセスがハングアップする原因となる。
推奨構成の極意:
1. eslint-plugin-import: インポート順序の正規化。
2. eslint-config-prettier: Prettierと競合するESLintルールをすべて無効化する。
3. lint-stagedの実行順序: `eslint –fix` を先に実行せよ。Prettierはあくまで最後の「仕上げ」だ。
実務におけるパフォーマンスハック
大規模プロジェクト(数千ファイル規模)では、`lint-staged` の実行中に `tsc –noEmit` を走らせてはいけない。型チェックは時間がかかるため、それはIDEのバックグラウンド(Language Server)に任せ、コミット前フックでは「構文の正当性」と「フォーマット」のみに集中させるのが、開発者のストレスを減らす唯一の道だ。
この設計が完成したとき、君のチームから「リンターの待ち時間」という概念は消失し、コードレビューは「ロジックの改善」という本質的な議論のみに集中できるようになるだろう。これこそが、開発効率を極限まで引き上げるエンジニアの矜持だ。