dkmqflx's blogGithub

프론트엔드 개발 환경 자동화: 코드 컨벤션 지키기 (Husky, Commitlint, Lint, Prettier, Stylelint)

2023.12.10

🚀 배경

협업을 원활하게 하기 위해서는 일관된 코드 스타일과 규칙이 필요하다. 커밋 메시지 작성 방식, 브랜치 전략 등 다양한 규칙을 정하지만, 결국 개발은 사람이 수행하는 작업이기 때문에 실수로 규칙을 어기는 경우가 많다. 문서화만으로는 이러한 실수를 방지하기 어려우므로, 자동으로 규칙을 강제하는 환경을 구축하면 더욱 효율적인 개발이 가능하다.

이 글에서는 Git Hooks, Husky, Commitlint, ESLint, Prettier, Stylelint 등을 활용하여 문서 없이도 코드 컨벤션을 강제할 수 있는 환경을 구축하는 방법을 정리한다.


🔹 Git Hooks

필요한 도구들을 도입하기에 앞서 우선 Git Hooks에 대한 이해가 필요하다.

Git Hooks은 특정 Git 이벤트가 발생할 때 자동으로 실행되는 스크립트이다. 예를 들어:

$ cd .git/hooks
$ ls -a
 
# 아래와 같이 다양한 hook이 있는 것을 확인할 수 있다.
applypatch-msg.sample     post-update.sample        pre-merge-commit.sample   pre-receive.sample        update.sample
commit-msg.sample         pre-applypatch.sample     pre-push.sample           prepare-commit-msg.sample
fsmonitor-watchman.sample pre-commit.sample         pre-rebase.sample         push-to-checkout.sample
 

위 명령어를 실행하면 여러 개의 Hook 파일을 확인할 수 있다.

그리고 .sample 확장자를 제거하면 해당 Hook을 활성화할 수 있다.

예를 들어 pre-commit Hook은 커밋 전에 실행되는 스크립트이며, 아래처럼 설정하면 커밋할 때마다 "pre-commit!"이 출력된다.

# .git/hooks/pre-commit
 
echo "pre-commit!"

하지만 Git Hooks는 .git 폴더 내부에 위치하기 때문에 원격 저장소에 공유할 수 없고, 모든 팀원이 직접 설정해야 하는 번거로움이 있다. 이러한 문제를 해결하는 도구가 바로 Husky이다.


🔹 Husky

Husky는 Git Hooks를 쉽게 관리할 수 있도록 도와주는 라이브러리이다. 설치하면 .husky 폴더가 생성되며, 여기에 Git Hooks 스크립트를 작성하면 된다.

$ npx husky-init && npm install

예를 들어, pre-commit Hook을 추가하면 Git Hooks를 사용할 때와 동일하게 동작하지만, Git Hooks을 사용할 때와 달리 원격 저장소에 올릴 수 있기 때문에 이제 모든 팀원이 동일한 Hook을 사용할 수 있도록 공유 가능하다.

# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
 
echo "husky pre-commit!"

이제 팀원들이 자동으로 동일한 Git Hooks 환경을 갖출 수 있다.


🔹 Git Flow

Husky를 활용하면 잘못된 브랜치에 직접 push하는 실수를 방지할 수도 있다. 예를 들어 develop, stage, master 브랜치에는 직접 push하지 못하도록 설정할 수 있다.

pre-push hook은 말 그대로 git push 명령어를 입력했을 때 실행되는 Hook으로 커밋된 파일을 원격에 올리기 전에 실행된다.

# .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
 
branch="$(git rev-parse --abbrev-ref HEAD)"
 
if [ "$branch" = "master" ] || [ "$branch" = "stage" ] || [ "$branch" = "develop" ]; then
  echo "$branch 브랜치에 직접 push 할 수 없습니다."
  exit 1
fi

이제 master 브랜치에 직접 push를 하면 다음과 같은 메시지가 출력되면서 push가 차단된다.

$ git push origin master
# master 브랜치에 직접 push 할 수 없습니다.

🔹 Commitlint: 커밋 메시지 컨벤션 강제

커밋의 목적을 명확하게 나타내고 협업을 원활하게 하기 위해 커밋 컨벤션을 정한다. type으로 허용되는 키워드, subject의 사용 유무와 같은 여러 컨벤션을 정하지만 실제로 커밋 메시지를 보면 컨벤션을 지키지 않는 경우가 많다.

이 때 Commitlint라는 도구를 도입하면 커밋 명령어를 작성할 때 컨벤션을 강제하기 때문에 커밋 컨벤션이 지켜지지 않는 상황을 사전에 막을 수 있다. 예를 들어, feat, fix, refactor 등 지정한 키워드만 사용할 수 있도록 강제할 수 있다.

우선 공식문서에 따라 Commitlint를 설치한 다음, 설정 파일을 생성하는 명령어를 실행한다.

$ npm install --save-dev @commitlint/{cli,config-conventional}

명령어를 실행하고 나면 commitlint.config.cjs 파일이 생성되는데 해당 파일에서 필요한 설정을 추가해줄 수 있다.

예를들어 “커밋의 type으로는 feat, fix, chore, refactor, test만 사용될 수 있다” 라는 컨벤션이 있다면 아래와 같이 rule을 추가해준다.

// commitlint.config.js
module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      ["feat", "fix", "style", "chore", "refactor", "test"],
    ],
  },
};

여기서 각각의 rule은 배열로 Level, Applicable, Value라는 세가지 값을 전달받는데 각각의 역할은 다음과 같다.

Husky와 연동하여, 올바르지 않은 커밋 메시지가 입력되면 커밋이 차단되도록 설정할 수 있다.

rule을 추가 한 다음 huksy 폴더 내부에 commit-msg 파일을 생성하고 아래처럼 명령어를 작성한다.

commit-msg hook은 커밋 하기에 앞서 실행되는 Hook으로, 이전에 rule로 정해준 규칙에 위배된 커밋 메시지를 작성하게 되면 커밋이 되지 않는다.

# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
 
echo "commit 컨벤션을 체크합니다."
npx commitlint --edit $1

이제 잘못된 커밋 메시지를 입력하면 경고 메시지가 출력되면서 커밋이 차단된다.


🔹 ESLint & lint-staged: 코드 품질과 스타일 유지

ESLint는 ECMAScript 코드에서 문법 오류를 검사하고, 일관된 코드 스타일을 유지할 수 있도록 도와주는 정적 분석 도구이다. 단순한 코드 스타일 검사뿐만 아니라, 버그가 발생할 가능성이 있는 코드를 감지하는 기능도 제공한다.

ESLint 설정

ESLint를 프로젝트에 적용하려면 먼저 라이브러리를 설치해야 한다.

$ npm install --save-dev eslint

ESLint를 설치 후 설정 파일 내부에서 다양한 설정을 해줄수 있다. 많은 키워드들이 있지만 extends, plugins, rules에 대해서 우선 이해를 하는게 중요한데, 각각의 키워드는 다음과 같은 역할을 한다.

아래와 같이 설정 파일에 필요한 설정을 추가할 수 있다.

// .eslintrc.js
module.exports = {
  extends: [
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "airbnb",
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  plugins: ["@typescript-eslint", "react"],
  rules: {
    "react/jsx-filename-extension": 0,
    "react/react-in-jsx-scope": 0,
  },
};

lint-staged와의 연동

프로젝트 규모가 커질수록 전체 코드베이스를 대상으로 ESLint를 실행하는 것은 비효율적이다.

이러한 문제를 lint-staged를 사용해서 해결할 수 있는데 lint-staged는 stage 상태의 파일에만 lint가 적용될 수 있도록 도와주는 라이브러리로, pre-commit hook과 함께 사용하면 커밋할 때 lint 규칙이 적용되도록 할 수 있다.

lint-staged를 활용하면 Git에 staged된 파일만 대상으로 검사를 실행하여 성능을 최적화할 수 있다.

$ npm install --save-dev lint-staged

설치 후 package.json에 스크립트와 lint가 적용될 파일의 확장자를 아래처럼 명시한다.

// package.json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
  }
}

추가적으로 pre-commit hook에 위의 스크립트를 실행하는 코드를 추가한다.

# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
 
echo "commit 이전에 lint 규칙을 적용합니다"
npx lint-staged

이제 커밋을 하게 되면 staged 된 파일들을 대상으로만 lint가 적용되기 때문에 모든 파일을 대상으로 lint를 검사 했을 때 보다 더 빠르게 커밋을 완료할 수 있다.

지금까지는 husky를 사용해서 특정한 시점에 동작을 처리해서 컨벤션을 지킬 수 있는 방법에 대해 알아보았다면, 아래는 이번에는 husky를 사용하지는 않지만 컨벤션을 지킬 수 있는 방법들이다.


🔹 Prettier

Prettier는 일관된 코드 스타일을 유지하게 해주는 코드 포맷터이다다.

앞서 설명한 ESLint도 일관된 코드 스타일을 유지할 수 있도록 도와주는 코드 포맷팅 역할을 하지만, Prettier가 코드 포맷팅에 더 특화되어 있는 만큼 보통 ESLint를 Prettier와 함께 사용한다.

Prettier는 주로 코드 스타일의 일관성을 유지하고 ESLint는 코드 품질과 잠재적인 버그를 검사하는 데 사용된다.

$ npm install --save-dev --save-exact prettier

설정 파일을 추가하여 원하는 포맷팅 규칙을 정의할 수 있다.

// .prettierrc
{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 120
}

파일을 저장할 때 실시간으로 포맷팅을 하기 위해 Prettier 익스텐션을 설치한 다음 VS Code 설정 파일에 아래와 같이 Prettier를 기본 포맷터로 하고 저장할 때 마다 실행되도록 하는 설정을 추가한다.

// .vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true
}

ESLint와 Prettier를 함께 사용할 때 일부 규칙이 충돌할 수 있으므로 eslint-config-prettier 라이브러리를 설치하여 이를 방지할 수 있다.

$ npm install --save-dev eslint-config-prettier
// .eslintrc.js
{
  "extends": ["prettier"]
  // eslint-config-*와 같은 형식을 갖는 패키지는 eslint-config 부분은 생략할 수 있다.
}

이제 ESLint와 Prettier가 충돌 없이 함께 작동하도록 설정되었다.


🔹 Stylelint

아래는 스타일 관련된 컨벤션이 없는 상황에서 styled-components를 사용해서 스타일을 적용한 코드이다.

Box라는 컴포넌트를 스타일링 할 때 누군가는 width를 먼저 작성할 수 있지만 또 사람은 height를 먼저 작성할 수 있다.

// styled-components
 
const Box = styled.div`
  width: 200px;
  height: 200px;
 
  padding-left: 20px;
  padding-right: 20px;
  font-size: 12px;
  line-height: 20px;
  border-top: 1px solid black;
`;
 
const Box = styled.div`
  height: 200px;
  width: 200px;
 
  padding-left: 20px;
  padding-right: 20px;
  font-size: 12px;
  line-height: 20px;
  border-top: 1px solid black;
`;

이렇게 작성하는 방식이 다양하다면 코드의 양이 늘어날수록 가독성의 문제는 더욱 심각해질 것이다.

스타일 코드의 수가 늘어나게 되면 개행을 사용해서 구분을 지을 수 있겠지만 개행을 하는 기준 또한 사람마다 달라 근본적인 해결책이 되지는 못한다.

이 때 사용할 수 있는게 Stylelint로 Stylelint는 CSS, SCSS 및 CSS-in-JS 코드의 스타일 가이드 준수를 강제하는 도구이다.

CSS 속성 순서, 중복된 스타일 제거, 미사용 코드 검출 등을 자동화할 수 있다.

Stylelint 설정

Stylelint를 설치하고 필요한 플러그인을 추가한다.

$ npm install --save-dev  stylelint
                          stylelint-config-standard
                          stylelint-config-recess-order
                          postcss-styled-syntax

설정 파일을 추가하여 속성 정렬 및 스타일 규칙을 정의한다.

// .stylelintrc.js
const propertyGroups = require("stylelint-config-recess-order/groups");
 
module.exports = {
  customSyntax: "postcss-styled-syntax",
  extends: ["stylelint-config-standard", "stylelint-config-recess-order"],
  plugins: ["stylelint-order"],
  rules: {
    "order/properties-order": propertyGroups.map((group) => ({
      ...group,
      emptyLineBefore: "always",
      noEmptyLineBetween: true,
    })),
    "declaration-empty-line-before": null,
  },
};

여기서 중요한 것은 order/properties-order 으로 해당 속성에 지정한 값을 통해 속성을 그룹별로 나눠서 정렬할 수 있다.

그리고 Stylelint 익스텐션을 설치한 다음 VS Code의 설정 파일에 아래와 같은 옵션을 추가해서 자동으로 규칙이 적용되도록 한다.

// .vscode/settings.json
{
  "stylelint.enable": true,
  "stylelint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}

이제 Stylelint가 적용되어 스타일 코드도 일관된 컨벤션을 유지할 수 있다.

이렇게 설정을 하고나면 위에서 작성한 코드와 동일한 코드를 작성하더라도 rule에서 설정한 규칙에 따라 순서대로 정렬되는 것 뿐 아니라 각각의 그룹 별로 나누어져 있는 것을 확인할 수 있다.

export const Box = styled.div`
  width: 200px;
  height: 200px;
 
  padding-left: 20px;
  padding-right: 20px;
 
  font-size: 12px;
  line-height: 20px;
 
  border-top: 1px solid black;
`;

🔹 마무리

ESLint, lint-staged, Prettier, Stylelint는 코드 스타일 유지 및 품질 향상을 위한 유용한 도구이다. 이러한 자동화된 도구를 활용하면 코드 스타일을 강제하고, 개발자의 실수를 방지하며, 협업의 효율성을 높일 수 있다.

이를 도입하면 문서화에 의존하지 않고도 코드 컨벤션을 준수할 수 있으며, 팀 전체의 생산성을 높일 수 있다.