ESLint와 Prettier (+husky, lint-staged)

이희제·2024년 6월 30일
post-thumbnail

프로젝트를 진행하면서 eslint와 prettier를 사용해서 코드 퀄리티를 보장하고 일관성 있는 코드를 작성하고 있다.

이번에 새로 프로젝트를 진행하면서 다시 적용을 하는데 적용을 하는 김에 eslint, prettier에 관련된 내용을 기록하고자 한다.

우선 airbnb에서 정의된 eslint 룰을 기준으로 적용하고 불필요한 룰을 커스텀하여 사용하기로 합의가 된 상태라 동일하게 적용한다.

먼저 airbnb 관련 룰을 프로젝트에 적용하기 위해서 airbnb의 eslint config를 설치한다. pnpm을 사용하고 있기 때문에 다음과 같이 설치할 수 있다.

pnpm add -D eslint-config-airbnb

추가로 typescript를 사용하고 있기 때문에 airbnb-typescript의 eslint config를 설치한다.

pnpm add -D eslint-config-airbnb-typescript

그리고 eslint-config 설정을 바로 프로젝트에 사용할 수 있도록 .eslintrc.json 내 extends 항목 내에 추가를 해준다.

"extends": [
    "airbnb",
    "airbnb/hooks"
    "airbnb-typescript"
  ],

리액트 훅 관련된 린트 설정을 활성화해주기 위해서 위와 같이 airbnb/hooks를 추가해줬다. (참고)

지금까지 구글링을 통해서 단순히 뭘 설치하고 어떻게 적용해야 하는지만 파악을 했던거 같다. 그러다 보니 설정값에 대해 확실히 알고 가는게 좋을 것 같다는 생각이 들었다.

나의 궁금증에 따라서 내용을 서술하겠다.

먼저 eslint-config-airbnb 이런식으로 airbnb 관련 eslint 설정을 설치하고 extends에 넣었는데 왜 여기에 넣어야하고 단순히 airbnb 라고만 추가를 해주는 것일까?

eslint-cofig-*eslint-plugin-*

우선, eslint에서의 cofig와 plugin에 대해 알고 구별해서 이해가 필요하다.

plugin은 eslint 룰을 정의한 것이고 config는 이런 룰들을 모아둔 하나의 설정파일이다.

그래서 설치할 때 eslint-plugin-, eslint-config- 접두사가 붙는다.

  • eslint-plugin-* 는 특정 패키지에 대한 룰을 정의 (참고)
  • eslint-config-* 는 룰들을 모아서 설정으로 만든 것 (룰들이 조합되어있음)

예시를 통해 살펴보자.

만약 eslint 설정 파일의 plugins 항목 내에 다음과 같이 되어 있으면 jquery 관련된 룰을 가지고 오겠다는 것이다.

{
    // ...
    "plugins": [
        "jquery", // eslint-plugin-jquery이 설정된 것이다.
    ]
    // ...
}

즉, 추가적인 규칙(rule)을 사용할 수 있게 해준다. 추가적인 룰들은 rules 항목 내에 정의할 수 있다.

주의할 점은 플러그인만 추가해주면 관련 규칙이 바로 활성화되지 않는다는 것이다. 플러그인은 새로운 규칙을 단순히 설정이 가능한 상태로 만들어주기만 한다.

앞에서 airbnb의 config를 등록한 예시를 다시 보자.

"extends": [
    "airbnb", // eslint-config-airbnb가 설정이 된 것이다.
  ],

위 설정의 뜻은 airbnb에서 미리 정의된(pre-defined) 룰이나 다른 설정을 가져와서 그대로 내 프로젝트에 사용하겠다는 것이다. (참고)

참고로 eslint-config-airbnb 내에는 js 관련 eslint 설정이 있는 eslint-config-airbnb-base가 포함되어 있다. (참고)

eslint 설정 파일 내 extendsplugins 항목에 대해

위에서 기본적인 설명이 되었으니 해당 설정 항목에 대해서는 이해하기 쉬울 것이다.

  • plugin - 추가 규칙(rule)을 가지고 올 수 있는 곳
    • 기본으로 제공되는 규칙(rule) 외에도 추가적인 규칙(rule)을 사용할 수 있도록 만들어주는 다양한 플러그인(plugin)
    • 설치하고 추가만 한다고 적용되지 않고 rules에 정의를 해주고나 extends 옵션을 통해 룰을 추가 설정해줘야 한다.
    • ex) eslint-plugin-react - 리액트 관련된 룰 정의
  • extends
    • 보통 eslint-config-* 가 접두사로 들어간 설정 파일을 설치해서 extends 내 설정한다.
    • 설정 파일의 extends 옵션을 통해서 기업/개인들이 공개해놓은 미리 정의된 설정을 그대로 가져와 기반(base) 설정으로 활용
    • 플러그인의 추천 설정도 여기 내에서 설정하여 사용할 수 있다.
      • ex) "plugin:react/recommended" - react 룰에 대해 추천 설정을 export하기 때문에 가져올 수 있다. (참고)

내가 프로젝트에 설치한 configplugins를 보면서 이해도를 높여보자.

  • eslint-config-airbnb :airbnb 의 리액트 관련 규칙을 적용하기 위한 eslint 설정이다.
    • 내부 설정을 살펴보면 기본적으로 자바스크립트용 eslint-config-airbn-base가 추가된다.
  • eslint-config-airbnb-typescript : 타입스크립트 지원으로 airbnb의 eslint config를 강화한다.
    타입을 사용하는 것이기 때문에 아래와 같이 설정을 해줘야 한다.
{
  extends: ['airbnb', 'airbnb-typescript'],
+ parserOptions: {
+   project: './tsconfig.json'
+ }
}
  • eslint-config-prettier: prettier와 중복, 충돌되는 ESLint 룰을 비활성화한다. ESLint에도 포맷팅을 하는 기능이 있는데 이것이 prettier와 충돌하여 에러를 발생시킬 수 있으므로 해당 패키지를 통해 이런 오류를 사전에 방지하는 것이다.
    충돌만 방지하도록 설정하고 lint-staged가 동작할 때 prettier 포맷팅을 따로 하도록 해줬다. (참고)

참고 - eslint-plugin-prettier는 eslint가 동작할 때 prettier의 포맷팅 기능을 사용할 수 있게 해주는 plugin이다.

eslint 8.53.0 버전부터 eslint에서 포맷팅 룰이 Deprecated 처리되었고 최소한 ESLint 10.0.0 버전까지는 제거되지 않는다고 한다.
https://velog.io/@typo/deprecation-of-formatting-rules


추가로 lint-staged를 활용해서 커밋 시에 자동으로 prettier를 통해 포맷팅을 하고 lint 검사를 하도록 적용해뒀는데(lint 검사에 실패나면 커밋이 되지 않는다.) 발생한 이슈 사항에 대해 기록하고자 한다.

lint-staged 사용 시 특이 사항

lint 검사가 실패했을 때 prettier의 포맷팅도 원래대로 되돌아 가는 현상이 있었다. 원래 기대했던 바로는 preiiter --write로 먼저 스타일 포맷팅일 이뤄지고 lint 검사가 진행되기 때문에 스타일 포맷팅은 정상적으로 적용될 것이라 생각했다. 근데 eslint 검사가 실패나는 순간 스타일 포맷팅이 이전으로 돌아가는 것을 발견했다.

나의 lint-staged 내에는 다음과 같이 되어 있다.

"lint-staged": {
  "./src/**/*.{js,jsx,ts,tsx}": [
    "prettier --write",
    "eslint --max-warnings=0 --cache --fix" //warning도 lint 검사 시에 에러로 판단하기 위해 `--max-warnings=0` 옵션을 추가했다.
  ]
}

터미널 내에서 에러가 발생하면 기존 상태도 되돌리는 문구를 발견했다. 이를 통해 lint-staged 패키지와 관련되어 있다는 것을 알게 되었다.

lint-staged의 공식 문서 내 --no-stash라는 옵션을 확인했다.

  • lint-staged는 기본적으로 백업 stash를 생성한다.
  • 에러 발생 시에 모든 수정 사항을 revert 처리한다. 즉 백업해둔 stach 상태로 돌아가는 것이다.

→ --no-stash 라는 옵션을 사용할 경우 백업 stash를 생성하지 않고 수정된 그 상태를 그대로 유지한다.

그치만 eslint 체크에 실패한 파일에 대해서 포맷팅 적용을 하지 않는 것이 코드를 수정할 때 용이하나고 판단하여 --no-stash 옵션을 사용하지 않는 것으로 합의를 했다.


추가 eslint 이슈

프로젝트 진행하면서 상대 경로보다는 절대 경로를 사용하는 것이 가독성이 좋아 사용하고자 했다.

개발을 진행하면서 path alias 설정이 더 추가될 수도 있겠지만 현재는 기본으로 "@"를 "./src"로 설정되어 있다. (프로젝트 환경 구성 단계이다.)

import 시에 확장자 명시 필수 오류가 발생하는 것을 발견했다.

ESLint: Missing file extension for "@/app/components/getData"(import/extensions)

.tsx, .ts 로 구성된 파일에 대해 확장자를 사용하지 않을 것이기 때문에 우선 airbnb의 config가 어떻게 설정되어 있는지 보았다.

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/imports.js#L139

위 링크를 가보면 아래와 같이 확인할 수 있다.

.tsx, .ts 확장자에 대한 룰은 따로 없기 때문에 확장자를 사용하면 에러가 발생하도록 다음과 같이 룰을 추가해줬다. (컴포넌트와 일반 함수 성격의 ts와 헷갈리수도 있겠다, ts는 확장자를 붙이는 방향으로?)

→ import/extensions를 통해 ts, tsx를 import할 때 확장자를 입력하지 않아도 되도록 설정 (참고)

  • never : 확장자 사용을 금지한다. 확장자가 붙어 있다면 에러.
  • always : 모든 구문에 확장자를 사용해야 한다. 확장자가 붙어있지 않다면 에러.
  • ignorePackages라이브러리 패키지 구문을 제외하고 확장자를 사용해야 한다. (node_modules에서 import하는 경우 확장자를 검사하지 않는다)

→ 추가로 https://github.com/import-js/eslint-import-resolver-typescript 를 설치하고 설정을 아래와 같이 해줘야 한다.

  • eslint 에서는 tsconfig에 설정된 path alias 경로를 인식하지 못하기 때문에 필요하다.
  • 해당 모듈 문서에 가보면 tsconfig.json 내에 설정된 paths를 쓸 수 있다고 한다. (eslint에서)
  • eslint-config-next에 기본적으로 포함되어 있는 모듈임.
"settings":{
    "import/resolver":{
      "typescript":{
        "alwaysTryTypes": true
      }
    }
  },

이번 경험을 통해 익숙하게 사용하던 eslint와 prettier에 대해 더 자세히 알 수 있었다. 추후에는 팀 내에서 공통으로 사용할 수 있는 eslint 룰을 직접 정의하여 사용하는 것이 좋지 않을까 싶다.

profile
그냥 하자

0개의 댓글