Husky, lint-staged и pre-commit хуки

Контроль качества кода на уровне локального репозитория строится вокруг Git-хуков, которые запускаются в момент выполнения операций commit, push и других событий жизненного цикла репозитория. Pre-commit хуки формируют слой автоматической валидации, который предотвращает попадание некорректного кода в историю проекта до запуска CI.

В проектах на Vite этот подход особенно эффективен из-за высокой скорости сборки и ориентации на современный стек ESM, где типичные проверки (lint, форматирование, базовые тесты) выполняются быстро и могут быть встроены в локальный workflow без заметной задержки.

Git hooks как механизм контроля качества

Git hooks представляют собой скрипты, которые автоматически выполняются при наступлении определённых событий:

  • pre-commit — перед созданием коммита
  • commit-msg — проверка сообщения коммита
  • pre-push — перед отправкой в удалённый репозиторий

На уровне архитектуры это позволяет внедрять слой проверки, не зависящий от CI/CD, и обеспечивать единообразие кода на раннем этапе.

Однако базовый механизм Git неудобен для масштабируемого использования:

  • отсутствует кроссплатформенная стандартизация
  • сложно управлять зависимостями хуков
  • нет декларативной конфигурации
  • отсутствует удобное управление набором задач

Эти ограничения решаются использованием специализированных инструментов.


Husky как слой управления Git hooks

Husky предоставляет инфраструктуру для декларативного управления Git-хуками внутри проекта. Основная цель — сделать хуки частью репозитория и упростить их распространение между разработчиками.

Установка и инициализация

В проектах на Vite Husky обычно устанавливается как dev-зависимость:

npm install -D husky

Инициализация структуры хуков:

npx husky init

После выполнения создаётся директория .husky/, которая становится точкой управления всеми Git hooks проекта.

Структура типичного Vite-проекта после добавления Husky:

project/
 ├─ src/
 ├─ public/
 ├─ vite.config.js
 ├─ package.json
 ├─ .husky/
 │   ├─ pre-commit
 │   ├─ commit-msg

Принцип работы

Husky подключается к Git hooks через внутренние shell-скрипты. Каждый файл в .husky/ соответствует определённому событию Git.

Пример pre-commit:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test

При каждом коммите выполняется указанный набор команд.

Интеграция с npm scripts

В Vite-проектах логика обычно делегируется package.json:

{
  "scripts": {
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "vitest"
  }
}

Husky вызывает уже готовые npm-скрипты, что упрощает поддержку и снижает дублирование логики.


lint-staged как оптимизация проверки изменённых файлов

lint-staged решает ключевую проблему pre-commit хуков — избыточное выполнение линтеров по всему проекту.

Вместо анализа всей кодовой базы выполняется обработка только файлов, добавленных в staging area Git.

Принцип работы

lint-staged получает список staged файлов и фильтрует их по шаблонам, после чего выполняет заданные команды.

Это обеспечивает:

  • минимальное время выполнения
  • фокус на изменениях текущего коммита
  • снижение нагрузки на локальную машину

Конфигурация lint-staged

Установка:

npm install -D lint-staged

Добавление конфигурации в package.json:

{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md}": [
      "prettier --write"
    ]
  }
}

Каждый паттерн соответствует набору файлов, к которым применяются операции.


Связка Husky + lint-staged

На практике эти инструменты работают совместно:

  • Husky отвечает за запуск Git hook
  • lint-staged выполняет выборочную обработку файлов
  • ESLint/Prettier/Vitest выполняют конкретные проверки

Пример .husky/pre-commit:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

В результате commit становится точкой автоматической валидации изменений.


Интеграция с Vite-проектом

Vite ориентирован на модульную архитектуру и быстрый dev server, поэтому pre-commit проверки не должны конфликтовать с его скоростью разработки.

Типичный стек:

  • Vite — сборка и dev server
  • ESLint — статический анализ
  • Prettier — форматирование
  • Vitest — тестирование

ESLint в связке с Vite

ESLint конфигурация для современного ESM-проекта:

export default [
  {
    files: ["src/**/*.{js,ts,vue}"],
    rules: {
      "no-console": "warn",
      "no-unused-vars": "error"
    }
  }
];

Lint-staged вызывает ESLint только на изменённых файлах, снижая время проверки до долей секунды.


Prettier как часть pre-commit pipeline

Форматирование кода в pre-commit этапе устраняет конфликты стиля между разработчиками.

Пример интеграции:

{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "prettier --write"
    ]
  }
}

Prettier выполняется до ESLint или после него в зависимости от стратегии:

  • Prettier → ESLint (часто используется)
  • ESLint → Prettier (реже, при строгих правилах линтера)

Vitest и проверка тестов на уровне коммита

В Vite-экосистеме часто используется Vitest как тестовый раннер.

Пример добавления в lint-staged:

{
  "lint-staged": {
    "*.{js,ts}": [
      "vitest related --run"
    ]
  }
}

Однако запуск тестов в pre-commit требует баланса:

  • быстрые unit-тесты допустимы
  • интеграционные тесты обычно выносятся в CI

commit-msg hook и контроль сообщений

Husky позволяет подключить проверку формата коммитов.

Пример установки commitlint:

npm install -D @commitlint/config-conventional @commitlint/cli

Конфигурация:

export default {
  extends: ["@commitlint/config-conventional"]
};

Husky hook:

npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

Это обеспечивает стандартизацию истории Git.


Производительность pre-commit pipeline

Основной риск pre-commit хуков — замедление процесса коммита.

Факторы оптимизации:

  • использование lint-staged вместо полного lint
  • кэширование ESLint
  • исключение тяжёлых тестов
  • параллельное выполнение задач

Пример оптимизированной конфигурации:

{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --cache --fix",
      "prettier --write"
    ]
  }
}

Ключевой параметр --cache значительно сокращает время повторных проверок.


Monorepo сценарии

В монорепозиториях (Turborepo, Nx) pre-commit hooks требуют дополнительной координации.

Особенности:

  • lint-staged запускается только в пределах изменённых пакетов
  • Husky находится на уровне корня репозитория
  • команды должны учитывать workspace структуру

Пример:

{
  "lint-staged": {
    "packages/*/src/**/*.{js,ts}": [
      "eslint --fix"
    ]
  }
}

Частые проблемы и поведение системы

Хуки не запускаются

Причины:

  • Git hooks не инициализированы
  • отсутствует .husky/ директория в репозитории
  • некорректные права на shell-скрипты

lint-staged не находит файлы

Причины:

  • файлы не добавлены в staging area
  • некорректные glob-паттерны
  • изменения вне отслеживаемых директорий

конфликты с Windows окружением

Возможные проблемы:

  • отсутствие bash-совместимой среды
  • различия в путях
  • CRLF vs LF

Решение обычно связано с настройкой Git:

git config --global core.autocrlf true

Архитектурная роль pre-commit хуков в Vite-проектах

В современной фронтенд-архитектуре pre-commit слой выполняет функцию локального gatekeeper-а, снижая нагрузку на CI и уменьшая количество дефектов, попадающих в удалённый репозиторий.

Комбинация Vite + Husky + lint-staged формирует лёгкую, но эффективную систему контроля качества, где:

  • Vite обеспечивает быстрый dev loop
  • Husky управляет точками входа Git
  • lint-staged минимизирует объём проверяемых данных
  • ESLint/Prettier/Vitest обеспечивают техническую проверку кода