Альтернатива: разделённый запуск

Разделение запуска ESLint становится критически важным в больших кодовых базах, где линтинг всего проекта целиком приводит к значительным задержкам, блокирует CI-пайплайны и ухудшает скорость обратной связи при разработке. Основная идея заключается в том, чтобы разбивать процесс анализа кода на независимые части и выполнять их параллельно или избирательно, минимизируя объём обрабатываемых файлов за один запуск.

Самый прямолинейный способ разнести запуск ESLint — ограничение области анализа через файловые шаблоны. Вместо глобального запуска по всему проекту используется несколько отдельных команд.

eslint src/
eslint tests/
eslint scripts/

Такой подход формирует несколько независимых процессов, каждый из которых работает в собственной области ответственности. При этом уменьшается нагрузка на парсер, особенно в проектах с большим количеством TypeScript-файлов или сложной конфигурацией правил.

Более детализированное разделение выполняется через glob-шаблоны:

eslint "src/**/*.ts"
eslint "src/**/*.tsx"
eslint "config/**/*.js"

Разделение по расширениям файлов снижает вероятность избыточной проверки нерелевантных файлов и позволяет более точно настраивать правила под конкретные типы кода.

Разделённый запуск через пакетные скрипты

В среде Node.js часто используется разбиение линтинга на набор npm-скриптов, каждый из которых отвечает за свою часть проекта:

{
  "scripts": {
    "lint:core": "eslint src/core",
    "lint:ui": "eslint src/ui",
    "lint:tests": "eslint tests"
  }
}

Далее такие задачи могут выполняться независимо или в параллельном режиме. Подобная структура особенно эффективна в монорепозиториях, где разные области кода имеют разные правила и зависимости.

Параллельное выполнение процессов ESLint

Для ускорения обработки применяется параллельный запуск нескольких процессов ESLint. Это особенно актуально на многоядерных системах, где последовательный запуск не использует вычислительные ресурсы полностью.

npm-run-all --parallel lint:core lint:ui lint:tests

Или через GNU parallel:

ls -d src/* | parallel eslint

Параллелизация требует осторожности при работе с общими кешами, поскольку одновременная запись может приводить к конфликтам, если не настроен корректный cache location.

Использование кеширования как основы разделения

Кеширование ESLint существенно снижает стоимость повторных запусков. При разделённой стратегии кеш становится связующим элементом между независимыми частями анализа.

eslint src --cache --cache-location node_modules/.cache/eslint/core
eslint tests --cache --cache-location node_modules/.cache/eslint/tests

Разделение кеша по областям позволяет избежать перезаписи и обеспечивает независимость результатов между разными частями проекта.

Инкрементальный запуск через git diff

Одним из наиболее эффективных подходов является линтинг только изменённых файлов. В этом случае ESLint запускается на основе списка файлов, полученных из системы контроля версий.

eslint $(git diff --name-only --diff-filter=ACMRTUXB origin/main | grep "\.js$\|\.ts$")

Такой метод часто используется в CI-системах, где нет необходимости повторно анализировать неизменённый код. Он значительно снижает время проверки при частых коммитах.

Для более строгого разделения можно дополнительно группировать изменённые файлы:

git diff --name-only origin/main | split -l 50 - chunk_

И затем запускать ESLint для каждого фрагмента отдельно.

Шардирование в CI-пайплайнах

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

Пример логики разделения:

FILES=$(git ls-files "*.ts")
echo "$FILES" | awk "NR % 4 == 0"
echo "$FILES" | awk "NR % 4 == 1"

Каждый CI-агент получает свой шард и запускает ESLint только на нём. Это обеспечивает линейное масштабирование проверки при увеличении количества агентов.

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

В монорепозиториях разделённый запуск становится архитектурной необходимостью. ESLint применяется к независимым пакетам, каждый из которых может иметь собственную конфигурацию.

eslint packages/ui
eslint packages/api
eslint packages/shared

Инструменты управления монорепозиториями дополнительно автоматизируют этот процесс, анализируя граф зависимостей и определяя, какие пакеты затронуты изменениями.

При этом важно учитывать, что разные пакеты могут использовать разные версии зависимостей, что влияет на поведение правил ESLint и плагинов.

Использование Nx и Turborepo для разделённого линтинга

Системы оркестрации задач позволяют автоматически разбивать выполнение ESLint на независимые задачи.

В Nx задачи линтинга определяются как часть графа зависимостей:

nx run-many --target=lint --all

При этом система выполняет только те задачи, которые затронуты изменениями.

В Turborepo используется кэширование на уровне задач:

turbo run lint

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

Разделение через pre-commit хуки

В локальной разработке применяется стратегия запуска ESLint только для изменённых файлов перед коммитом. Это позволяет распределить нагрузку на множество мелких запусков вместо одного большого.

npx lint-staged

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

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

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

Изоляция конфигурации при разделённом запуске

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

  • разные .eslintrc
  • разные parserOptions
  • разные плагины

Поэтому запуск в отдельных частях проекта часто требует явного указания working directory:

eslint --config packages/ui/.eslintrc.js packages/ui

Или через параметр --cwd, чтобы избежать конфликтов путей и относительных импортов.

Типовые проблемы разделённого запуска

Разделение ESLint на независимые части создаёт ряд технических сложностей.

Одной из них является дублирование вычислений: одинаковые плагины и парсеры загружаются многократно в разных процессах, увеличивая общее потребление памяти.

Другой проблемой становится несогласованность правил между шардированными частями. При изменении конфигурации в одной зоне проекта возможны расхождения с другими зонами, если кеш или CI-агенты используют устаревшие данные.

Также критичным фактором становится порядок выполнения. В некоторых случаях результаты линтинга могут зависеть от порядка загрузки файлов, особенно при использовании нестандартных плагинов.

Комбинированные стратегии разделения

На практике используется сочетание нескольких подходов:

  • разделение по пакетам в монорепозитории
  • кеширование по зонам проекта
  • параллельный запуск CI-шардов
  • фильтрация через git diff
  • pre-commit запуск только изменённых файлов

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