Миграция с legacy config на flat config

Переход на flat config в ESLint означает отказ от традиционной модели конфигурации на базе .eslintrc.* в пользу явного JavaScript-конфига, где все правила, плагины и настройки описываются в одном массиве объектов. Это изменение устраняет неявные механизмы наследования и сложную систему каскадирования конфигураций, делая поведение линтера более предсказуемым.

В legacy-конфигурации использовались файлы .eslintrc, .eslintrc.json, .eslintrc.js, .eslintrc.yml, а также механизмы extends, overrides, env, globals. Flat config заменяет это единым файлом eslint.config.js, где каждая конфигурация — это объект с явными полями.


Базовая структура flat config

Flat config представляет собой массив конфигурационных объектов:

// eslint.config.js
export default [
  {
    files: ["**/*.js"],
    rules: {
      semi: "error",
      "no-unused-vars": "warn"
    }
  }
];

Каждый объект описывает:

  • к каким файлам применяется конфигурация (files)
  • какие плагины используются (plugins)
  • какие правила активны (rules)
  • дополнительные настройки (parser, languageOptions и др.)

Ключевое отличие от legacy config

В legacy конфигурации поведение строилось на неявном объединении:

  • базовые конфиги через extends
  • перекрытия через overrides
  • глобальные настройки через env
  • автоматическое объединение правил из разных источников

Flat config заменяет это на явную композицию массивов, где порядок элементов имеет значение.


Замена extends

В legacy:

{
  "extends": ["eslint:recommended", "plugin:react/recommended"]
}

В flat config:

import js from "@eslint/js";
import react from "eslint-plugin-react";

export default [
  js.configs.recommended,
  react.configs.recommended
];

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


Работа с plugins

В legacy plugins регистрировались строками:

{
  "plugins": ["react"]
}

В flat config плагины импортируются как модули:

import react from "eslint-plugin-react";

export default [
  {
    files: ["**/*.jsx"],
    plugins: {
      react
    },
    rules: {
      "react/jsx-uses-react": "error"
    }
  }
];

Ключевое изменение заключается в том, что больше нет строковых идентификаторов — только явные ссылки на объекты.


languageOptions вместо parserOptions и env

Flat config объединяет несколько legacy-полей в languageOptions.

Пример:

import js from "@eslint/js";

export default [
  js.configs.recommended,
  {
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module",
      globals: {
        window: "readonly",
        document: "readonly"
      }
    }
  }
];

В legacy:

  • parserOptions.ecmaVersionlanguageOptions.ecmaVersion
  • parserOptions.sourceTypelanguageOptions.sourceType
  • envlanguageOptions.globals

Замена overrides

В legacy:

{
  "overrides": [
    {
      "files": ["*.test.js"],
      "rules": {
        "no-unused-expressions": "off"
      }
    }
  ]
}

В flat config overrides исчезают как отдельная сущность, так как каждый объект уже является изолированным override:

export default [
  {
    files: ["**/*.js"],
    rules: {
      "no-unused-expressions": "error"
    }
  },
  {
    files: ["**/*.test.js"],
    rules: {
      "no-unused-expressions": "off"
    }
  }
];

Глобальные переменные (globals)

В legacy:

{
  "globals": {
    MyGlobal: "readonly"
  }
}

В flat config:

export default [
  {
    languageOptions: {
      globals: {
        MyGlobal: "readonly"
      }
    }
  }
];

Подключение parser и кастомных парсеров

В legacy:

{
  "parser": "@babel/eslint-parser"
}

В flat config:

import babelParser from "@babel/eslint-parser";

export default [
  {
    languageOptions: {
      parser: babelParser
    }
  }
];

Parser становится обычным объектом, а не строковым идентификатором.


Подключение правил из plugins

Flat config требует явного доступа к правилам через объект плагина.

import react from "eslint-plugin-react";

export default [
  {
    plugins: {
      react
    },
    rules: {
      "react/jsx-no-undef": "error",
      "react/jsx-uses-vars": "error"
    }
  }
];

Некоторые плагины экспортируют готовые конфигурации:

import react from "eslint-plugin-react";

export default [
  react.configs.flat.recommended
];

Игнорирование файлов

В legacy использовался .eslintignore. В flat config используется поле ignores.

export default [
  {
    ignores: ["dist/**", "node_modules/**"]
  },
  {
    files: ["**/*.js"],
    rules: {
      "no-console": "warn"
    }
  }
];

ignores может быть как отдельным объектом, так и частью общей конфигурации.


Порядок применения конфигураций

Flat config строго полагается на порядок массива:

  1. сначала базовые конфигурации
  2. затем специализированные
  3. затем узкие overrides

Пример:

export default [
  baseConfig,
  nodeConfig,
  {
    files: ["**/*.test.js"],
    rules: {
      "no-undef": "off"
    }
  }
];

Каждый последующий объект перекрывает предыдущие значения.


Миграция существующего проекта

Процесс перехода с legacy на flat config состоит из последовательного преобразования конфигурации.

Шаг 1. Анализ legacy-конфига

Типичная структура:

{
  "extends": ["eslint:recommended"],
  "parserOptions": {
    "ecmaVersion": 2021
  },
  "env": {
    "node": true
  },
  "rules": {
    "no-console": "warn"
  }
}

Шаг 2. Перенос в массив конфигураций

import js from "@eslint/js";

export default [
  js.configs.recommended,
  {
    languageOptions: {
      ecmaVersion: 2021,
      globals: {
        console: "readonly"
      }
    },
    rules: {
      "no-console": "warn"
    }
  }
];

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

Неявные extends

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

Плагины с legacy-конфигурацией

Не все плагины сразу предоставляют flat-совместимые конфигурации. В таких случаях требуется вручную переносить правила.

Конфликты порядка

Так как порядок имеет значение, неправильное расположение объектов может привести к неожиданному переопределению правил.


Совместимость и гибридные сценарии

В некоторых версиях ESLint допускается параллельное существование legacy и flat конфигурации, но приоритет отдаётся eslint.config.js. Это может привести к ситуации, когда старые .eslintrc файлы игнорируются полностью.


Структурирование большого flat config

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

import base from "./config/base.js";
import node from "./config/node.js";
import react from "./config/react.js";

export default [
  base,
  node,
  react
];

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


Управление сложными правилами

Flat config делает управление правилами более явным:

  • нет скрытого наследования
  • нет автоматического объединения env
  • нет строковых алиасов для плагинов

Каждое правило существует в конкретном месте и применяется строго по порядку.


Особенности дебага конфигурации

Поскольку конфиг становится JavaScript-кодом, возможны:

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

Пример условной логики:

const isProduction = process.env.NODE_ENV === "production";

export default [
  {
    rules: {
      "no-console": isProduction ? "error" : "warn"
    }
  }
];

Итоговая модель мышления

Flat config требует перехода от декларативной “магии” legacy-конфига к композиционной модели:

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

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