Git-hooks без боли: расследуем Husky + lint-staged
Воскресенье. 10:02. Твой коллега Лёша делает «быстрый» хот-фикс, жмёт git commit -m "hotfix"
— и получает стену ошибок ESLint. Паника: «Почему линтер рушит мой рабочий день?»
Разберёмся, что происходит, настроим Husky v9 и lint-staged 15, чтобы — вместо фрустрации — автоматом править код до коммита и пуша, и при этом не тормозить работу новичков.
1. Что такое Git-hook и зачем он нужен
Git-hook — это скрипт, который Git запускает в определённый момент жизненного цикла (pre-commit
, pre-push
, post-merge
и т. д.).
Преимущества:
- ловит опечатки до CI;
- держит репозиторий «чистым» — ни одного кривого форматирования;
- экономит время ревьюеров (фокус на логике, а не на пробелах).
2. Почему Husky v9 считается стандартом-2025
- Лёгкий — всего ≈ 2 kB, без зависимостей;
- Кросс-платформенный — Windows, macOS, Linux;
- Использует
core.hooksPath
, не трогая системные файлы; - Поддерживает все 13 клиентских Git-хуков из коробки.
Важно: Husky v9 перешёл на «zero-install» — он работает, даже если у коллеги нет
npm i
в глобальном кэше.
3. lint-staged: ленимся правильно
lint-staged
запускает задачи только для файлов, добавленных в индекс, а значит:
- линтеры обрабатывают десяток файлов, а не весь проект;
- проверки занимают секунды, не минуты;
- отпадает соблазн отключать линтер «пока что».
4. Пошаговая установка (Node ≥ 18, Git ≥ 2.40)
4.1 Инициализация Husky
# 1) ставим пакеты
pnpm add -D husky@^9 lint-staged@^15
# 2) активируем Husky
npx husky install
# → создаётся папка .husky и настраивается core.hooksPath
Добавь строку в package.json
, чтобы у новичков Husky включался сам:
{
"scripts": {
"prepare": "husky install"
}
}
4.2 Создаём pre-commit-hook
npx husky add .husky/pre-commit "pnpm lint-staged"
В файле .husky/pre-commit
уже прописана команда; права (chmod +x
) выставлены автоматически.
4.3 Конфиг lint-staged
lint-staged.config.js
(TypeScript-тайпинги появились в v15) — удобно для автодополнения.
/** @type {import('lint-staged').Configuration} */
export default {
// Форматируем всё Prettier
"*.{js,ts,jsx,tsx,json,md}": ["prettier --write"],
// Лечим + проверяем только изменённые TS/JS
"*.{js,ts,jsx,tsx}": ["eslint --fix", "eslint"],
};
5. Что делать, если hook «долго думает»
- Проверь фильтры — убедись, что не указал
"*"
без исключений (раньше так ошибались и гоняли линтер по всему проекту). - Отладка — запускай
HUSKY=0 git commit ...
, чтобы временно отключить хуки. - Перенеси тяжёлые тесты на
pre-push
или CI: быстро сохранять работу важнее, чем гонять Jest в pre-commit.
6. Расширяем строгость: pre-push + тесты
# добавим второй хук
npx husky add .husky/pre-push "pnpm dlx vitest run"
Теперь:
- pre-commit = форматирование + линтер (быстро);
- pre-push = юнит-тесты (могут идти дольше);
- CI проверяет e2e — полный цикл.
7. Полезные инструменты рядом
Задача | Инструмент | Коротко |
---|---|---|
Просмотреть, что именно запускает Hook | git config core.hooksPath | Путь к .husky |
Вручную запустить Hook | npm run lint-staged -- --debug | Лог вызовов |
GUI-диагностика Git-хуков | lefthook | Альтернатива Husky |
Hook-агрегация под любой язык | pre-commit (Python) | Аналог Husky без Node |
8. Итоги
- 5 минут настройки → команда коммитит идеально отформатированный код.
- Проверки быстрые (только staged-файлы).
- Настройка прозрачна — никаких скрытых bash-скриптов.
В следующий раз, когда Лёша попробует закоммитить «горячий фикс», Husky вежливо поправит файлы, и тревога превратится в зелёный тик. Преступление плохого кода предотвращено до того, как попало в репозиторий.