|
Professor Seleznov
|
Введение В большинстве компаний линтинг со временем превращается в хаос: разные правила ESLint, устаревшие конфиги и копипаста между проектами. Покажу, как навести порядок – собрать линт-инфраструктуру в один пакет и выстроить систему контроля кода для всех репозиториев. Для кого эта статья Статья будет полезна:
- разработчикам, которые хотят навести порядок в линтинге нескольких проектов;
- тимлидам и техлидам, которые строят единые стандарты кода в команде;
- тем, кто планирует вынести конфигурации ESLint, Prettier и Stylelint в отдельный пакет.
Предполагается базовое знакомство с ESLint, Prettier и экосистемой 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. Использование
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.json, lint-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. Конструктивные замечания всегда приветствуются. Буду рад полезному фидбеку, чтобы сделать код еще более качественным и надежным.-Источник
|