Lint, Format и Git Hooks
Linting, formatting и Git hooks помогают автоматически поддерживать качество и единообразие кода.
Они решают разные задачи:
| Инструмент | Назначение |
|---|---|
| ESLint | Находит потенциальные ошибки и нарушения правил JavaScript |
| Prettier | Автоматически форматирует код |
| EditorConfig | Согласует базовые настройки редакторов |
| Husky | Позволяет хранить и настраивать Git hooks в проекте |
| lint-staged | Запускает команды только для подготовленных к коммиту файлов |
| commitlint | Проверяет формат сообщения коммита |
| Conventional Commits | Определяет единый формат сообщений коммитов |
Типичный процесс:
Разработчик изменяет файлы
↓
Prettier форматирует код
↓
ESLint проверяет и исправляет код
↓
git commit
↓
pre-commit запускает lint-staged
↓
commit-msg проверяет сообщение коммита
↓
Git создаёт коммитЛинтер и форматтер не заменяют тестирование:
ESLint — статический анализ кода
Prettier — внешний вид кода
Tests — проверка поведения программы
TypeScript — проверка типовСодержание
- ESLint
- Установка ESLint
- Flat config ESLint
- Структура конфигурации ESLint
- Правила ESLint
- Рекомендуемая конфигурация
- Плагины ESLint
- Отключение правил ESLint
- Команды ESLint
- Prettier
- Установка Prettier
- Конфигурация Prettier
- `.prettierignore`
- Prettier и ESLint
- Форматирование в редакторе
- EditorConfig
- EditorConfig и Prettier
- Git hooks
- `pre-commit`
- `commit-msg`
- Обход Git hooks
- Husky
- Настройка `pre-commit` в Husky
- lint-staged
- Конфигурация lint-staged
- Как lint-staged изменяет файлы
- Частично подготовленные файлы
- Conventional Commits
- Типы Conventional Commits
- Scope
- Description
- Тело коммита
- Breaking changes
- Связь Conventional Commits с версиями
- Проверка сообщений через commitlint
- Настройка `commit-msg` в Husky
- Настройка правил commitlint
- Полный пример настройки
- Инициализация проекта
- Пример рабочего процесса
- Проверки в CI
- Частые проблемы
- Рекомендации по организации
- Краткая памятка
ESLint
ESLint — инструмент статического анализа JavaScript-кода.
Он может находить:
- использование необъявленных переменных;
- недостижимый код;
- случайные присваивания;
- неправильную работу с промисами при наличии соответствующих плагинов;
- неиспользуемые переменные;
- нарушение стиля проекта;
- некоторые потенциальные ошибки;
- проблемные или запрещённые конструкции.
Пример:
function calculateTotal(items) {
let result = 0;
for (const item of items) {
result += item.price;
}
return reslt;
}Переменная reslt не объявлена. ESLint сообщит об ошибке:
'reslt' is not defined no-undefESLint не выполняет программу. Он анализирует исходный код по заданным правилам.
Установка ESLint
Для npm-проекта:
npm install --save-dev eslint @eslint/js globalsСокращённая запись:
npm i -D eslint @eslint/js globalsПроверка версии:
npx eslint --versionКоманда инициализации:
npm init @eslint/configОна задаёт несколько вопросов и создаёт конфигурацию с учётом типа проекта.
Flat config ESLint
Современная конфигурация ESLint называется flat config.
Обычно она хранится в одном из файлов:
eslint.config.js
eslint.config.mjs
eslint.config.cjsЕсли проект не использует ES-модули, удобно создать:
eslint.config.mjsПример базовой конфигурации для браузерного JavaScript:
import js from "@eslint/js";
import globals from "globals";
export default [
{
ignores: [
"dist/**",
"build/**",
"coverage/**",
"node_modules/**"
]
},
js.configs.recommended,
{
files: ["src/**/*.{js,mjs}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser
}
},
rules: {
"no-console": "warn",
"no-debugger": "error",
"no-var": "error",
"prefer-const": "error",
"eqeqeq": ["error", "always"]
}
}
];Проверка проекта:
npx eslint .Автоматическое исправление:
npx eslint . --fixНе все ошибки можно исправить автоматически. Например, ESLint может обнаружить неизвестную переменную, но обычно не может достоверно определить, какое имя должен был написать разработчик.
Структура конфигурации ESLint
Flat config экспортирует массив конфигурационных объектов:
export default [
{
// Общая конфигурация
},
{
// Конфигурация для определённых файлов
}
];Порядок объектов имеет значение. Более поздние настройки могут дополнять или переопределять предыдущие.
files
Определяет файлы, для которых действует конфигурация:
{
files: ["src/**/*.js"]
}Несколько расширений:
{
files: ["src/**/*.{js,mjs,cjs}"]
}Конфигурация для тестов:
{
files: ["tests/**/*.js", "**/*.test.js"]
}ignores
Исключает файлы и каталоги из проверки:
{
ignores: [
"dist/**",
"coverage/**",
"public/vendor/**",
"**/*.min.js"
]
}Обычно не нужно проверять:
- зависимости;
- сгенерированные файлы;
- результаты сборки;
- отчёты покрытия;
- минифицированные сторонние библиотеки.
languageOptions
Настраивает синтаксис и окружение:
{
languageOptions: {
ecmaVersion: "latest",
sourceType: "module"
}
}Значения sourceType:
module — ES-модули с import и export
commonjs — CommonJS с require и module.exports
script — обычный скриптГлобальные переменные
Для браузерного проекта:
import globals from "globals";
export default [
{
languageOptions: {
globals: {
...globals.browser
}
}
}
];После этого ESLint распознает:
window
document
navigator
location
localStorageДля Node.js:
{
files: ["scripts/**/*.js"],
languageOptions: {
globals: {
...globals.node
}
}
}ESLint будет распознавать:
process
Buffer
__dirname
setImmediateДля тестов можно подключать глобальные переменные используемого тестового окружения:
{
files: ["**/*.test.js"],
languageOptions: {
globals: {
...globals.jest
}
}
}Набор должен соответствовать реальному тестовому инструменту.
Правила ESLint
Правила задаются в объекте rules:
{
rules: {
"no-debugger": "error",
"no-console": "warn",
"prefer-const": "error"
}
}У каждого правила есть уровень строгости:
| Значение | Число | Поведение |
|---|---|---|
"off" |
0 |
Правило отключено |
"warn" |
1 |
Показывается предупреждение |
"error" |
2 |
Показывается ошибка |
Примеры эквивалентных записей:
{
rules: {
"no-console": "warn",
"no-debugger": 2
}
}Предпочтительнее использовать строковые значения: они понятнее при чтении конфигурации.
Правила с параметрами
Некоторые правила принимают дополнительные настройки:
{
rules: {
"eqeqeq": ["error", "always"],
"quotes": ["error", "double"],
"semi": ["error", "always"]
}
}Первый элемент массива задаёт уровень, остальные — параметры правила.
eqeqeq
Требует строгого сравнения:
{
rules: {
"eqeqeq": ["error", "always"]
}
}Нежелательно:
if (value == 10) {
console.log("Равно");
}Предпочтительно:
if (value === 10) {
console.log("Равно");
}no-var
Запрещает var:
{
rules: {
"no-var": "error"
}
}Нежелательно:
var userName = "Анна";Предпочтительно:
const userName = "Анна";prefer-const
Требует const, если переменная не переназначается:
{
rules: {
"prefer-const": "error"
}
}Нежелательно:
let userName = "Анна";
console.log(userName);Предпочтительно:
const userName = "Анна";
console.log(userName);no-unused-vars
Находит неиспользуемые переменные:
{
rules: {
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
]
}
}Параметр разрешает намеренно неиспользуемые имена с _:
function handleError(_error, request, response) {
response.status(500).send("Ошибка");
}Такое соглашение должно быть единым для проекта.
no-console
Ограничивает использование Console API:
{
rules: {
"no-console": "warn"
}
}Можно разрешить отдельные методы:
{
rules: {
"no-console": [
"warn",
{
allow: ["warn", "error"]
}
]
}
}В серверных приложениях console может быть допустим, хотя для рабочих систем обычно используют структурированный логгер.
no-debugger
Запрещает оставлять инструкцию debugger:
{
rules: {
"no-debugger": "error"
}
}Проблемный код:
function submitForm() {
debugger;
sendRequest();
}Рекомендуемая конфигурация
Пакет @eslint/js предоставляет стандартные наборы правил:
import js from "@eslint/js";
export default [
js.configs.recommended
];Рекомендуемая конфигурация включает правила, направленные прежде всего на обнаружение возможных ошибок.
Её можно расширить:
import js from "@eslint/js";
import globals from "globals";
export default [
{
ignores: ["dist/**", "coverage/**"]
},
js.configs.recommended,
{
files: ["src/**/*.js"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser
}
},
rules: {
"eqeqeq": ["error", "always"],
"no-console": ["warn", { allow: ["warn", "error"] }],
"no-debugger": "error",
"no-var": "error",
"prefer-const": "error"
}
}
];Плагины ESLint
Плагины добавляют правила для определённых технологий.
Распространённые направления:
TypeScript
React
Vue
Node.js
импорты
тестовые фреймворки
доступность интерфейсов
безопасностьПравила плагинов обычно имеют префикс:
react/jsx-key
import/no-unresolved
@typescript-eslint/no-floating-promisesПлагин необходимо:
- установить;
- импортировать в конфигурацию;
- зарегистрировать или подключить его готовый набор;
- включить необходимые правила.
Не следует устанавливать множество плагинов без необходимости. Каждый дополнительный набор правил усложняет конфигурацию и может замедлять проверку.
Отключение правил ESLint
Правило можно временно отключить для одной строки:
console.log(user); // eslint-disable-line no-consoleИли для следующей строки:
// eslint-disable-next-line no-console
console.log(user);Для фрагмента:
/* eslint-disable no-console */
console.log("Начало");
console.log("Конец");
/* eslint-enable no-console */Для всего файла:
/* eslint-disable no-console */Отключение правила должно быть обоснованным. Не рекомендуется скрывать ошибки широким комментарием:
/* eslint-disable */Лучше отключить только конкретное правило и объяснить причину:
// Библиотека требует запись диагностического сообщения в консоль.
// eslint-disable-next-line no-console
console.warn("Используется резервный режим");Команды ESLint
Проверить весь проект:
npx eslint .Проверить каталог:
npx eslint srcПроверить конкретный файл:
npx eslint src/app.jsИсправить автоматически:
npx eslint . --fixЗапретить предупреждения в CI:
npx eslint . --max-warnings=0Вывести итоговую конфигурацию для файла:
npx eslint --print-config src/app.jsПоследняя команда помогает понять, какие правила фактически применяются к конкретному файлу.
Prettier
Prettier — автоматический форматтер кода.
Он отвечает за внешний вид:
- отступы;
- переносы строк;
- пробелы;
- кавычки;
- завершающие запятые;
- расположение скобок;
- максимальную длину строки;
- единое оформление файлов.
Prettier не предназначен для глубокого анализа логических ошибок.
Например, исходный код:
const user={id:42,name:"Анна",roles:["admin","editor"]}После форматирования:
const user = {
id: 42,
name: "Анна",
roles: ["admin", "editor"],
};Установка Prettier
npm install --save-dev prettierПроверка форматирования:
npx prettier . --checkАвтоматическое форматирование:
npx prettier . --writeФорматирование одного файла:
npx prettier src/app.js --writeКонфигурация Prettier
Конфигурация может храниться в разных форматах:
.prettierrc
.prettierrc.json
.prettierrc.yml
prettier.config.js
prettier.config.mjsПример .prettierrc.json:
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}printWidth
Рекомендуемая длина строки:
{
"printWidth": 80
}Это не строгий запрет. Prettier может оставить более длинную строку, если безопасно перенести её невозможно.
tabWidth
Размер одного уровня отступа:
{
"tabWidth": 2
}useTabs
Использовать табуляцию вместо пробелов:
{
"useTabs": false
}semi
Добавлять точку с запятой:
{
"semi": true
}singleQuote
Использовать одинарные кавычки в JavaScript:
{
"singleQuote": true
}Настройка не изменяет требования JSON: в JSON ключи и строки всё равно записываются в двойных кавычках.
trailingComma
Добавлять завершающую запятую:
{
"trailingComma": "all"
}Пример:
const user = {
id: 42,
name: "Анна",
};endOfLine
Стиль переноса строк:
{
"endOfLine": "lf"
}Это помогает избежать лишних изменений файлов между Windows, Linux и macOS.
.prettierignore
Файл .prettierignore исключает файлы из форматирования:
node_modules
dist
build
coverage
public/vendor
package-lock.json
*.min.jsНе следует без необходимости исключать исходный код проекта.
Некоторые сгенерированные файлы можно форматировать, но это увеличивает объём изменений и может конфликтовать с генератором.
Prettier и ESLint
ESLint и Prettier частично пересекаются в правилах оформления.
Например, ESLint может проверять:
кавычки
точки с запятой
отступы
переносы
запятыеОдновременно эти же части кода изменяет Prettier. Несогласованные настройки приводят к конфликту:
ESLint требует один вариант
Prettier записывает другой вариантДля отключения конфликтующих правил ESLint используется пакет:
npm install --save-dev eslint-config-prettierПодключать его следует после остальных конфигураций:
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
export default [
js.configs.recommended,
{
rules: {
"no-debugger": "error",
"prefer-const": "error"
}
},
eslintConfigPrettier
];eslint-config-prettier не запускает Prettier. Он только отключает правила ESLint, конфликтующие с форматтером.
Рекомендуемое разделение:
ESLint — качество и потенциальные ошибки
Prettier — форматированиеПроверка выполняется двумя командами:
npx eslint .
npx prettier . --checkАвтоматическое исправление:
npx eslint . --fix
npx prettier . --writeФорматирование в редакторе
Редактор можно настроить на автоматическое форматирование при сохранении.
Пример .vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}Настройки в .vscode/settings.json относятся к Visual Studio Code. Пользователям других редакторов они не мешают.
Важно установить расширения:
ESLint
Prettier
EditorConfigЕсли форматирование выполняется и Prettier, и встроенным форматтером редактора, результат может быть непредсказуемым. Для проекта лучше выбрать один основной форматтер.
EditorConfig
EditorConfig задаёт базовые правила редактирования файлов независимо от конкретного редактора.
Конфигурация хранится в файле:
.editorconfigОна может определять:
- тип отступа;
- размер отступа;
- кодировку;
- стиль переноса строк;
- наличие завершающей пустой строки;
- удаление пробелов в конце строк.
Пример:
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tabroot
root = trueОстанавливает поиск других .editorconfig в родительских каталогах.
Без этого редактор может найти конфигурацию выше по файловой системе и объединить её с настройками проекта.
charset
charset = utf-8Задаёт кодировку файлов.
end_of_line
end_of_line = lfИспользует переносы строк LF.
Это уменьшает количество изменений при работе на разных операционных системах.
insert_final_newline
insert_final_newline = trueДобавляет перенос строки в конце файла.
indent_style
Пробелы:
indent_style = spaceТабуляция:
indent_style = tabindent_size
indent_size = 2Задаёт размер уровня отступа.
trim_trailing_whitespace
trim_trailing_whitespace = trueУдаляет пробелы в конце строк.
Для Markdown иногда используют:
[*.md]
trim_trailing_whitespace = falseВ Markdown два пробела в конце строки могут иметь специальное значение и создавать перенос.
EditorConfig и Prettier
EditorConfig и Prettier также частично пересекаются.
EditorConfig задаёт общие настройки редактора:
кодировка
отступы
переносы строк
завершающая строка
пробелы в конце строкPrettier форматирует структуру поддерживаемых языков:
JavaScript
TypeScript
JSON
CSS
HTML
Markdown
YAMLОбычно используют оба инструмента:
EditorConfig — базовое поведение редактора
Prettier — полное форматирование кодаИх настройки должны совпадать:
# .editorconfig
indent_size = 2
end_of_line = lf{
"tabWidth": 2,
"endOfLine": "lf"
}Git hooks
Git hook — скрипт, который Git запускает при определённом событии.
Локальные хуки находятся в каталоге:
.git/hooksПримеры:
| Hook | Когда выполняется |
|---|---|
pre-commit |
Перед созданием коммита |
prepare-commit-msg |
Перед открытием редактора сообщения |
commit-msg |
После получения сообщения, до создания коммита |
post-commit |
После создания коммита |
pre-push |
Перед отправкой коммитов в удалённый репозиторий |
Обычный каталог .git/hooks не отслеживается Git. Поэтому для общей настройки команды используют Husky или другой менеджер hooks.
pre-commit
Hook pre-commit выполняется до создания коммита.
Подходящие проверки:
- ESLint для подготовленных файлов;
- Prettier для подготовленных файлов;
- быстрые тесты;
- проверка случайно добавленных секретов;
- проверка запрещённых файлов.
Пример логики:
npx lint-stagedЕсли команда завершится с ненулевым кодом, коммит не будет создан.
pre-commit должен выполняться быстро. Если в нём запускать полную сборку и все интеграционные тесты, разработчики начнут обходить проверку.
Долгие проверки лучше выполнять:
- перед отправкой через
pre-push; - в CI;
- по отдельной команде.
commit-msg
Hook commit-msg проверяет сообщение коммита.
Git передаёт ему путь к временному файлу с сообщением:
npx --no -- commitlint --edit "$1"Он позволяет запретить сообщения вида:
update
fix
changes
work
исправленияи потребовать структурированный формат:
feat(auth): add password resetЕсли сообщение не соответствует правилам, коммит не создаётся.
Обход Git hooks
Git позволяет пропустить некоторые клиентские хуки:
git commit --no-verifyСокращённый вариант:
git commit -nПоэтому локальный hook не является абсолютной гарантией качества или безопасности.
Критичные проверки необходимо повторять в CI:
ESLint
Prettier --check
тесты
проверка типов
проверка сборки
commitlint при необходимостиHook помогает разработчику получить быструю обратную связь, а CI обеспечивает единый обязательный контроль.
Husky
Husky упрощает хранение Git hooks внутри проекта.
Установка:
npm install --save-dev huskyИнициализация:
npx husky initОбычно команда:
- создаёт каталог
.husky; - создаёт пример
pre-commit; - добавляет или обновляет скрипт
prepareвpackage.json.
Пример:
{
"scripts": {
"prepare": "husky"
}
}После установки зависимостей npm выполняет prepare, и Husky настраивает hooks для локального репозитория.
В CI или архиве без каталога .git поведение установки Husky может потребовать отдельной настройки.
Настройка pre-commit в Husky
Файл:
.husky/pre-commitСодержимое:
npx lint-stagedПри попытке создать коммит Git запустит lint-staged.
Не рекомендуется без необходимости запускать ESLint по всему проекту:
npx eslint .В большом проекте такая проверка может быть медленной. Для изменяемых файлов лучше использовать lint-staged, а полный проект проверять в CI.
lint-staged
lint-staged запускает команды только для файлов, добавленных в индекс Git.
Установка:
npm install --save-dev lint-stagedФайлы попадают в индекс после команды:
git add src/app.jsСостояние индекса можно проверить:
git statuslint-staged не должен форматировать весь проект при каждом коммите. Он выбирает только подготовленные файлы, подходящие под шаблоны.
Конфигурация lint-staged
Конфигурацию можно добавить в package.json:
{
"lint-staged": {
"*.{js,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,scss,html,md,yml,yaml}": [
"prettier --write"
]
}
}Здесь для JavaScript сначала выполняется ESLint, затем Prettier.
Для остальных поддерживаемых форматов запускается только Prettier.
Отдельный файл конфигурации:
lint-staged.config.mjsexport default {
"*.{js,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,scss,html,md,yml,yaml}": [
"prettier --write"
]
};Запуск вручную:
npx lint-stagedlint-staged передаёт командам имена выбранных файлов. Поэтому не нужно добавлять точку:
Правильно:
eslint --fix
Нежелательно:
eslint . --fixВторой вариант проверит весь проект, а не только staged-файлы.
Как lint-staged изменяет файлы
Допустим, разработчик выполнил:
git add src/app.js
git commit -m "fix: correct user validation"Далее происходит:
Git запускает pre-commit
↓
Husky выполняет npx lint-staged
↓
lint-staged находит staged-файл src/app.js
↓
ESLint исправляет доступные ошибки
↓
Prettier форматирует файл
↓
Исправления добавляются в коммит
↓
Git продолжает создание коммитаЕсли исправить проблему автоматически нельзя, команда завершается ошибкой:
Коммит не созданРазработчик должен исправить код, снова добавить файл и повторить коммит:
git add src/app.js
git commit -m "fix: correct user validation"Частично подготовленные файлы
Один файл может содержать:
- изменения, добавленные в индекс;
- изменения, ещё не добавленные в индекс.
Пример:
git add -p src/app.jslint-staged умеет работать с частично подготовленными файлами, временно скрывая неподготовленные изменения и затем восстанавливая их.
Несмотря на это, при сложных изменениях рекомендуется:
- проверять
git diff; - проверять
git diff --staged; - делать резервную копию незавершённой работы;
- избегать прерывания lint-staged во время изменения файлов.
Просмотр подготовленных изменений:
git diff --stagedConventional Commits
Conventional Commits — соглашение о формате сообщений коммитов.
Основной шаблон:
type(scope): descriptionРасширенный шаблон:
type(scope)!: description
body
footerПример:
feat(auth): add password resetС областью без критического изменения:
fix(api): handle empty responseБез области:
docs: update installation guideТипы Conventional Commits
feat
Новая функциональность:
feat(profile): add avatar uploadfix
Исправление ошибки:
fix(cart): prevent duplicate productsdocs
Изменение документации:
docs: describe environment variablesstyle
Изменения оформления кода, не влияющие на его поведение:
style: format configuration filesstyle не означает изменение CSS-дизайна. Изменение внешнего вида интерфейса может быть feat или fix в зависимости от задачи.
refactor
Изменение структуры кода без добавления функции и без исправления пользовательской ошибки:
refactor(auth): extract token serviceperf
Оптимизация производительности:
perf(search): cache normalized queriestest
Добавление или изменение тестов:
test(api): add user validation casesbuild
Изменение сборки или зависимостей:
build: update bundler configurationci
Изменение CI-конфигурации:
ci: add lint jobchore
Служебное изменение:
chore: update development dependenciesНе следует использовать chore для любых изменений без разбора. Если изменение исправляет ошибку или добавляет функцию, лучше выбрать fix или feat.
revert
Отмена предыдущего изменения:
revert: remove experimental cacheScope
scope уточняет часть проекта:
auth
api
cart
profile
config
deps
ui
databaseПримеры:
feat(auth): add two-factor authentication
fix(api): return 404 for missing user
refactor(database): extract query builder
test(cart): cover empty cart behaviorScope является необязательным, если правила проекта не требуют обратного.
Названия областей должны быть короткими и единообразными. Не рекомендуется одновременно использовать варианты:
auth
authentication
login
authorizationесли они обозначают одну и ту же часть системы.
Description
Краткое описание идёт после двоеточия и пробела:
fix(api): handle expired access tokenХорошее описание отвечает на вопрос:
Что делает этот коммит?Предпочтительно:
add password reset
handle empty API response
remove deprecated endpoint
update installation instructionsМенее информативно:
changes
update
fix bug
work
final versionЧасто описание пишут:
- в нижнем регистре;
- без точки в конце;
- в повелительной или краткой форме;
- на одном согласованном языке.
Правила языка и формулировок определяет команда.
Тело коммита
Тело объясняет причину и детали изменения:
fix(cache): prevent stale user profile
Invalidate the profile cache after updating the user's email.
Previously, the old value remained visible until the cache expired.Тело отделяется от заголовка пустой строкой.
В нём можно описать:
- причину изменения;
- выбранное решение;
- ограничения;
- отличие от предыдущего поведения;
- важные детали миграции.
Не стоит пересказывать в теле каждую изменённую строку. Это уже видно в diff.
Breaking changes
Несовместимое изменение можно отметить символом !:
feat(api)!: remove deprecated user endpointИли футером:
feat(api): change user response format
BREAKING CHANGE: the `fullName` field was replaced by `firstName`
and `lastName`.Можно использовать оба варианта:
feat(api)!: change user response format
BREAKING CHANGE: clients must read `firstName` and `lastName`
instead of `fullName`.Breaking change означает, что потребителям потребуется изменить код, конфигурацию или способ использования API.
Связь Conventional Commits с версиями
Соглашение удобно использовать вместе с Semantic Versioning:
MAJOR.MINOR.PATCHТипичное соответствие:
| Коммит | Изменение версии |
|---|---|
fix |
PATCH |
feat |
MINOR |
BREAKING CHANGE |
MAJOR |
Например:
1.4.2 → 1.4.3 fix
1.4.2 → 1.5.0 feat
1.4.2 → 2.0.0 breaking changeАвтоматическое повышение версии зависит от настроек инструмента выпуска. Сам формат Conventional Commits не публикует релиз самостоятельно.
Проверка сообщений через commitlint
Для автоматической проверки Conventional Commits используется commitlint.
Установка:
npm install --save-dev \
@commitlint/cli \
@commitlint/config-conventionalСоздайте файл:
commitlint.config.mjsСодержимое:
export default {
extends: ["@commitlint/config-conventional"]
};Проверка сообщения вручную:
echo "feat(auth): add password reset" | npx commitlintНекорректный пример:
echo "updated files" | npx commitlintПроверка должна завершиться ошибкой, потому что отсутствует Conventional Commits type.
Настройка commit-msg в Husky
Создайте файл:
.husky/commit-msgСодержимое:
npx --no -- commitlint --edit "$1"После этого команда:
git commit -m "feat(auth): add login form"пройдёт проверку.
Команда:
git commit -m "added login form"будет отклонена.
Параметр $1 содержит путь к файлу, в котором Git сохранил сообщение коммита.
Настройка правил commitlint
Правила можно переопределять:
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [2, "always", 100],
"subject-empty": [2, "never"],
"type-empty": [2, "never"],
"subject-full-stop": [2, "never", "."]
}
};Как и в ESLint, уровни задаются числами:
0 — отключено
1 — предупреждение
2 — ошибкаОграничение разрешённых типов
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"build",
"ci",
"chore",
"revert"
]
]
}
};Ограничение scope
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"scope-enum": [
2,
"always",
[
"api",
"auth",
"cart",
"config",
"profile",
"ui"
]
]
}
};Такое ограничение удобно в стабильном проекте, но может мешать, если структура приложения часто меняется.
Полный пример настройки
Установите зависимости:
npm install --save-dev \
eslint \
@eslint/js \
globals \
prettier \
eslint-config-prettier \
husky \
lint-staged \
@commitlint/cli \
@commitlint/config-conventionalpackage.json
{
"name": "example-project",
"version": "1.0.0",
"private": true,
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"check": "npm run lint && npm run format:check",
"prepare": "husky"
},
"lint-staged": {
"*.{js,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,scss,html,md,yml,yaml}": [
"prettier --write"
]
}
}eslint.config.mjs
import js from "@eslint/js";
import globals from "globals";
import eslintConfigPrettier from "eslint-config-prettier";
export default [
{
ignores: [
"node_modules/**",
"dist/**",
"build/**",
"coverage/**",
"**/*.min.js"
]
},
js.configs.recommended,
{
files: ["src/**/*.{js,mjs}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser
}
},
rules: {
"eqeqeq": ["error", "always"],
"no-console": [
"warn",
{
allow: ["warn", "error"]
}
],
"no-debugger": "error",
"no-var": "error",
"prefer-const": "error",
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
]
}
},
{
files: [
"eslint.config.mjs",
"commitlint.config.mjs",
"lint-staged.config.mjs",
"scripts/**/*.js"
],
languageOptions: {
globals: {
...globals.node
}
}
},
eslintConfigPrettier
];.prettierrc.json
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}.prettierignore
node_modules
dist
build
coverage
public/vendor
package-lock.json
*.min.js.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tabcommitlint.config.mjs
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [2, "always", 100],
"subject-empty": [2, "never"],
"type-empty": [2, "never"],
"subject-full-stop": [2, "never", "."]
}
};.husky/pre-commit
npx lint-staged.husky/commit-msg
npx --no -- commitlint --edit "$1"Инициализация проекта
После создания конфигурационных файлов выполните:
npm installЕсли Husky ещё не инициализирован:
npx husky initПроверьте, что в .husky/pre-commit осталась нужная команда:
npx lint-stagedСоздайте .husky/commit-msg:
npx --no -- commitlint --edit "$1"Проверьте ESLint:
npm run lintПроверьте Prettier:
npm run format:checkИсправьте файлы:
npm run lint:fix
npm run formatПроверьте hooks:
git add .
git commit -m "chore: configure code quality tools"Пример рабочего процесса
Разработчик изменяет файл:
const loadUser=async(id)=>{
const response=await fetch(`/api/users/${id}`)
return response.json()
}Затем добавляет его в индекс:
git add src/api.jsПроверяет подготовленные изменения:
git diff --stagedСоздаёт коммит:
git commit -m "feat(api): add user loading"Hook pre-commit запускает:
lint-stagedESLint и Prettier преобразуют код:
const loadUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};После этого commit-msg проверяет:
feat(api): add user loadingЕсли обе проверки завершились успешно, Git создаёт коммит.
Проверки в CI
Локальные hooks можно обойти, поэтому основные проверки следует повторять в CI.
Минимальный набор команд:
npm ci
npm run lint
npm run format:check
npm test
npm run buildДля ESLint в CI полезно запретить предупреждения:
{
"scripts": {
"lint": "eslint . --max-warnings=0"
}
}Но такое правило имеет смысл только в том случае, если команда действительно считает каждое предупреждение блокирующей проблемой.
Обычно CI не должен выполнять:
prettier . --writeCI не сохраняет автоматические исправления в репозитории. Вместо этого используется проверка:
prettier . --checkЧастые проблемы
ESLint и Prettier постоянно изменяют код друг после друга
Причина — конфликт правил форматирования.
Установите:
npm install --save-dev eslint-config-prettierИ подключите его последним:
export default [
// Другие конфигурации
eslintConfigPrettier
];ESLint не распознаёт window или document
Не настроено браузерное окружение:
import globals from "globals";
export default [
{
languageOptions: {
globals: {
...globals.browser
}
}
}
];ESLint не распознаёт process
Для соответствующих файлов добавьте Node.js globals:
{
files: ["scripts/**/*.js"],
languageOptions: {
globals: {
...globals.node
}
}
}Prettier форматирует ненужные файлы
Добавьте их в .prettierignore:
dist
coverage
public/vendorHook не запускается
Проверьте:
- установлен ли Husky;
- существует ли каталог
.git; - выполнен ли скрипт
prepare; - существует ли файл
.husky/pre-commit; - нет ли ошибок в hook;
- запускается ли команда вручную.
Повторная настройка:
npm run prepareПроверка lint-staged:
npx lint-stagedlint-staged ничего не проверяет
lint-staged работает только с файлами, добавленными в индекс:
git add src/app.js
npx lint-stagedПроверьте состояние:
git statusКоммит отклоняется из-за сообщения
Проверьте формат:
type(scope): descriptionПравильно:
fix(auth): handle expired tokenНеправильно:
Fixed expired tokenПосле форматирования коммит содержит неожиданные изменения
Проверьте индекс:
git diff --stagedПри необходимости отмените добавление файла в индекс:
git restore --staged src/app.jsКоманда убирает файл из staged-состояния, но обычно сохраняет изменения в рабочем каталоге.
Рекомендации по организации
Конфигурационные файлы следует хранить в репозитории:
.editorconfig
.prettierrc.json
.prettierignore
eslint.config.mjs
commitlint.config.mjs
.husky/pre-commit
.husky/commit-msg
package.json
package-lock.jsonВерсия Node.js также должна быть зафиксирована или задокументирована, например через:
.nvmrc
.node-version
engines в package.jsonПример:
{
"engines": {
"node": ">=22"
}
}Конкретный диапазон должен соответствовать требованиям проекта и используемых инструментов.
Не следует добавлять в pre-commit слишком долгие операции. Оптимальное разделение:
pre-commit — быстрые проверки изменённых файлов
commit-msg — проверка сообщения коммита
pre-push — более длительные локальные проверки при необходимости
CI — полный обязательный набор проверокКраткая памятка
Установка инструментов:
npm install --save-dev \
eslint \
@eslint/js \
globals \
prettier \
eslint-config-prettier \
husky \
lint-staged \
@commitlint/cli \
@commitlint/config-conventionalОсновные команды:
npx eslint .
npx eslint . --fix
npx prettier . --check
npx prettier . --write
npx lint-stagedСкрипты package.json:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"check": "npm run lint && npm run format:check",
"prepare": "husky"
}
}Hook pre-commit:
npx lint-stagedHook commit-msg:
npx --no -- commitlint --edit "$1"Формат Conventional Commits:
type(scope): descriptionПримеры:
feat(auth): add password reset
fix(api): handle empty response
docs: update installation guide
refactor(profile): extract avatar service
test(cart): add empty cart test
chore: update development dependenciesОсновные правила:
- ESLint должен проверять качество и возможные ошибки;
- Prettier должен отвечать за форматирование;
eslint-config-prettierдолжен отключать конфликтующие правила;- EditorConfig должен согласовывать базовое поведение редакторов;
- lint-staged должен обрабатывать только подготовленные файлы;
pre-commitдолжен оставаться быстрым;commit-msgдолжен проверять структуру сообщения;- Git hooks необходимо хранить в репозитории через Husky;
- важные проверки необходимо повторять в CI;
- сообщения коммитов должны объяснять смысл изменения;
- hooks не должны быть единственной защитой, поскольку их можно пропустить через
--no-verify.