Принцип Shareable Config

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

Shareable Config — это механизм ESLint, позволяющий вынести набор правил в отдельный пакет и подключать его в разных проектах через директиву extends.

Основные задачи Shareable Config:

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

Без Shareable Config каждый проект вынужден хранить собственный набор правил:

module.exports = {
  rules: {
    semi: ["error", "always"],
    quotes: ["error", "single"],
    eqeqeq: "error",
    no-var: "error",
    prefer-const: "error"
  }
};

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

Shareable Config решает эту проблему через публикацию общего пакета настроек.


Концепция переиспользуемой конфигурации

Предположим, компания использует единый стандарт JavaScript-разработки.

Создаётся пакет:

eslint-config-company

Внутри него хранится конфигурация:

module.exports = {
  rules: {
    semi: ["error", "always"],
    quotes: ["error", "single"],
    eqeqeq: "error"
  }
};

После публикации пакет устанавливается в любой проект:

npm install --save-dev eslint-config-company

Подключение производится через extends:

module.exports = {
  extends: ["company"]
};

Теперь весь набор правил автоматически применяется к проекту.


Как работает extends

Механизм Shareable Config полностью основан на свойстве extends.

Пример:

module.exports = {
  extends: ["airbnb"]
};

Во время запуска ESLint:

  1. Находит пакет конфигурации.
  2. Загружает его настройки.
  3. Объединяет их с локальной конфигурацией.
  4. Применяет итоговый набор правил.

Локальные настройки имеют приоритет над унаследованными.

Например:

module.exports = {
  extends: ["company"],

  rules: {
    quotes: ["error", "double"]
  }
};

Если в Shareable Config используются одинарные кавычки, а локально указаны двойные, итоговой станет локальная настройка.


Соглашение об именовании пакетов

Для Shareable Config ESLint использует специальную схему именования.

Полное имя пакета:

eslint-config-myconfig

Подключение:

extends: ["myconfig"]

Префикс eslint-config- можно не указывать.

ESLint автоматически добавляет его при поиске пакета.

Соответствие выглядит следующим образом:

Пакет Подключение
eslint-config-company company
eslint-config-team team
eslint-config-standard standard

Scoped-пакеты

В организациях часто используются scoped-пакеты npm.

Название:

@company/eslint-config

или

@company/eslint-config-base

Подключение:

extends: ["@company"]

либо:

extends: ["@company/base"]

ESLint автоматически определяет необходимый пакет по имени scope.


Структура Shareable Config

Минимальная структура пакета:

eslint-config-company/
├── package.json
└── index.js

Файл index.js:

module.exports = {
  rules: {
    semi: ["error", "always"],
    quotes: ["error", "single"]
  }
};

Файл package.json:

{
  "name": "eslint-config-company",
  "version": "1.0.0",
  "main": "index.js"
}

После публикации пакет готов к использованию в любых проектах.


Использование Flat Config

Начиная с современных версий ESLint основным способом настройки становится Flat Config.

Для Shareable Config в таком формате создаётся конфигурация:

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

Структура пакета:

eslint-config-company/
├── package.json
└── eslint.config.js

Подключение:

import companyConfig from "eslint-config-company";

export default [
  ...companyConfig
];

В экосистеме ESLint постепенно происходит переход именно к этому подходу.


Создание нескольких конфигураций внутри одного пакета

Часто требуется несколько вариантов правил:

  • базовая конфигурация;
  • конфигурация для React;
  • конфигурация для Node.js;
  • конфигурация для TypeScript.

Структура пакета:

eslint-config-company/
├── index.js
├── react.js
├── node.js
└── typescript.js

Базовая конфигурация:

module.exports = {
  rules: {
    semi: ["error", "always"]
  }
};

React-конфигурация:

module.exports = {
  extends: ["./index"],
  rules: {
    "react/jsx-uses-react": "error"
  }
};

Подключение:

extends: ["company/react"]

или

extends: ["company/typescript"]

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


Наследование других конфигураций

Shareable Config может использовать другие Shareable Config.

Пример:

module.exports = {
  extends: [
    "eslint:recommended",
    "standard"
  ]
};

Здесь конфигурация объединяет:

  • встроенные рекомендации ESLint;
  • правила StandardJS.

После этого поверх них можно добавлять собственные настройки:

module.exports = {
  extends: [
    "eslint:recommended",
    "standard"
  ],

  rules: {
    semi: ["error", "always"]
  }
};

Такой способ значительно сокращает объём конфигурации.


Использование плагинов внутри Shareable Config

Часто конфигурация зависит от ESLint-плагинов.

Пример:

module.exports = {
  plugins: ["import"],

  rules: {
    "import/order": "error"
  }
};

Для корректной работы пакет должен объявлять зависимости.

Пример:

{
  "name": "eslint-config-company",
  "peerDependencies": {
    "eslint": "^9.0.0",
    "eslint-plugin-import": "^2.30.0"
  }
}

Или:

{
  "dependencies": {
    "eslint-plugin-import": "^2.30.0"
  }
}

Выбор подхода зависит от стратегии распространения конфигурации.


Peer Dependencies

Исторически для Shareable Config часто использовались peerDependencies.

Пример:

{
  "peerDependencies": {
    "eslint": "^9.0.0",
    "eslint-plugin-react": "^7.37.0"
  }
}

Преимущества:

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

Недостаток заключается в необходимости дополнительной установки зависимостей в каждом проекте.


Расширение встроенных конфигураций ESLint

ESLint содержит несколько встроенных конфигураций.

Наиболее известная:

extends: ["eslint:recommended"]

Shareable Config может использовать её как основу:

module.exports = {
  extends: ["eslint:recommended"]
};

Затем поверх неё добавляются собственные требования:

module.exports = {
  extends: ["eslint:recommended"],

  rules: {
    eqeqeq: "error",
    no-var: "error",
    prefer-const: "error"
  }
};

Это позволяет избежать ручного перечисления десятков базовых правил.


Комбинирование нескольких Shareable Config

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

Пример:

module.exports = {
  extends: [
    "company",
    "company/react",
    "prettier"
  ]
};

Конфигурации применяются последовательно.

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

Порядок подключения играет важную роль:

extends: [
  "company",
  "prettier"
]

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


Конфигурация для TypeScript

Shareable Config часто используется вместе с TypeScript.

Пример:

module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],

  parser: "@typescript-eslint/parser"
};

После публикации все TypeScript-проекты могут использовать единый набор правил:

extends: ["company/typescript"]

Это особенно важно для монорепозиториев и крупных корпоративных систем.


Конфигурация для React

Отдельная React-конфигурация может выглядеть следующим образом:

module.exports = {
  extends: [
    "plugin:react/recommended"
  ],

  settings: {
    react: {
      version: "detect"
    }
  }
};

Подключение:

extends: ["company/react"]

В результате все React-проекты получают одинаковые требования к JSX, компонентам и хукам.


Конфигурация для Node.js

Shareable Config может учитывать особенности серверной среды.

Пример:

module.exports = {
  env: {
    node: true
  },

  rules: {
    "no-console": "off"
  }
};

Подключение:

extends: ["company/node"]

Такой подход отделяет браузерные правила от серверных.


Публикация Shareable Config

Подготовка пакета обычно включает следующие шаги:

  1. Создание npm-пакета.
  2. Написание конфигурации ESLint.
  3. Настройка зависимостей.
  4. Публикация через npm.

Публикация:

npm publish

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

npm install --save-dev eslint-config-company

Версионирование конфигураций

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

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

Пример:

1.2.0

Добавление новых необязательных возможностей.

2.0.0

Изменение существующих правил или ужесточение требований.

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


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

В монорепозитории может существовать единая конфигурация:

packages/
├── eslint-config-company
├── frontend
├── backend
└── shared

Каждый пакет подключает общий набор правил:

extends: ["company"]

Преимущества:

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

Лучшие практики проектирования Shareable Config

Минимизировать количество обязательных зависимостей

Каждый дополнительный плагин усложняет поддержку конфигурации.

Разделять конфигурации по назначению

Хорошая структура:

company
company/react
company/node
company/typescript

Использовать наследование

Вместо дублирования:

extends: ["./index"]

Документировать правила

Каждое нестандартное правило должно иметь объяснение причины его существования.

Соблюдать обратную совместимость

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

Выделять базовую конфигурацию

Базовый пакет должен содержать только универсальные правила:

module.exports = {
  rules: {
    eqeqeq: "error",
    no-var: "error",
    prefer-const: "error"
  }
};

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


Типичная схема распространения конфигураций

В зрелой инфраструктуре линтинга формируется следующая иерархия:

eslint-config-company
│
├── company/base
├── company/node
├── company/react
├── company/vue
├── company/typescript
└── company/testing

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

extends: [
  "company/typescript",
  "company/react"
]

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