[TIL 18] - [Git] pre-commit과 pre-push(feat. Eslint, Prettier, lint-staged)

찐새·2023년 6월 27일
0

TIL

목록 보기
17/20
post-thumbnail
post-custom-banner

1. linter와 formatter를 사용하는 이유

코드의 가독성을 높이고 실수를 미연에 방지하기 위해 linter와 formatter를 사용한다. 에디터의 익스텐션을 사용하면 손쉬운 설정으로 쾌적한 코딩 환경을 만들 수 있다.

하지만 협업을 해야 한다면 이야기가 달라진다. 나에게 편하고 익숙한 환경이 누군가에게는 어질어질하게 보일 수 있고, 서로 다른 linter는 각종 에러와의 사투를 유발할 수 있다. 이러한 불편을 미연에 방지하기 위해서 팀 단위로 논의하여 미리 lint와 formatter 규칙을 정한다.

2. Eslint와 Prettier 설정

대표적인 linter인 eslint와 대표적인 formatter인 prettier를 설치한다.

npm install -D eslint prettier eslint-config-prettier

eslint-config-prettiereslintprettier의 설정 충돌을 방지해준다. 예를 들어, prettiersingleQuote를 사용하지만 eslint는 금지한다면 둘이서 무한으로 즐길 것이다.

설정 파일을 만들어 필요한 규칙과 설정을 추가한다. 작성 양식은 파일 확장자에 따라 다른데, 각각의 공식문서에서 확인할 수 있다.

eslint - Configuration File Formats
prettier - Configuration File

// .eslintrc
{
  // eslint가 es6를 파싱할 수 있게 설정
  "env": { "es6": true },
  "rules": {
    // var 선언을 허용하지 않음
    "no-var": "error",
    // allow 메서드를 제외한 console 객체를 허용하지 않음
    "no-console": ["error", { "allow": ["warn", "error", "info"] }]
    }
}

// .prettierrc
{
  // 줄 간격 100
  "printWidth": 100,
  // 작은따옴표 허용
  "singleQuote": true,
  // 화살표 함수에서 인자가 하나면 괄호 안함
  "arrowParens": "avoid"
}

간단하게 이러한 옵션을 설정한 후 테스트해 보자. 테스트할 파일 하나를 만든다.

// index.js
var hello = "world";

var end = (x) => {
  console.log("bye, world");
};

먼저 prettier를 실행한다.

# prettier
npx prettier --write .

# result
.eslintrc 142ms
.prettierrc 17ms
index.js 30ms
package-lock.json 241ms
package.json 61ms

정리된 파일은 다음과 같다.

var hello = "world";

var end = (x) => {
  console.log("bye, world");
};

설정에 따라 넓던 간격이 줄어들었고, 큰따옴표가 작은따옴표로 변경되었다. 또한 화살표 함수의 인자도 괄호가 벗겨졌다.

prettier가 정상적으로 실행되었으니 eslint를 확이해 보자.

# eslint
npx eslint .

# result
  1:1  error  Unexpected var, use let or const instead  no-var
  3:1  error  Unexpected var, use let or const instead  no-var
  4:3  error  Unexpected console statement              no-console

✖ 3 problems (3 errors, 0 warnings)

예상대로 varconsole을 지적하는 에러가 발생했다. 수정한다면 아무것도 출력하지 않고 종료한다.

파일이 몇 개 되지 않을 때는 상관없지만, 그 양이 많아진다면 매번 모든 파일을 검사하는 것은 비효율적이다. 파일이 변경되었을 때만 적용되도록 --cache 옵션을 사용한다.

# prettier
npx prettier --write --cache .

# result
.eslintrc 6ms (cached)
.lintstagedrc 3ms (cached)
.prettierrc 3ms (cached)
index.js 3ms (cached)
package-lock.json 3ms (cached)
package.json 3ms (cached)

# eslint
npx eslint --cache . # .eslintcache가 생성됨

3. 자동화

코드 작성을 끝낸 후 매번 CLI를 입력하기란 번거롭다. scripts에 명령어를 등록하면 손쉽게 실행할 수 있다.

// package.json
{
  "scripts": {
    "format": "prettier --write --cache .",
    "lint": "eslint --cache --max-warnings=0 ."
  }
}

등록한 명령어는 npm run formatnpm run lint로 실행한다. scripts 내에서는 npx 없이 모듈명으로 실행할 수 있다. 추가로, lint--max-warnings=0은 경고 표시조차 없어야 통과시킨다.

명령어가 단순해졌지만, 이 역시 commit 전, push 전에 입력하기란 불편하고 비효율적이다. 이것은 git hook을 이용해 자동화할 수 있다.

4. pre-commit과 pre-push

.git 폴더 안에는 git hook을 위한 파일이 존재한다.

# .git/hooks
$ dir -l
applypatch-msg.sample
commit-msg.sample
fsmonitor-watchman.sample
post-update.sample
pre-applypatch.sample
pre-commit.sample
pre-merge-commit.sample
prepare-commit-msg.sample
pre-push.sample
pre-rebase.sample
pre-receive.sample
push-to-checkout.sample
update.sample

직접 수정하여 사용할 수 있지만 과정이 복잡하다. 이를 위한 편의성 라이브러리가 husky이다. npm install -D husky로 설치한다.

husky에 hook을 등록하기 전에 husky install을 실행하여 초기화해야 한다. 그러면 .husky 디렉토리가 생기고, 그 내부에 hook이 추가된다. 여기서 주의할 점은 패키지에 명시되어 있다고 해서 다른 팀원이 npm install했을 때 자동으로 설치되지 않는다. 이런 점은 postinstall 스크립트로 해결할 수 있다.

// package.json
{
  "scripts": {
    "postinstall": "husky install"
  }
}

postinstall은 말그대로 npm install이 종료된 후 실행되는 명령어이다. npm install 해보면 모든 설치가 끝난 후 위 스크립트가 실행되는 것을 볼 수 있다.

husky를 설치했다면 pre-commitpre-push hook을 등록할 차례다. commit 전에는 prettier를 실행해 코드를 정리하고, push 전에는 eslint를 거쳐 에러를 잡아내도록 하자.

npx husky add .husky/pre-commit "npm run format"
npx husky add .husky/pre-push "npm run lint"

.husky 디렉토리 안에 pre-commitpre-push 파일이 생성된다.

테스트를 위해 commit을 해보자.

$ git add .
$ git commit -m "prettier test"

> eslint-prettier-husky@1.0.0 format
> prettier --write --cache .

.eslintrc 2ms (cached)
.prettierrc 2ms (cached)
index.js 48ms
package-lock.json 1ms (cached)
package.json 1ms (cached)
[master d1de691] prettier test

commit이 되기 전에 prettier가 실행되었다. push도 확인해 보자

$ git push origin master

> eslint-prettier-husky@1.0.0 lint
> eslint --cache --max-warnings=0 .


D:\work-space\index.js
  7:1  error  Unexpected console statement  no-console

✖ 1 problem (1 error, 0 warnings)

husky - pre-push hook exited with code 1 (error)
error: failed to push some refs to 'https://github.com/Real-Bird/linter-test.git'

eslint가 에러를 잡아내고 push하지 않았다. 에러 수정 후 다시 push하자.

$ git push origin master

> eslint-prettier-husky@1.0.0 lint
> eslint --cache --max-warnings=0 .

Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 619 bytes | 68.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/Real-Bird/linter-test.git
   39eb53b..7f8806d  master -> master

성공적으로 push되었다!

5. lint-staged

하지만 여기에는 함정이 있다. pre-commit하면서 formatter가 동작하지만, 수정된 파일이 스테이징되지 않는다. 이런 문제를 도와주는 라이브러리가 lint-staged이다. npm install -D lint-staged로 설치하고, config를 설정한다. 패키지에 "lint-staged"를 추가하거나, 파일을 만든다. 여기서는 파일을 만들어 작성했다.

// .lintstagedrc

{
  {
    "*.js": "npm run format"
  }
}

js 파일만 포맷팅을 거친다. 다양한 옵션들은 okonet/lint-staged에서 확인할 수 있다.

.husky/pre-commit 파일 내 npm run formatnpx lint-staged로 수정한다.

. "$(dirname -- "$0")/_/husky.sh"

# npm run format # remove
npx lint-staged

commit을 해 보면 포맷팅 후 스테이징까지 하는 모습이 보인다. 이것으로 협업을 위한 기본적인 자동화가 끝났다.


원티드 프리온보딩 인턴십 11차에 참여하여 배운 것을 복습하며 정리해 봤다. 강사님이 "자동화에 미친 개발자도 있다"라고 하셨는데, 이련 효율성 때문에 그런 사상(?)을 갖게 되는 것 같다. 혼자 개발하면서 익스텐션만 사용하니 별 생각 없었는데, 이번을 계기로 자동화에 대해서도 고려해 봐야겠다.

profile
프론트엔드 개발자가 되고 싶다
post-custom-banner

0개의 댓글