javascript

Линт проектов: собираем ESLint, Prettier и Stylelint в один пакет

  • воскресенье, 24 мая 2026 г. в 00:00:16
https://habr.com/ru/articles/1038340/

Введение

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

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

Для кого эта статья

Статья будет полезна:

  • разработчикам, которые хотят навести порядок в линтинге нескольких проектов;

  • тимлидам и техлидам, которые строят единые стандарты кода в команде;

  • тем, кто планирует вынести конфигурации ESLint, Prettier и Stylelint в отдельный пакет.

Предполагается базовое знакомство с ESLintPrettier и экосистемой JavaScript.

Почему для компании стоит создавать свой lint пакет

Если в компании больше одного проекта, со временем почти всегда появляется одинаковая проблема: линт-конфиги начинают расползаться.

В одном репозитории ESLint настроен так, в другом – немного иначе. Где-то есть stylelint, где-то нет. В одном проекте используются recommended правила, в другом – половина из них отключена.

Через год это обычно выглядит примерно так:

  • одинаковые конфиги копируются из проекта в проект;

  • правила случайно расходятся;

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

  • новые проекты начинают, используя старые, устаревшие конфиги.

В итоге линтинг, который должен помогать поддерживать единый стиль кода, сам становится источником хаоса.

Решение у этой проблемы довольно простое – вынести всю линт-инфраструктуру в отдельный npm-пакет@company/lint.

После этого:

  • все проекты используют одни и те же правила;

  • обновление линта происходит в одном месте;

  • новые проекты получают готовую инфраструктуру за минуту;

  • команда может обновлять правила постепенно.

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

Содержимое lint пакета

Lint-пакет стоит рассматривать не просто как сборку конфигураций, а как централизованный гайдлайн по стандартам кода компании.

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

Пример реализованного пакета можно посмотреть в этом репозитории: https://github.com/Dozalex/lint-configs.

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

ESLint

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

Ранее вынос конфигурации ESLint в отдельный пакет был не очень удобным. Каждую зависимость, используемую в конфиге (плагины, парсеры, расширения), приходилось дублировать в package.json каждого проекта. Иначе возникали конфликты зависимостей и ошибки загрузки плагинов.

С выходом ESLint 9 ситуация изменилась. Плоский конфиг (Flat Config) перестал быть экспериментальным и позволил инкапсулировать все зависимости внутри одного пакета.

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

Prettier

Prettier – это форматтер кода, который автоматически:

  • выравнивает отступы;

  • убирает лишние пустые строки;

  • расставляет переносы строк.

Stylelint

Stylelint выполняет ту же роль, что и ESLint, но для CSS.

Он помогает находить ошибки в стилях и следить за единообразием CSS-кода. Однако особенно полезным он становится при использовании плагина stylelint-order, который автоматически сортирует CSS-свойства в заданном порядке.

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

Madge

Madge – менее очевидный, но полезный инструмент анализа зависимостей.

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

Немного о новых инструментах

В последнее время появляются инструменты, которые пытаются объединить линтер и форматтер в одном решении. Одним из таких проектов является Biome.

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

Также, начиная с Vite 8 и связанных проектов VoidZero, экосистема Vite движется в сторону единого toolchain’а: кроме сборки, в неё интегрируются задачи линтинга, форматирования и type-checking. Для линтинга используется Oxlint из экосистемы Oxc.

Возможно, со временем такие решения смогут заменить текущий набор инструментов и сократить время выполнения линтинга без потери качества проверки кода. Но, в любом случае, инструменты, работа с которыми описана в этой статье, будут существовать в экосистеме еще долгие годы, так как проверены временем и используюся в огромном числе компаний.

ESLint на практике

Содержимое конфига

Долгое время eslint-config-airbnb считался де-факто стандартом для фронтенд-проектов. Большинство команд просто брали его за основу и отключали или переопределяли отдельные правила. Однако со временем пакет перестал активно обновляться и сегодня он содержит большое количество легаси-правил, не соответствующих современному стеку разработки.

При создании собственного lint-пакета я решил детально разобрать правила из этого конфига. Там оказалось большое кол-во стилистического мусора, правил для классовых компонентов реакта и прочих древностей. Рекомендованные конфиги для React также содержат большое количество правил, которые существуют в основном для обратной совместимости.

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

Плюсы:

  • в конфиге нет ничего лишнего;

  • не нужно делать десятки выключений правил;

  • нужные правила не пропадут неожиданно при обновлении recommended конфига;

  • линт выполянется быстрее благодаря минимальному набору правил.

Минусы:

  • если хочется самому отфильтровать каждое правило, это займет довольно много времени;

  • когда появятся новые правила для нового синтаксиса языка или фреймворка, будет не достаточно просто обновить recommended конфиг, нужно будет найти эти новые правила, решить, нужны ли они, перенести в свой конфиг.

Было принято решение, что плюсы перевешивают минусы, поэтому все правила из eslint-airbnb-config прошли фильтрацию от легаси, изменены в соответствии с современными стандартами. Также были добавлены некоторые новые полезные правила.

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

eslint/
  core/ – содержит best-practices для js
  reactBase/ – содержит best-practices для react
  base.mjs – базовые настройки + объединение всех конфигов из core + typescript (потому что typescript это де-факто база)
  react.mjs – base.mjs + объединение всех конфигов из reactBase
  typescript.mjs – правила для typescript, подключение tsconfig.json
  webpack.mjs – подключение webpack.config.ts

Такая структура делает конфигурацию расширяемой. Например, можно добавить vue.mjs, подключить базовые правила и получить полноценный конфиг для Vue-проекта, не включая при этом правила, относящиеся к React.

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

Из интересного в конфиге хочется выделить:

  1. Правило import/order.

    Раньше часто сталкивался с тем, что некоторым разработчикам все равно, что у них там в импортах. Как IDE автоматически добавила импорт, так они и оставили – вперемешку импорты библиотек, относительные импорты, алиасы.

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

  2. Использование eslint-config-flat-gitignore.

    Этот конфиг помогает ESLint обходить ненужные для индексации и проверки файлы и директории. За основу берет .gitignore файл проекта. Соответственно, если все настроено корректно, линтер не будет ходить в build, dist, node_modules и прочие папки.

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

  1. Устанавливаем зависимости

    pnpm i -D @company/lint eslint

    Этих зависимостей достаточно, чтобы все заработало. Это возможно благодаря тому, что все плагины и дополнительные зависимости инкапсулированы внутри @company/lint и будут корректно зарезолвлены без конфликтов.

  2. Добавляем файл конфига

    // eslint.config.js
    import eslintReactConfig from '@company/lint/eslint/react';
    
    export default eslintReactConfig;
  3. Добавляем скрипт

    // package.json
    "scripts": {
      "lint:eslint": "eslint . --fix",
    }

Prettier на практике

Prettier – простой и надёжный инструмент форматирования кода, который автоматически приводит файлы к единому стилю.

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

  • авторы ESLint официально отказались от поддержки форматирующих правил (их поддержка перешла в проект ESLint Stylistic);

  • Prettier работает быстрее, чем форматирование через ESLint;

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

Prettier, наоборот, придерживается минималистичной философии: небольшое количество настроек, которых достаточно для поддержания читаемого и единообразного кода.

Ранее распространённым подходом было использование Prettier вместе с ESLint через eslint-config-prettier и eslint-plugin-prettier.

  • eslint-config-prettier отключал стилистические правила ESLint, которые конфликтовали с Prettier;

  • eslint-plugin-prettier позволял запускать Prettier как правило ESLint.

От этой практики отказались, теперь рекомендуется запускать эти инструменты отдельно, так как это более производительно и независимо.

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

  1. Устанавливаем зависимости

    pnpm i -D @company/lint prettier
  2. Добавляем файл конфига

    // .prettierrc
    "@company/lint/prettier"
  3. Добавляем скрипт

    // package.json
    "scripts": {
      "lint:prettier": "prettier . -w --log-level error --ignore-unknown",
    }

    Флаг --ignore-unknown позволяет пропускать файлы, которые Prettier не умеет форматировать.

  4. Добавляем файл для игнора

    // .prettierignore
    pnpm-lock.yaml

    Если используется pnpm, рекомендуется добавить pnpm-lock.yaml в список игнорируемых файлов. В противном случае Prettier может пытаться форматировать его при каждом запуске. В остальном, Prettier по умолчанию смотрит в .gitignore, находящийся в папке запуска, и не сканирует файлы и папки, которые там перечислены.

Stylelint на практике

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

  1. Устанавливаем зависимости

    pnpm i -D @company/lint stylelint
  2. Добавляем файл конфига

    // stylelint.config.js
    module.exports = {
      extends: ['@company/lint/stylelint'],
    };
  3. Добавляем скрипт

    // package.json
    "scripts": {
      "lint:style": "stylelint \"{src,packages/*/src}/**/*.css\" --fix",
    }

Madge на практике

С этим инстурументом все просто – установил, добавил конфиг, запустил и получил в консоль ошибки, если они есть.

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

  1. Устанавливаем зависимости

    pnpm i -D madge
  2. Добавляем файл конфига

    // .madgerc
    {
      "detectiveOptions": {
        "ts": {
          "skipTypeImports": true
        },
        "tsx": {
          "skipTypeImports": true
        }
      },
      "fileExtensions": ["ts", "tsx"],
      "tsConfig": "./tsconfig.json"
    }

    По желанию можно удалить из конфига игнорирование импортов типов, если вы хотите, чтобы для них тоже не было рекурсии, но в рамках типов обычно это допускается, если мы делаем их через import type.

  3. Добавляем скрипт

    // package.json
    "scripts": {
      "lint:madge": "madge --circular src packages/*/src",
    }

Typescript на практике

Инструмент линтинга кода номер один. Обязателен в современном мире веб разработки. Если вы его не используете, значит полагаетесь на волю случая, ибо одно только использование typescript избавляет вас от 90% багов (субъективная оценка, основанная на прошлом опыте), которые могли бы быть, если бы вы использовали только javascript.

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

  1. Устанавливаем зависимости

    pnpm i -D typescript
  2. Добавляем файл конфига tsconfig.json с настройками для вашего проекта

  3. Добавляем скрипт

    // package.json
    "scripts": {
      "lint:ts": "tsc --noEmit",
    }

Lint-staged на практике

Инструмент, который запускает команды только для файлов, изменённых в git staging area (то есть добавленных через git add).

Обычно используется с Husky (разберем далее) в pre-commit hook:

  1. Меняем файлы

  2. Делаем git add.

  3. Перед git commit запускается lint-staged.

  4. Он прогоняет линтеры/форматтеры только по staged-файлам.

  5. Если всё ок – коммит проходит.

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

  1. Устанавливаем зависимости

    pnpm i -D lint-staged
  2. Добавляем конфиг

    // package.json
    "lint-staged": {
      "*.{css,ts,tsx}": ["stylelint --fix"],
      "*.{js,jsx,ts,tsx,mjs,cjs}": ["eslint --fix --quiet"],
      "*": ["prettier -w --log-level error --ignore-unknown"]
    }

    Команды для разных масок запускаются параллельно. Если файл подходит под несколько масок – для него выполнятся все подходящие команды.

Husky на практике

Инструмент, который позволит автоматически запускать реализованные выше скрипты, непосредственно перед коммитом изменений.

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

  1. Устанавливаем зависимости

    pnpm i -D husky
  2. Добавляем файл конфига

    // .husky/pre-commit
    pnpm lint:madge
    pnpm lint-staged
    pnpm lint:ts

    Важные моменты:

    • запуск lint:madge добавляем сюда, а не в lint-staged, потому что в lint-staged идет работа с каждым файлом отдельно, а здесь один раз сделает прогон и все, экономит время.

    • запуск lint:ts добавляем сюда, а не в lint-staged, потому что в противном случае рискуем закоммитить код, который содержит ошибки. Например, если мы обновили только зависимости в package.jsonlint-staged проверит только сам packages.json и lock файл, но не обнаружит typescript ошибок, которые могли возникнуть из за обновления этих зависимостей.

  3. Добавляем скрипт

    // package.json
    "scripts": {
      "prepare": "husky",
    }

    Этот скрипт сработает автоматически, в момент установки зависимостей (pnpm i), и настроит husky для работы в проекте.

Теперь, когда разработчик решит сделать коммит изменений, то husky автоматически запустит указанные в pre-commit команды.

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

Итоговый пример package.json со всеми реализванными настройками и зависимостями

{
  "scripts": {
    "prepare": "husky",
    "lint:ts": "tsc --noEmit",
    "lint:eslint": "eslint . --fix",
    "lint:madge": "madge --circular src packages/*/src",
    "lint:prettier": "prettier . -w --log-level error --ignore-unknown",
    "lint:style": "stylelint \"{src,packages/*/src}/**/*.css\" --fix",
    "lint": "yarn lint:madge && yarn lint:style && yarn lint:eslint && yarn lint:ts && yarn lint:prettier"
  },
  "lint-staged": {
    "*.{css,ts,tsx}": ["stylelint --fix"],
    "*.{js,jsx,ts,tsx,mjs,cjs}": ["eslint --fix --quiet"],
    "*": ["prettier -w --log-level error --ignore-unknown"]
  },
  "devDependencies": {
    "@company/lint": "^1.0.0",
    "eslint": "^9.39.4",
    "husky": "^9.1.7",
    "lint-staged": "^13.0.3",
    "madge": "^8.0.0",
    "prettier": "^3.8.1",
    "stylelint": "^17.4.0",
    "typescript": "^5.9.3",
  }
}

Удобно, во время разработки, запустить все проверки одной командой pnpm lint. Важно запускать их в правильном корядке, чтобы скрипты не конфликтовали после автоматических правок.

Заключение

Создание пакета с конфигами для линта сильно облегчило мою работу:

  • cтало меньше бойлерплейтного кода в проектах;

  • правила линтинга инкапсулировались в одном месте;

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

Если вы захотите иметь подобный набор инструментов у себя в проектах, чтобы было от чего отталкиваться, можете воспользоваться моим репозиторием https://github.com/Dozalex/lint-configs и пакетом @dozalex/lint.

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