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

В проектах с JavaScript и TypeScript линтинг с помощью ESLint часто становится одной из самых затратных операций в цепочке CI/CD и локальной разработки. По мере роста кодовой базы проверка всего проекта на каждый коммит или запуск пайплайна приводит к увеличению времени ожидания и снижению эффективности разработки. Оптимизация достигается за счёт выполнения линтинга только над изменёнными файлами, что реализуется через связку Git hooks и утилиты lint-staged.

lint-staged выполняет фильтрацию файлов, добавленных в staging area Git, и запускает заданные команды только над этим набором. В сочетании с ESLint это позволяет проверять строго ограниченный набор файлов перед коммитом, не затрагивая всю кодовую базу.


Модель работы staged-файлов в Git

Git разделяет состояние файлов на несколько зон:

  • рабочая директория (working directory)
  • индекс (staging area)
  • история коммитов

lint-staged использует именно staging area как источник правды. В неё попадают файлы после выполнения git add. Такой подход обеспечивает детерминированность: проверяются только те изменения, которые фактически планируются к коммиту.

Ключевой принцип:

линтинг выполняется только над файлами, находящимися в индексе Git


Архитектура lint-staged

lint-staged представляет собой оркестратор команд, который:

  1. Получает список staged-файлов
  2. Фильтрует их по glob-шаблонам
  3. Группирует файлы по соответствующим задачам
  4. Выполняет команды параллельно или последовательно
  5. При необходимости повторно добавляет исправленные файлы в staging

Типичный сценарий взаимодействия с ESLint:

  • staged-файлы с расширением .js, .ts, .jsx, .tsx
  • запуск eslint --fix
  • возврат исправленных файлов обратно в индекс

Интеграция ESLint и lint-staged

Основная схема интеграции строится вокруг автоматического запуска ESLint перед коммитом.

Установка зависимостей

В типичном проекте используется следующая связка:

  • ESLint — механизм статического анализа
  • lint-staged — обработка staged-файлов
  • Husky — управление Git hooks

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

Вариант через package.json

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "git add"
    ]
  }
}

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


Вариант через отдельный конфиг

Файл .lintstagedrc.js:

module.exports = {
  "*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "git add"
  ]
};

Использование JS-конфига позволяет динамически изменять поведение, подключать условия и переиспользовать логику.


Git hooks и Husky

lint-staged не работает самостоятельно в контексте Git событий. Для автоматического запуска используется Husky, который подключает hook pre-commit.

Инициализация Husky

npx husky install

Добавление hook:

npx husky add .husky/pre-commit "npx lint-staged"

Механика работы:

  1. Git инициирует commit
  2. Срабатывает pre-commit hook
  3. Запускается lint-staged
  4. Выполняется ESLint только на staged-файлах
  5. При успешной проверке commit продолжается

Поведение ESLint в режиме lint-staged

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

  • --fix — автоматическое исправление проблем

Команда:

eslint --fix file.js

Особенности поведения:

  • исправления записываются в файлы
  • изменения не теряются благодаря повторному git add
  • ошибки, не поддающиеся автоматическому исправлению, блокируют commit

Фильтрация файлов и glob-выражения

lint-staged использует glob patterns для маршрутизации файлов.

Примеры:

JavaScript и TypeScript

"*.{js,ts}": "eslint --fix"

Разделение по типам файлов

{
  "*.{js,ts}": "eslint --fix",
  "*.{css,scss}": "stylelint --fix",
  "*.{json,md}": "prettier --write"
}

Такая структура позволяет комбинировать разные инструменты линтинга в одном pre-commit процессе.


Производительность и оптимизация

Основное преимущество lint-staged заключается в сокращении объёма обрабатываемых данных.

Без lint-staged

  • ESLint анализирует весь проект
  • время растёт линейно от размера кодовой базы

С lint-staged

  • анализируются только изменённые файлы
  • время выполнения зависит от количества staged-файлов

Ключевой эффект:

снижение времени pre-commit с секунд до миллисекундного диапазона в небольших изменениях


Повторное добавление файлов в индекс

После выполнения ESLint с --fix возможна модификация файлов. lint-staged автоматически управляет этим через:

git add

или встроенную опцию --stash/--diff режимов.

Без повторного добавления изменения не попадут в commit, что приведёт к рассинхронизации состояния.


Обработка ошибок

Если ESLint возвращает ненулевой код выхода:

  • выполнение lint-staged останавливается
  • commit блокируется
  • изменения остаются в рабочей директории

Это обеспечивает механизм предотвращения попадания невалидного кода в репозиторий.


Работа в монорепозиториях

В монорепозиториях (npm workspaces, Turborepo, Nx) lint-staged применяется к локально изменённым пакетам.

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

  • фильтрация происходит на уровне git diff
  • ESLint должен быть настроен с учётом root конфигурации или overrides
  • возможна необходимость указания разных конфигов для пакетов

Пример структуры:

{
  "packages/*/*.{js,ts}": "eslint --fix"
}

Комбинирование с Prettier и другими инструментами

Часто ESLint используется совместно с Prettier, где задачи разделяются:

  • ESLint — логика и ошибки кода
  • Prettier — форматирование

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

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

Порядок выполнения критичен: ESLint выполняется до форматирования, чтобы избежать конфликтов правил.


Кэширование ESLint

Для ускорения обработки применяется ESLint cache:

eslint --fix --cache

Файл .eslintcache позволяет:

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

Однако в связке с staged-файлами кэш даёт меньший эффект, так как набор файлов уже ограничен Git.


Типичные проблемы и ограничения

Частичное форматирование

Если часть файлов не попала в staging, возникает несогласованность стиля между коммитами.

Конфликты инструментов

Одновременное использование ESLint и Prettier без согласованной конфигурации приводит к циклическим изменениям файлов.

Большие бинарные diff

Включение неподходящих файлов в glob (например, build artifacts) увеличивает время обработки.

Несинхронизированные hooks

При отсутствии Husky или аналогов lint-staged не запускается автоматически, что снижает эффективность контроля качества.


Поведение в CI/CD

lint-staged ориентирован на локальные pre-commit проверки, однако может использоваться в CI с адаптацией:

  • вместо staged-файлов используются изменённые файлы между ветками
  • ESLint запускается в режиме diff-based анализа

Однако основная ценность сохраняется именно в локальной разработке, где важно минимизировать задержки перед коммитом.


Стратегия внедрения в проект

Типовая последовательность внедрения:

  • установка ESLint и базовой конфигурации
  • добавление lint-staged
  • подключение Husky pre-commit hook
  • настройка glob-правил для файлов проекта
  • интеграция Prettier при необходимости
  • включение --fix для автоматических исправлений

Результатом становится система контроля качества, встроенная в процесс коммита, работающая исключительно с изменёнными файлами и минимизирующая избыточные вычисления.