코드 리뷰 할 때마다 보이는 지켜지지 않은 컨벤션… 심지어 타입 오류로 빌드도 안되는 상태… 이를 해결하기 위한 husky
와 lint-staged
도입 계기와 설정 방법을 소개합니다.
개요
익히 husky
와 lint-staged
사용은 프로젝트의 코드 품질과 일관성을 유지하기 위해 사용되는 글이 많습니다.
물론 그것을 중점으로 도입되는 것은 맞지만 다른 관점으로 코드 리뷰어로써 도입하게 된 배경을 소개해 보려고 합니다.
도입 배경
당시 저는 TS
+ Next.js
+ MUI
를 이용한 프로모션 사이트
의 메인 코드 리뷰어
Role으로 활동 중이었는데 처음에는 큰 문제가 없었지만 점차 문제가 발생하기 시작했습니다.
나만 열심히 사용하는 Prettier
, ESLint
친절히 개인이 사용하는 코드 Editer (like. IDE
) 에 설정을 하고 쓰면 좋겠지만 그렇지 않은 경우가 꽤 있었습니다.
사실 prettier
까지는 포멧팅
정도이기에 그냥 넘어갈 수 있지만 (눈물을 흘리며 내가 npx prettier --write .
실행하기…)
ESLint
의 검사를 하지 않는 것은 문제가 발생했습니다.
개발을 위해 MUI
를 사용하고 있는데 import { Button } from '@mui/material';
와 같이 최상위에서 가져오면 아직 사용하지 않을 Component
까지 번들링되어서 전체적인 DX
경험과 Build
시간이 길어지는 문제가 발생했습니다. (가뜩이나 MUI
느린데 말이죠!)
이를 위해서는 MUI
에서 가이드 하는 방법 중 하나로 import Button from '@mui/material/Button';
와 같이 use path imports
를 해야 했습니다.
개발하다 보면 나도 모르는 사이에 최상위에서 import
를 하고 있을 수 있고 이는 전체적인 DX
와 build
퍼포먼스 저하로 이뤄지는데 시스템적으로 개선이 필요했습니다.
Type Error!
TS
+ Next.js
을 사용했는데 npm run dev
으로 수행된 development
상태에서는 프로젝트의 특정 파일 중 타입 에러가 발생하더라도 내가 수정하고 있는 페이지에 대해서 문제가 없다면 화면에 잘 표시되었기에 마치 프로젝트가 문제가 없는 것처럼 보이는 문제가 있었습니다.
note
이것은 예시로 보고 있는 페이지에서도 타입 에러가 발생하더라도 npm run dev
에서는 정상적으로 보이지만 npm run build
시 type error
이 발생하는 것을 볼 수 있습니다.
주로 리펙토링 혹은 타입을 확장하면서 많이 발생했는데 이런 경우 build
를 하면 Type error
으로 프로젝트가 망가지기 십상이었습니다.
애써 PR
이 올라와서 열심히 코드를 보고 있는데 수행이 되면 안 되는 코드들이 보이고, 그런 것을 놓치고 승인을 눌렀더니 build
에 실패해서 다시 코드가 Push
되고 리뷰어로써는 괴로운 순간이었습니다.
정적 검사를 도입하자
코드 리뷰
를 위해 PR
(like. 코드 리뷰) 가 올라올 때마다 코드를 리뷰하는 것이 아닌 숨어있는 문제와 숨바꼭질
을 늘 하게 되니 리뷰의 피로도는 높았습니다.
이러하여 리뷰어
로써의 불편함도 개선하면서 프로젝트의 코드 품질과 일관성을 지키기 위해서 중간에 정적 검사
를 추가하기로 하였습니다.
개발 프로세스
중 어느 이벤트에 추가하느냐는 여러 방법이 있겠지만 프로세스
상 가장 검증이 필요한 git push
단계에 넣는 것이 좋다고 생각했고 이와 관련된 Node.js
계열의 라이브러리
로 husky
, lint-staged
를 사용하게 되었습니다.
husky와 lint-staged는?
왜 두 개나 사용해야 하나 싶은데 차이점은 husky
는 git hook
을 이용해서 git
의 특정 단계에 대해서 원하는 스크립트
를 수행할 수 있도록 해주고 lint-staged
는 모든 파일에 대해서 정적 검사
를 하면 느리기에 git staging area
만 검사할 수 있도록 해주는 라이브러리입니다.
husky 는?
git push
단계에서 특정 스크립트
를 실행시켜서 작업하고 싶은 기능을 제공하는 것은 git hook
입니다.
근데 git hook
이 .git
안으로 들어가는 형태라서 열심히 hook
을 정의해도 .git
디렉터리는 버전 관리 대상
이 아니므로 공유가 어렵습니다.
그래서 husky
를 사용하여 손쉽게 공유 및 관리를 달성하는 것입니다.
husky
도 까보면 .git/hooks
에 있는 스크립트를 .husky
으로 돌리는 것이라고 합니다.
lint-staged 는?
모든 파일에 대해서 정적 검사
를 해도 좋은 경우와 그렇지 않은 경우가 있을 것입니다.
예를 들어 prettier
는 전체 프로젝트에 대해서 한번 진행이 되었다면 수정되는 파일에 대해서만 진행하는 것이 경제적입니다.
이제 이럴 때 lint-staged
을 이용해서 commit
하려고 하는 (수정이 된 파일) 에 대해서만 추가 작업을 진행할 수 있는 패키지입니다.
헷갈리면 안 될 것이 lint-staged
는 스테이징 영역에 있는 파일 리스트에 대해서 추가 작업을 정의할 수 있도록 해주는 도구
입니다.
스테이징 영역
으로 들어갔을 때 hook
으로 실행해 주는 것이 아니므로 왜 스테이징
으로 들어갔는데 실행 안되지? 라고 햇갈리지 않도록 합니다.
원리 및 적용 방법
시간에 지남에 따라 적용 방법은 바뀔 수 있기에 공식 문서
를 보는 것을 추천드립니다.
적용 방법은 심플하게 설명하고 적용하면서 알게 된 원리와 주의점에 대해서 설명을 해보겠습니다.
husky Automatic 방법으로 설치
공식 문서에서 권장하는 방법인 Automatic 을 실행합니다.
그러면 아래와 같이 설치가 될 텐데
prepare
를 이용해서 프로젝트 설치 전husky
내용을 설치하도록 하는 것을 볼 수 있습니다.package.json{
"scripts": {
"prepare": "husky install",
"format": "prettier --write",
"lint": "eslint --fix",
"type-check": "tsc --pretty --noEmit",
"lint:full-inspection": "next lint --fix"
},
"devDependencies": {
"husky": "^8.0.0"
}
}또한, 샘플로
pre-commit
가 생기는 것을 볼 수 있습니다.pre-commit
이외도 다른git hook
을 이용한 파일을 생성해서hook
을 정의할 수 있습니다..husky/pre-commit#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test이외
_
내부에 있는husky.sh
를 보면 git hook과 관련된 것은 모두husky
에서 사용할 수 있도록Overwrite
하고 있는 것을 볼 수 있습니다.
pre-commit
hook에 lint-staged
지정
아까 생성된 pre-commit
파일 하단에 스크립트
를 작성하면 commit 을 실행하기 전에 실행
때 원하는 작업을 Local
에서 수행할 수 있습니다.
여기서는 commit
시 git staging area
에 올라온 파일에 대해서만 특별한 작성을 하기 위해서 lint-staged
를 사용하기 때문에 아래와 같이 명시했습니다.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# `.lintstagedrc.js` 규칙에 맞게 검사
npx lint-staged
lint-staged 설치 및 지정
lint-staged
를 설치합니다.npm install lint-staged --save-dev
.lintstagedrc.js
을 생성하여 어떤 파일을 어떻게 체크할 것인지 정의합니다..lintstagedrc.jsmodule.exports = {
// script 실행만을 위함이므로 기본 패키지 매니저인 npm으로 하는 것이 깔끔합니다
"*": "npm run format",
"*.@(js|jsx|ts|tsx)": "npm run lint",
};
수동으로 lint-staged
를 수행하는 방법은 npx lint-staged
가 있습니다.
위에서 pre-commit
에 npx lint-staged
를 지정했기 때문에 commit
시 npx lint-staged
가 실행되어 스테이징
에만 있던 파일은 .lintstagedrc.js
에 정의된 대로 작업이 수행될 것입니다.
pre-push
hook으로 type
검사 진행
왜, type
검사는 pre-push
에서 하는지 의문일 수 있습니다.
pre-commit
에서 하기에는 매번 commit
마다 확인하기에는 부담스러운 작업이고, lint-staged
에서 진행하기에는 스테이징
에만 올라온 파일만 검사하기 때문에 문제가 발생합니다.
lint-staged
에서 진행하면 아래의 문제가 발생하는데
- 예시로
a.ts
는b.ts
의 파일을import
합니다. - 여기서
a.ts
만 수정되어스테이징
에 올라가면 어떻게 될까요? - 타입 검사는 에러가 발생합니다. 이유는
a.ts
만 검사를 하기 때문에b.ts
가 없다고 말이죠
때문에 Push
으로 Remote Server
으로 보내기 전 pre-push
으로 검사하는 것이 깔끔합니다.
기호에 맞게 수정하면 되는데 아래와 같이 구성할 수 있습니다.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 타입 검사의 경우 모듈 의존성 때문에 모든 파일을 대상으로 진행되어야 합니다. 때문에 `lint-staged` 으로 타입 검사 시 스테이징에 올라온 파일만 검사하다 에러가 발생하므로 push 이전에 확인하도록 합니다.
npm run type-check
# next에서 검사하는 lint도 `pages` 디렉터리가 있냐 없냐와 같은 전체 파일을 대상으로 검사하기 때문에 `lint-staged` 으로 검사가 어려워 push 이전에 확인하도록 합니다.
npm run lint:full-inspection
Insight
lint-staged
에서 Prettier
이나 ESLint
실행 시 .
으로 전체 파일에 대한 경로를 지정하면 의미가 없습니다.
lint-staged
를 사용하면서 가장 중요한 부분이라고 생각되는데 ESLint
를 예로 든다면 eslint --fix .
시 .eslintignore
를 제외한 파일에 대해 모두 검사한다는 것 입니다.
우리가 lint-staged
를 사용해서 얻는 이점은 스테이징
에 올라온 파일만 검사하는 것인데 위와 같이 아예 .
을 옵션으로 넘겨주는 순간 의미가 없어집니다.
고로 아래와 같이 정의하면 js|jsx|ts|tsx
에 해당하는 파일만 옵션
으로 전달되어 검사하게 되어 스테이징
에 있는 파일만 지정하여 검사할 수 있는 것입니다.
module.exports = {
"*.@(js|jsx|ts|tsx)": "eslint --fix",
};
Windows
에서 husky
생성한 경우 Linux
에서 동작하지 않는 문제
프로젝트
에서 타입 에러
가 발생하고 있는데 Push
가 된 것을 보고 Windows
에서 잘 되던 husky
가 WSL
에서 동작하지 않음을 감지했습니다.
아무 파일이나 수정하고 coomit
하니까 아래와 같은 메시지가 출력되었는데 이는 Push
도 마찬가지로 git hook
이 동작하지 않는 것을 볼 수 있었습니다.
hint: The '.husky/pre-push' hook was ignored because it's not set as executable.
hint: You can disable this warning with `git config advice.ignoredHook false`.
저와 같은 고민을 하는 이슈 ‣ 를 보고 Stackoverflow
를 통해서 아래의 명령어를 통해 권한을 줘서 문제를 해결했습니다.
chmod ug+x .husky/*
husky
init을 Windows
에서 해서 Linux
에서는 파일 실행 권한
이 없어서 발생한 문제이었는데 나름 여러 환경
에서 개발하다 만난 이슈
라서 신기했습니다.
마무리
이렇게 husky
와 lint-staged
를 왜 도입했는지, 어떻게 설정하는지를 알아보았습니다.
코드의 규모가 커질수록 일관적
인 코드 품질은 유지보수
성 및 가독성
을 향상시킬 수 있습니다.
해당 페이지에서 소개한 도구를 사용해서 더 좋은 DX
으로 즐거운 개발을 해나가보세요!
읽어주셔서 감사합니다.