Монорепозиторий представляет собой единое хранилище исходного кода, содержащее несколько приложений, библиотек, сервисов и вспомогательных пакетов. По мере роста такого проекта конфигурация ESLint становится одним из ключевых элементов поддержки качества кода. Неправильно организованная система линтинга приводит к дублированию настроек, снижению производительности проверок и усложнению сопровождения.
Основная задача оптимизации заключается в создании централизованной, масштабируемой и производительной конфигурации, которая может использоваться всеми пакетами монорепозитория без копирования одних и тех же настроек.
Типичный монорепозиторий может содержать следующую структуру:
repo/
├── apps/
│ ├── frontend/
│ └── admin/
├── packages/
│ ├── ui/
│ ├── utils/
│ └── api-client/
├── tools/
└── package.json
Каждый пакет может использовать:
Без централизованного подхода возникает большое количество отдельных файлов конфигурации:
apps/frontend/.eslintrc.json
apps/admin/.eslintrc.json
packages/ui/.eslintrc.json
packages/utils/.eslintrc.json
Подобная организация быстро становится трудноуправляемой.
Наиболее распространённым подходом является размещение базовой конфигурации в корне репозитория.
Пример:
// eslint.config.js
export default [
{
rules: {
semi: ["error", "always"],
quotes: ["error", "single"]
}
}
];
Все подпроекты автоматически наследуют данные настройки.
Преимущества:
В крупных монорепозиториях конфигурацию ESLint часто выносят в отдельный внутренний пакет.
Структура:
packages/
└── eslint-config/
├── package.json
└── index.js
Файл конфигурации:
module.exports = {
rules: {
semi: ["error", "always"],
quotes: ["error", "single"]
}
};
Использование:
module.exports = {
extends: ['@company/eslint-config']
};
Преимущества подхода:
Монорепозиторий обычно требует нескольких уровней настроек.
Например:
eslint-config-base
│
▼
eslint-config-react
│
▼
apps/frontend
Базовая конфигурация:
module.exports = {
rules: {
semi: ['error', 'always']
}
};
React-конфигурация:
module.exports = {
extends: ['./base'],
plugins: ['react']
};
Конфигурация приложения:
module.exports = {
extends: ['@company/eslint-config-react']
};
Подобная схема обеспечивает гибкое наследование правил без их дублирования.
Начиная с ESLint 9 основной моделью конфигурации становится Flat Config.
Пример:
import js from '@eslint/js';
export default [
js.configs.recommended,
{
rules: {
semi: ['error', 'always']
}
}
];
Преимущества Flat Config:
Для монорепозиториев Flat Config особенно полезен благодаря явному описанию всех конфигурационных блоков.
Часто разные части монорепозитория требуют различных настроек.
Пример:
apps/frontend
packages/server
packages/shared
В Flat Config это реализуется через свойство files.
export default [
{
files: ['apps/frontend/**/*.{js,jsx}'],
rules: {
'no-alert': 'error'
}
},
{
files: ['packages/server/**/*.js'],
rules: {
'no-process-exit': 'error'
}
}
];
Преимущества:
В монорепозиториях количество файлов может исчисляться сотнями тысяч.
Линтер не должен анализировать:
node_modules/
dist/
build/
coverage/
.next/
storybook-static/
Пример:
export default [
{
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/coverage/**'
]
}
];
Грамотное исключение директорий существенно ускоряет работу ESLint.
TypeScript является наиболее затратной частью линтинга.
Типичная конфигурация:
parserOptions: {
project: './tsconfig.json'
}
При большом количестве пакетов ESLint начинает многократно создавать экземпляры TypeScript Program.
Для оптимизации рекомендуется использовать отдельный конфигурационный файл:
tsconfig.eslint.json
Пример:
{
"extends": "./tsconfig.json",
"include": ["src/**/*"]
}
Это позволяет уменьшить количество анализируемых файлов.
В монорепозиториях TypeScript часто применяются ссылки между проектами.
{
"references": [
{
"path": "../shared"
}
]
}
ESLint способен использовать такую структуру значительно эффективнее, чем полностью независимые проекты.
Преимущества:
Наиболее ресурсоёмкими считаются правила, использующие информацию о типах.
Примеры:
@typescript-eslint/no-floating-promises
@typescript-eslint/no-misused-promises
@typescript-eslint/restrict-plus-operands
Если пакет содержит простой код утилит, подобные проверки могут быть отключены:
{
files: ['packages/utils/**/*.ts'],
rules: {
'@typescript-eslint/no-floating-promises': 'off'
}
}
Подход позволяет перераспределить вычислительные ресурсы на более важные части системы.
Одним из самых эффективных способов ускорения ESLint является использование кэша.
Запуск:
eslint . --cache
Создаётся файл:
.eslintcache
Повторная проверка анализирует только изменённые файлы.
Для CI-пайплайнов часто используют:
eslint . --cache --cache-location .cache/eslint
Преимущества:
Полный запуск ESLint на крупном монорепозитории может занимать несколько минут.
Более эффективный подход — проверка изменённых файлов.
Пример через Git:
git diff --name-only HEAD~1
Далее список передаётся в ESLint:
eslint file1.js file2.js
Многие инструменты автоматизации строят линтинг именно по этому принципу.
Система управления монорепозиториями Nx предоставляет встроенную поддержку ESLint.
Пример конфигурации цели:
{
"lint": {
"executor": "@nx/eslint:lint",
"options": {
"lintFilePatterns": [
"apps/frontend/**/*.ts"
]
}
}
}
Nx вычисляет граф зависимостей и запускает линтер только для затронутых проектов.
Преимущества:
В Turborepo ESLint обычно выступает отдельной задачей.
Пример:
{
"pipeline": {
"lint": {
"outputs": []
}
}
}
Запуск:
turbo run lint
Система автоматически использует локальный и удалённый кэш результатов.
Это особенно полезно при большом количестве пакетов.
Современные процессоры содержат множество ядер.
ESLint поддерживает распараллеливание через внешние инструменты и системы сборки.
Пример:
turbo run lint
или
nx run-many --target=lint
Проверки нескольких пакетов выполняются одновременно, что значительно сокращает общее время выполнения.
Большое количество правил увеличивает время анализа.
Практикой крупных команд является разделение правил на категории:
semi
quotes
eqeqeq
no-unused-vars
no-console
prefer-const
import/no-cycle
sonarjs/cognitive-complexity
@typescript-eslint/no-floating-promises
Тяжёлые правила нередко запускаются только в CI.
Локальная разработка требует высокой скорости.
CI требует максимального контроля качества.
Пример разделения:
export default [
{
rules: {
'no-console': 'warn'
}
}
];
Для CI:
export default [
{
rules: {
'no-console': 'error'
}
}
];
Такой подход сохраняет комфорт разработки и одновременно обеспечивает строгие проверки перед публикацией изменений.
Внутренние ESLint-конфигурации рекомендуется версионировать так же, как обычные пакеты.
Пример:
{
"name": "@company/eslint-config",
"version": "3.2.0"
}
Преимущества:
При росте монорепозитория эффективной считается следующая архитектура:
eslint-config-base
│
├── eslint-config-react
│
├── eslint-config-node
│
├── eslint-config-typescript
│
└── eslint-config-testing
Каждый пакет подключает только необходимые наборы правил.
Например:
export default [
...typescriptConfig,
...reactConfig
];
Подобная модульность позволяет поддерживать тысячи файлов и десятки проектов без усложнения инфраструктуры линтинга.
Проблема:
20 пакетов
20 одинаковых .eslintrc
Последствия:
Проблема:
parserOptions: {
project: '**/tsconfig.json'
}
Последствия:
Проблема:
eslint .
Вместо:
eslint . --cache
Последствия:
Проблема:
eslint .
Последствия:
Более эффективным решением является анализ только изменённых файлов или затронутых проектов.
repo/
├── eslint.config.js
├── packages/
│ ├── eslint-config-base/
│ ├── eslint-config-react/
│ ├── eslint-config-node/
│ └── eslint-config-typescript/
├── apps/
├── packages/
└── .eslintcache
Ключевые принципы такой архитектуры:
Подобный подход обеспечивает высокую производительность ESLint даже в монорепозиториях, содержащих десятки пакетов, множество приложений и сотни тысяч строк кода.