Функция defineConfig

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

Функция defineConfig используется как обёртка над массивом конфигурационных объектов ESLint. Её основная задача заключается в том, чтобы:

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

Фактически defineConfig не изменяет семантику конфигурации, а формализует её структуру, делая её более строгой и предсказуемой.

Место в архитектуре flat config

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

  • задавать глобальные параметры анализа;
  • переопределять правила;
  • подключать плагины;
  • ограничивать область применения через files и ignores.

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

Базовый синтаксис

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

import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.js"],
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module"
    },
    rules: {
      semi: ["error", "always"],
      quotes: ["error", "single"]
    }
  }
]);

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

Структура конфигурационного объекта

Конфигурационный объект внутри defineConfig обычно включает несколько ключевых секций:

files

Поле files определяет область применения конфигурации. Поддерживаются glob-шаблоны:

files: ["src/**/*.ts", "tests/**/*.ts"]

Этот механизм заменяет концепцию overrides из старого формата, делая правила более локализованными и явными.

rules

Раздел rules определяет поведение линтера:

rules: {
  no-console: "warn",
  eqeqeq: "error"
}

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

languageOptions

Секция languageOptions управляет синтаксическим анализом:

languageOptions: {
  ecmaVersion: "latest",
  sourceType: "module",
  globals: {
    window: "readonly"
  }
}

Эта часть напрямую влияет на работу парсера и интерпретацию кода.

plugins

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

import js from "@eslint/js";

export default defineConfig([
  {
    plugins: {
      js
    }
  }
]);

Плагины становятся объектами, доступными для правил через пространство имён.

Объединение конфигураций

Одним из ключевых аспектов defineConfig является предсказуемое объединение конфигурационных слоёв. При наличии нескольких объектов в массиве применяется последовательная стратегия:

  1. Более ранние объекты задают базовые настройки.
  2. Более поздние объекты переопределяют совпадающие ключи.
  3. Массивы и объекты объединяются по специфическим правилам ESLint.

Пример композиции:

export default defineConfig([
  {
    rules: {
      quotes: ["error", "single"],
      semi: ["error", "always"]
    }
  },
  {
    rules: {
      quotes: ["error", "double"]
    }
  }
]);

В результате правило quotes будет переопределено, а semi сохранится из первого объекта.

Типизация и использование с TypeScript

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

Типизация позволяет:

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

Пример:

import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    rules: {
      "no-unused-vars": "error"
    }
  }
]);

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

Интеграция с плагинами

Конфигурация через defineConfig упрощает подключение внешних наборов правил. Плагины часто экспортируют готовые конфигурации, которые могут быть включены напрямую:

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

export default defineConfig([
  js.configs.recommended,
  {
    plugins: {
      react
    },
    rules: {
      "react/react-in-jsx-scope": "off"
    }
  }
]);

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

Условные конфигурации

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

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

export default defineConfig([
  {
    rules: {
      "no-debugger": isProduction ? "error" : "off"
    }
  }
]);

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

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

В flat config игнорирование реализуется через отдельные объекты:

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

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

Приоритет и порядок применения

Механизм приоритета основан на последовательности массива:

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

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

export default defineConfig([
  {
    rules: {
      semi: ["error", "always"]
    }
  },
  {
    files: ["**/*.test.js"],
    rules: {
      semi: "off"
    }
  }
]);

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

Расширенные паттерны использования

Сегментация по типам файлов

export default defineConfig([
  {
    files: ["src/**/*.js"],
    rules: {
      "no-console": "warn"
    }
  },
  {
    files: ["scripts/**/*.js"],
    rules: {
      "no-console": "off"
    }
  }
]);

Разделение runtime и test окружений

export default defineConfig([
  {
    files: ["**/*.test.js"],
    languageOptions: {
      globals: {
        describe: "readonly",
        it: "readonly"
      }
    }
  }
]);

Комбинирование базовых пресетов

import js from "@eslint/js";

export default defineConfig([
  js.configs.recommended,
  {
    rules: {
      "no-var": "error",
      prefer-const: "error"
    }
  }
]);

Особенности поведения при конфликте правил

При конфликте правил применяется принцип «последний применённый имеет приоритет». Однако некоторые правила ESLint обладают внутренней логикой объединения:

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

Ограничения и особенности

defineConfig не вводит собственную семантику выполнения. Его ограничения связаны исключительно с моделью flat config:

  • отсутствие неявного наследования;
  • необходимость явного перечисления всех слоёв;
  • строгая зависимость от порядка элементов массива;
  • отсутствие legacy-совместимости с .eslintrc без миграционного слоя.

Влияние на структуру проекта

Использование defineConfig приводит к изменению подхода к организации конфигурации:

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

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