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

Страницы:  1

Ответить
 

Professor Seleznov


Введение
В большинстве компаний линтинг со временем превращается в хаос: разные правила 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 бы сразу выдал ошибку.
Из интересного в конфиге хочется выделить:
  • Правило import/order.
    Раньше часто сталкивался с тем, что некоторым разработчикам все равно, что у них там в импортах. Как IDE автоматически добавила импорт, так они и оставили – вперемешку импорты библиотек, относительные импорты, алиасы.
    Прекрасно понимаю, что вручную их корректировать это безумие, но единый порядок способствует лучшей нафигации по коду, а следовательно ускорению разработки (еще и глазу приятнее). Поэтому данное правило позволяет всем оставаться довольными без применения усилий, так как автоматически сортирует и группирует импорты. Можно сделать максимально гибко, но базовых настроек уже будет хватать.
  • Использование eslint-config-flat-gitignore.
    Этот конфиг помогает ESLint обходить ненужные для индексации и проверки файлы и директории. За основу берет .gitignore файл проекта. Соответственно, если все настроено корректно, линтер не будет ходить в build, dist, node_modules и прочие папки.
Использование
  • Устанавливаем зависимости
    pnpm i -D @company/lint eslint
    
    Этих зависимостей достаточно, чтобы все заработало. Это возможно благодаря тому, что все плагины и дополнительные зависимости инкапсулированы внутри @company/lint и будут корректно зарезолвлены без конфликтов.
  • Добавляем файл конфига
    // eslint.config.js
    import eslintReactConfig from '@company/lint/eslint/react';
    export default eslintReactConfig;
  • Добавляем скрипт
    // 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.
От этой практики отказались, теперь рекомендуется запускать эти инструменты отдельно, так как это более производительно и независимо.
Использование
  • Устанавливаем зависимости
    pnpm i -D @company/lint prettier
    
  • Добавляем файл конфига
    // .prettierrc
    "@company/lint/prettier"
  • Добавляем скрипт
    // package.json
    "scripts": {
    "lint:prettier": "prettier . -w --log-level error --ignore-unknown",
    }
    Флаг --ignore-unknown позволяет пропускать файлы, которые Prettier не умеет форматировать.
  • Добавляем файл для игнора
    // .prettierignore
    pnpm-lock.yaml
    Если используется pnpm, рекомендуется добавить pnpm-lock.yaml в список игнорируемых файлов. В противном случае Prettier может пытаться форматировать его при каждом запуске. В остальном, Prettier по умолчанию смотрит в .gitignore, находящийся в папке запуска, и не сканирует файлы и папки, которые там перечислены.
Stylelint на практике
Использование
  • Устанавливаем зависимости
    pnpm i -D @company/lint stylelint
    
  • Добавляем файл конфига
    // stylelint.config.js
    module.exports = {
    extends: ['@company/lint/stylelint'],
    };
  • Добавляем скрипт
    // package.json
    "scripts": {
    "lint:style": "stylelint \"{src,packages/*/src}/**/*.css\" --fix",
    }
Madge на практике
С этим инстурументом все просто – установил, добавил конфиг, запустил и получил в консоль ошибки, если они есть.
Использование
  • Устанавливаем зависимости
    pnpm i -D madge
    
  • Добавляем файл конфига
    // .madgerc
    {
    "detectiveOptions": {
    "ts": {
    "skipTypeImports": true
    },
    "tsx": {
    "skipTypeImports": true
    }
    },
    "fileExtensions": ["ts", "tsx"],
    "tsConfig": "./tsconfig.json"
    }
    По желанию можно удалить из конфига игнорирование импортов типов, если вы хотите, чтобы для них тоже не было рекурсии, но в рамках типов обычно это допускается, если мы делаем их через import type.
  • Добавляем скрипт
    // package.json
    "scripts": {
    "lint:madge": "madge --circular src packages/*/src",
    }
Typescript на практике
Инструмент линтинга кода номер один. Обязателен в современном мире веб разработки. Если вы его не используете, значит полагаетесь на волю случая, ибо одно только использование typescript избавляет вас от 90% багов (субъективная оценка, основанная на прошлом опыте), которые могли бы быть, если бы вы использовали только javascript.
Использование
  • Устанавливаем зависимости
    pnpm i -D typescript
    
  • Добавляем файл конфига tsconfig.json с настройками для вашего проекта
  • Добавляем скрипт
    // package.json
    "scripts": {
    "lint:ts": "tsc --noEmit",
    }
Lint-staged на практике
Инструмент, который запускает команды только для файлов, изменённых в git staging area (то есть добавленных через git add).
Обычно используется с Husky (разберем далее) в pre-commit hook:
  • Меняем файлы
  • Делаем git add.
  • Перед git commit запускается lint-staged.
  • Он прогоняет линтеры/форматтеры только по staged-файлам.
  • Если всё ок – коммит проходит.
Использование
  • Устанавливаем зависимости
    pnpm i -D lint-staged
    
  • Добавляем конфиг
    // 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 на практике
Инструмент, который позволит автоматически запускать реализованные выше скрипты, непосредственно перед коммитом изменений.
Использование
  • Устанавливаем зависимости
    pnpm i -D husky
    
  • Добавляем файл конфига
    // .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 ошибок, которые могли возникнуть из за обновления этих зависимостей.
  • Добавляем скрипт
    // 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.
Конструктивные замечания всегда приветствуются. Буду рад полезному фидбеку, чтобы сделать код еще более качественным и надежным.-Источник
 
Loading...
Error