이번에 PR #183, 블로그 글 🔥 린트 eslintrc.cjs -> eslint.config.js : Flat Config 업그레이드! 에서 eslint.config.js 를 제대로 도입하게 되면서, 프로젝트의 코드 품질을 일관되게 유지하고 팀원 간의 협업 효율성을 높이기 위해 husky, lint-staged, commitlint 세 가지 도구의 도입을 제안했다.
github discussion 활용, 배포 회의때 동료에게 설명하였다.
eslint 기반 Git Hook 품질 검사 도구 - Husky, lint-staged, commitlint · IT-Cotato 10th-Kampus-FE · Discussion #184
이번에 PR #183 에서 eslint.config.js 를 제대로 도입하게 되면서, 프로젝트의 코드 품질을 일관되게 유지하고 팀원 간의 협업 효율성을 높이기 위해 husky, lint-staged, commitlint 세 가지 도구의 도입을 제
github.com
작업한 PR 이다.
[CHORE] git hooks 자동화, build 전에 Prettier와 ESLint 검사 by chae-dahee · Pull Request #194 · IT-Cotato/10th-Kampus-
개요 Resolves: #192 discussions : #184 PR 유형 어떤 변경 사항이 있나요? 새로운 기능 추가 빌드 부분 혹은 패키지 매니저 수정 작업 내용 커밋 add 된 파일만 검사합니다 (lint-staged 역할) 커밋을 실행했을
github.com
🐶 Git Hooks 자동화 시스템 Husky
Git Hooks : Git 특정 이벤트(commit, add, push) 발생 전후로, 자동으로 특정 스크립트hook을 실행할 수 있도록 하는 기능. 이때 코드 품질, 커밋 메시지 컨벤션 일관성 검사 과정을 자동적으로 실행한다.
예를 들어, 커밋된 코드는 필수로 formatting(prettier) 되어야 하고, push 된 코드는 elinst 가 pass 이여야만 한다는 규칙을 자동화로 구현할 수 있다.
husky 는 Git Hooks 를 쉽게 사용할 수 있는 도구이다.
husky 를 사용하지 않을 경우, 직접 .git/hooks 디렉토리에 직접 스크립트를 작성해야 한다.
husky 를 사용하여 .husky/pre-commit 등 디렉토리에 명령어를 사용하여 돌아갈 수 있도록 한다. (v4 이하 package.json 필드에 직접 정의하는 방식)
💡개발자가 수동으로 검사 도구(eslint, prettier)를 실행하는 것을 잊더라도, 자동으로 실행되므로 휴먼 에러를 방지하고, 팀 전체가 동일한 코드 품질 기준을 따르도록 강제할 수 있다.
즉, husky 를 통해 lint-staged 와 commitlint 실행을 자동화 하는 것이 핵심이다.
1. 개발 의존성으로 husky 를 설치한다.
npm install -D husky
2. 이후 husky 에 설정할 lint-staged, commitlint 설치가 필요하다.
2-1. lint-staged, commiltint 를 먼저 설정한 후
✚ staging 된 파일에 대한 자동 린팅 lint-staged
husky 에서 직접 eslint, prettier 를 실행시킬 수도 있다.
그러나 검사를 수행한 후 커밋이 진행되기까지 꽤 긴 시간을 필요로 한다. eslint 와 prettier 가 모든 파일을 검사하기 때문이다.
lint-staged 는 커밋하기 위해 add 스테이징 된 파일에 대해서만 검사를 할 수 있도록 한다. staged 된 파일들 → 변경사항이 있고 품질검사가 필요한 파일들만 검사하여 시간을 단축하고, 효율성을 높인다.
npm install -D lint-staged
package.json 파일에 lint-stage 를 정의하여, prettier 와 eslint 로 검사할 수 있도록 설정한다.
"devDependencies": {
"lint-staged": "^16.1.2",
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
🚨 eslint 에 대해서 warn 에 대한 경고는 건너띄고 싶다면 `--quiet` 옵션을 뒤에 붙여줄 수 있다. 그러나 build 배포단계에서 eslint 를 검사할때 warn 도 마찬가지로 문제라고 검사하고 실패를 발생시키기 때문에, 해당 옵션은 사용하지 않는 것을 추천한다.
커밋 전에 변경된 코드에 대한 피드백(formatting, linting 관련)을 즉시 받아볼 수 있어 코드 품질 문제를 미리 발견하고 수정할 수 있다. 또한, 전체 프로젝트에 대한 검사 비용 없이 코드 스타일을 관리할 수 있습니다.
😈 커밋 컨벤션 검사 commitlint
commitlint 는 Git 커밋 메시지가 팀의 규칙을 따르는지 검사하는 도구이다.
커밋 메시지를 정해진 규칙에 따라 작성하도록 강제하여, 팀의 커밋 히스토리를 깔끔하고 일관되게 만들어주는 역할을 한다.
프로젝트가 커지고 팀원이 늘어날수록 Git 커밋 히스토리를 관리하는 일은 중요해진다. 어떤 변경사항이 있었는지 쉽게 파악하고, 특정 기능을 찾거나 버그의 원인을 추적할 때 잘 정리된 커밋 메시지는 도움이 된다.
커밋 이력만 보더라도 어떤 변경사항이 있었는지 쉽게 파악할 수 있게 되어 코드 리뷰나 프로젝트 히스토리 추적이 용이해진다. 규칙에 어긋나는 메시지일 경우 커밋이 실패하고 어떤 규칙을 위반했는지 알려준다.
npm install -D @commitlint/cli @commitlint/config-conventional
Conventional Commits 커밋 메시지 형식 규칙을 기반으로 커밋 메시지의 일관성, 가독성을 확보한다.
type(scope): subject
<--빈 줄-->
body
<--빈 줄-->
footer
여기서 가장 중요한 type은 해당 커밋이 어떤 성격의 변경사항인지를 나타낸다.
우리 프로젝트에서는 아래와 같은 타입을 정의하여 사용하고 있다.
타입 (Type) | 설명 |
feat | 새로운 기능 추가 |
fix | 버그 수정 |
docs | 문서 수정 (README.md 등) |
style | 코드 포맷팅, 세미콜론 누락 등 (기능 변경 없음) |
refactor | 코드 리팩토링 |
test | 테스트 코드 추가 또는 수정 |
chore | 빌드 업무, 패키지 매니저 설정 등 (프로덕션 코드 변경 없음) |
perf | 성능 개선 |
ci | CI/CD 관련 설정 변경 |
build | 빌드 시스템 또는 외부 종속성 관련 변경사항 |
커밋 유형을 위 10가지로 제한하기 위해 명시적으로 선언한다.
commitlint.config.js → @commitlint/config-conventional 패키지에 미리 정의된, 가장 대중적인 Conventional Commits 규칙을 상속받아 기본으로 사용하기 위해 extends 를 설정한다.
export default {
extends: ['@commitlint/config-conventional'],
rules: {
// 커밋 타입 규칙
'type-enum': [
2,
'always',
[
'feat', // 새로운 기능
'fix', // 버그 수정
'docs', // 문서 수정
'style', // 코드 포맷팅, 세미콜론 누락 등 (기능 변경 없음)
'refactor', // 코드 리팩토링
'test', // 테스트 코드 추가/수정
'chore', // 빌드 업무 수정, 패키지 매니저 수정 등
'perf', // 성능 개선
'ci', // CI/CD 관련
'build', // 빌드 시스템 또는 외부 종속성에 영향을 주는 변경사항
],
],
// 커밋 타입 소문자, 비어있으면 안됨
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
// 제목 50자 이하, 비어있거나 마침표로 끝나면 안됨
'subject-max-length': [2, 'always', 50],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
// 본문, 푸터 72자 이하로 줄바꿈
'body-max-line-length': [2, 'always', 72],
'footer-max-line-length': [2, 'always', 72],
},
parserPreset: {
parserOpts: {
// 기본 형식: type: subject
// 예: feat: 새로운 기능 추가
headerPattern: /^(\w*): (.*)$/,
headerCorrespondence: ['type', 'subject'],
maxLineLength: 72,
},
},
};
우리 프로젝트만의 커밋 규칙을 지정하기위해 rules 를 통해 덮어씌웠다.
commitlint의 모든 규칙은 기본적으로 세 부분으로 이루어진 배열 `[Level, Applicable, Value]`
- Level (강도): 규칙 위반 시 어떻게 처리할지 결정
- 0: 규칙을 적용하지 않음 (off)
- 1: 경고(Warning)만 표시하고 커밋은 허용
- 2: 오류(Error)를 표시하고 커밋 중단
- Applicable (적용 조건): 규칙을 언제 적용할지
- always: 항상 규칙을 지켜야 함
- never: 절대 규칙을 위반하면 안 됨 (예: 제목 끝에 마침표를 쓰지 마라)
- Value (값): 규칙에 사용할 구체적인 값 (예시는 아래와 같음)
- 커밋 타입 type
- type-enum: feat, fix 등 허용할 커밋 타입의 목록을 정의한다. 여기에 정의되지 않은 타입으로 커밋하면 오류를 발생시킨다.
- type-case: 커밋 타입은 항상 lower-case(소문자)여야 한다는 규칙
- type-empty: 커밋 타입은 비어 있을 수 없다는 규칙
- 커밋 제목 subject
- subject-empty: 커밋의 제목은 비어 있을 수 없다
- subject-max-length: 커밋 제목의 최대 길이를 50자로 제한한다. 제목은 간결하게 작성하는 것이 좋기 때문이다.
- subject-full-stop: 커밋 제목은 마침표(.)로 끝나면 안 된다는 규칙
- 커밋 본문 body, 푸터 footer
- body-max-line-length: 커밋 본문의 한 줄은 최대 72자를 넘지 않도록 설정
- footer-max-line-length: 커밋 푸터의 한 줄도 최대 72자를 넘지 않도록 설정
- parserPreset 을 통해 커밋 메시지의 형식을 규정한다. 해당 규칙으로 커밋 메시지를 해석(파싱)한다고 정의하는 부분
- headerPattern: 커밋 메시지의 헤더(첫 줄)가 어떤 정규식 패턴을 따라야 하는지 정의한다.
- `^(\w*): (.*)$`는 `(타입): (제목)` 형식
- headerCorrespondence: headerPattern의 각 그룹이 무엇에 해당하는지 알려준다.
- 첫 번째 그룹(\w*) type, 두 번째 그룹(.*) subject
- headerPattern: 커밋 메시지의 헤더(첫 줄)가 어떤 정규식 패턴을 따라야 하는지 정의한다.
🚨 만일 규칙을 지정하고 싶지 않다면, 해당 속성을 지우면 된다.
feat: 드롭다운 컴포넌트의 디자인 변경
Dropdown 컴포넌트의 색상 변경입니다.
☄️ husky 에 lint-staged, commitlint 설정하기
이제 Husky를 활성화하고, Git 이벤트와 위에서 설정한 도구들을 연결한다. (이전 단계에서 설치만 함)
1. Husky 초기화
npx husky init
.husky 폴더가 생성되며 Husky가 활성화된다.
2. pre-commit Hook 추가 (lint-staged 연동)
커밋 직전에 lint-staged를 실행하도록 아래 명령어로 pre-commit hook을 추가한다.
많은 블로그 글에서는 echo 를 사용했는데, 공식문서 Migrate from v4 에는 npm, yarn 을 사용할 시 훨씬 간편하게 훅을 추가할 수 있다고 한다.
npx husky add .husky/pre-commit "npx lint-staged"
.husky/pre-commit 파일이 아래처럼 생성된다. 이제 커밋 시마다 자동으로 코드 스타일과 품질 검사가 이루어집니다.
🚫 lint-staged 가 아닌, prettier 와 eslint 를 직접 선언하게 되면 → 커밋 때에 프로젝트의 모든 파일에 대해 검사하게 되어 비효율적으로 동작한다.
3. commit-msg Hook 추가 (commitlint 연동)
커밋 메시지를 검사하도록 아래 명령어로 commit-msg hook을 추가한다.
npx husky add .husky/commit-msg "npx commitlint --edit $1"
✨ 여기서 `$1`은 Husky가 commit-msg 훅을 실행할 때, 작성된 커밋 메시지가 임시로 저장된 파일의 경로를 commitlint에 전달하는 매우 중요한 파라미터이다.
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
🚨 위처럼 초기화 될 수도 있는데, husky deprecated 경고가 발생하기 때문에 위의 두줄은 제거해도 좋다.
🔄 실행 흐름
개발자가 커밋을 하면 아래와 같은 순서로 작업이 자동으로 진행된다.
- 개발자가 파일을 수정하고 `git add .` 명령어로 파일들을 staged 상태로 만든다.
- `git commit -m "feat: 새로운 기능 추가"` 명령어를 실행한다.
- Husky: pre-commit 이벤트를 감지하고 `.husky/pre-commit 파일`을 실행한다.
- lint-staged: `npx lint-staged`가 실행되어 staged된 파일들에 대해 ESLint와 Prettier를 실행한다.
- 만약 여기서 오류가 발생하면, 수정이 필요한 부분을 알려주며 커밋 중단
- Husky: pre-commit이 성공하면 commit-msg 이벤트를 감지하고 `.husky/commit-msg 파일`을 실행한다.
- commitlint: `npx commitlint`가 실행되어 작성된 커밋 메시지가 conventional 규칙에 맞는지 검사한다.
- 규칙에 맞아서 커밋 메시지가 유효한 경우
- 규칙에 맞지 않아, 문제가 발생한 경우: 올바른 형식을 알려주며 커밋이 중단
7. 성공: 모든 검사를 통과하면 비로소 커밋이 최종적으로 완료된다.
간단히 요약하자면
💡 git commit을 실행하면 Husky가 각 단계를 가로채 아래 작업들을 실행
1-1. 코드 자동 수정 및 검사 (pre-commit 단계): Husky가 lint-staged를 실행시켜, staged 상태인 파일들의 코드 포맷팅(Prettier)과 문법 오류(ESLint)를 자동으로 수정하고 검사한다. 여기서 문제가 발생하면 커밋 중단
1-2. 커밋 메시지 검사 (commit-msg 단계): 1-1 코드 검사를 통과하면, Husky가 이번엔 commitlint를 실행시켜서 커밋 메시지가 정해진 규칙(커밋 컨벤션)에 맞는지 검사한다. 여기서 규칙을 어기면 커밋 중단
🤗 도입 후 기대효과
이 도구들을 도입한 것은 단순히 기능을 추가하는 것을 넘어서, 팀의 개발 문화를 발전시키는 중요한 과정이라고 생각했다. 초기 설정에 드는 시간은 짧았지만, 그로 인해 얻는 장기적인 효과는 매우 클 것으로 기대했다.
- 휴먼 에러 감소 및 품질 보장
- 개발자가 매번 신경 써야 했던 코드 스타일이나 커밋 규칙 같은 부분들이 시스템을 통해 강제되었다.
- 이로써 사소한 실수로 인한 버그나 코드 품질 저하를 원천적으로 방지하고, 항상 일관된 상태의 코드 베이스를 유지할 수 있었다.
- 학습 곡선 없는 생산성 향상
- 이 모든 변화는 개발자가 git commit을 실행하는 순간 자동으로 적용된다. 팀원들이 새로운 명령어나 복잡한 절차를 배울 필요가 없으므로, 별도의 학습 곡선 없이 팀 전체의 개발 생산성을 즉시 높일 수 있었다.
- 코드 리뷰에서는 이제 스타일 지적 대신 핵심 로직에 더 집중할 수 있다.
- 명확한 협업 기준 정립
- 자동화된 규칙은 팀의 코드 작성과 소통에 대한 명확한 기준이 되었다.
- 누가 프로젝트에 참여하든 같은 규칙을 따르게 되므로, 협업의 효율성이 높아지고, 잘 정리된 커밋 히스토리는 프로젝트의 흐름을 파악하는 데 큰 도움을 주었다.
결론적으로, 이 시스템의 도입은 적은 초기 투자로 장기적인 개발 효율성과 코드 베이스의 안정성을 모두 챙길 수 있는 전략이었다. 처음에는 규칙에 맞춰 커밋 메시지를 작성하거나, 코드의 규칙을 따르는 것이 다소 번거롭게 느껴질 수 있다. 하지만 이 노력이 모여 전체 프로젝트의 히스토리를 명확하게 만들고, 미래에 발생할 수 있는 문제를 더 쉽게 해결하는 데 큰 도움이 될 수 있다.