Миграция с ESLint 8 на 9

В ESLint 9 повышена минимальная версия Node.js. Поддержка старых рантаймов, которые ещё встречались в экосистеме ESLint 8, прекращена. Основной целевой средой становится актуальная LTS-ветка Node.js, что упрощает поддержку современных возможностей языка и уменьшает количество обходных совместимых реализаций внутри линтера.

При обновлении инфраструктуры CI/CD требуется проверить версии Node.js в пайплайнах, контейнерах Docker и локальных средах разработчиков. Несоответствие версии приводит не к предупреждениям, а к полной невозможности запуска ESLint.


Переход на Flat Config как базовую модель

Ключевое архитектурное изменение ESLint 9 заключается в закреплении flat config как основной и рекомендованной системы конфигурации. Модель .eslintrc считается устаревающей и постепенно выводится из обращения.

Flat config опирается на файл eslint.config.js (или .mjs, .cjs), где конфигурация описывается как массив объектов:

  • каждый объект представляет набор правил для определённого набора файлов;
  • отсутствует наследование через extends в классическом виде;
  • логика конфигурации становится более явной и композиционной.

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

export default [
  {
    files: ["**/*.js"],
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: "module"
    },
    rules: {
      "no-unused-vars": "warn"
    }
  }
];

Переход на flat config меняет саму модель мышления: конфигурация становится декларацией слоёв, а не цепочкой наследования.


Отказ от .eslintrc как основного механизма

В ESLint 9 формат .eslintrc официально считается legacy-подходом. Поддержка сохранена, но с рядом ограничений:

  • новые возможности ESLint сначала реализуются в flat config;
  • часть современных плагинов ориентируется исключительно на eslint.config.js;
  • поведение некоторых резолверов и override-механик отличается.

Миграция с .eslintrc требует ручного переноса логики:

  • extends превращается в импортируемые конфигурационные объекты;
  • overrides разбиваются на отдельные элементы массива;
  • env заменяется на languageOptions.globals.

Типичный фрагмент миграции:

import js from "@eslint/js";

export default [
  js.configs.recommended,
  {
    files: ["**/*.js"],
    rules: {
      eqeqeq: "error"
    }
  }
];

Изменения в системе плагинов

ESLint 9 усиливает строгую модель загрузки плагинов. Плагины теперь должны быть совместимы с flat config-форматом и явно импортироваться.

Ключевые изменения:

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

Ранее распространённый стиль:

{
  "plugins": ["react"]
}

в flat config трансформируется в:

import react from "eslint-plugin-react";

export default [
  {
    plugins: {
      react
    }
  }
];

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


Изменения в CLI и режиме выполнения

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

  • приоритет отдаётся eslint.config.js;
  • автоматическое обнаружение .eslintrc происходит только в режиме совместимости;
  • ускорена инициализация за счёт уменьшения количества шагов резолвинга.

Команда запуска остаётся прежней:

eslint "src/**/*.{js,ts}"

Однако внутренняя логика обработки конфигурации теперь строится вокруг единого дерева flat config, а не цепочки наследуемых JSON-файлов.


Совместимость и режим миграции

Для проектов с крупной историей конфигураций предусмотрен режим совместимости. Он позволяет временно использовать старые конфигурации без полной переписки на flat config.

Типичные ограничения этого режима:

  • невозможность использования части новых возможностей;
  • потенциальные расхождения в поведении правил;
  • деградация производительности при сложных цепочках extends.

Практика миграции обычно включает постепенный переход:

  1. подключение flat config параллельно с .eslintrc;
  2. перенос базовых правил и глобальных настроек;
  3. миграция override-логики;
  4. удаление legacy-конфигурации после стабилизации поведения.

Изменения в резолвинге конфигураций

ESLint 9 пересматривает механизм поиска конфигурационных файлов. Приоритет становится более строгим:

  1. eslint.config.js / mjs / cjs
  2. режим совместимости .eslintrc
  3. fallback-логика для старых сценариев

Это влияет на монорепозитории, где ранее использовалось несколько конфигурационных файлов в разных директориях. Теперь поведение зависит от явной структуры массива конфигураций, а не от автоматического подъёма конфигураций вверх по дереву директорий.


Обновление правил и внутренних API

Часть правил и внутренних API подверглась переработке. Основные изменения касаются:

  • унификации формата контекста правил;
  • отказа от некоторых устаревших утилит внутри RuleTester;
  • стабилизации интерфейсов для плагинов.

Плагины, написанные под ESLint 8, могут требовать обновления, особенно если использовали внутренние API, не входящие в публичный контракт.


Изменения в формате глобальных переменных

Глобальные переменные теперь определяются исключительно через languageOptions.globals. Старый подход через env сохраняется только в режиме совместимости.

Пример:

export default [
  {
    languageOptions: {
      globals: {
        window: "readonly",
        document: "readonly"
      }
    }
  }
];

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


Поведение ignore-файлов

.eslintignore постепенно теряет значение в пользу встроенного механизма игнорирования в flat config. Игнорирование теперь задаётся через отдельные конфигурационные блоки:

export default [
  {
    ignores: ["dist/**", "node_modules/**"]
  }
];

Это объединяет логику конфигурации и исключений в единую систему, устраняя разрозненность настроек.


Особенности миграции TypeScript-проектов

В экосистеме TypeScript изменения ощущаются сильнее из-за зависимости от ESLint-плагинов и парсеров:

  • требуется обновление @typescript-eslint/* до версий, совместимых с ESLint 9;
  • часть старых конфигураций parserOptions.project может вести себя иначе;
  • интеграция с TS project references требует проверки резолвинга путей.

Flat config упрощает подключение TypeScript-конфигураций за счёт явного управления языковыми опциями, но требует более аккуратного описания окружения.


Переработка механизма расширений конфигураций

Механизм extends заменяется композиционной моделью:

  • базовые конфигурации импортируются как JavaScript-объекты;
  • порядок применения определяется порядком в массиве;
  • конфликты правил разрешаются последним объявлением.

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


Изменения в производительности

ESLint 9 демонстрирует улучшения производительности в сценариях с flat config:

  • уменьшено время инициализации;
  • сокращено количество операций чтения файлов конфигурации;
  • оптимизировано кэширование результатов анализа.

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


Рекомендации по структурированию конфигурации после миграции

Flat config предполагает иной подход к архитектуре линтинга:

  • разделение конфигурации по слоям (base, framework, project-specific rules);
  • минимизация дублирования правил;
  • явное управление порядком применения конфигураций;
  • группировка файлов через files и ignores, а не через дерево директорий.

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