The journey to becoming a developer

My future is created by what I do today, not tomorrow.

Projects/Pullanner

[Pullanner] 프론트엔드 개발 환경 세팅 과정 Part 1. Formatting & Code Quality

Millie 2023. 4. 30. 00:28

Introduction

풀래너의 프론트엔드 팀은 개발 환경을 세팅할 때 많은 공을 들였습니다. 👏

89일 간 13번의 심도 있는 회의를 통해서, 현재는 개발 환경 세팅이 완료된 상태입니다.

이번 포스팅에서는 Formatting과 Code Quality와 관련된 세팅을 어떻게 했는지에 대해서 자세히 파헤쳐 보려고 합니다.


Code Formatter와 Linter를 도입한 이유

  • 저희 프론트엔드 팀은 코드를 일관성 있게 작성하고, 잠재적인 에러를 미리 발견해서 코드의 퀄리티를 높이면서도 효율성 있게 작업하고 싶었습니다.
  • 그래서 ESLint, Prettier는 필수라고 생각을 했고, 이제 이것들을 어떻게 우리 팀에 맞게 커스터마이징하여 사용할 것인가를 고민했어요.
  • 코드를 본격적으로 작성하기 전에 충분히 고민을 한다면, 앞으로 코드를 작성할 때 확실히 스타일에 대한 고민을 줄일 수 있고 로직 작성에 더 집중해서 속도를 높일 수 있을 거라 생각했어요.

Linting은 ESLint에게, Code Formatting은 Prettier에게 맡기자!

  • ESLint에도 코드 포맷팅 기능이 있지만, Prettier의 자동 코드 포맷팅 기능이 더 강력해요.
  • 저희 팀은 eslint-config-prettier 플러그인을 설치해서, eslint에서 formatting 관련한 rule들을 모두 해제했어요.

ESLint와 Prettier가 자동으로 적용될 수 있게 하자!

  • Git Hook을 좀 더 편리하게 사용할 수 있는 도구인 Husky와 Lint-staged를 활용해서, 커밋을 할 때 ESLint와 Prettier가 적용된 후에 커밋이 수행될 수 있도록 설정했어요.

ESLint 설정

  • ESLint에 사용한 extends와 rules를 정리해 보았어요.
  • extends는 무슨 역할을 하는지, rules는 왜 적용했는지 하나하나 알아보고 정리했답니다. 

사용한 extends 목록

Module Name eslintrc.cjs Description Examples
eslint-plugin-jsx-a11y plugin:jsx-a11y/recommended JSX를 정적으로 평가(static evaluation)하여 애플리케이션의 접근성 문제를 찾아주는 플러그인 alt-text 룰을 통해 img 태그 등에 alternative text를 명시하지 않을 시 에러를 발생시켜, 사용자가 요소를 볼 수 없는 상황일 경우 대체할 정보를 표시할 수 있도록 한다.
no-noninteractive-element-interactions룰을 통해 interactive element가 아닌 div 태그 등에 mouse나 keyboard event handler를 할당하지 못하도록 에러를 발생시킨다.
eslint-plugin-import eslint-import-resolver-typescript plugin:import/typescript eslint-import-resolver-plugin은 eslint-plugin-import에 TypeScript 지원을 추가한다. •  .cts/.mts/.ts/.tsx/.d.cts/.d.mts/.d.ts 확장자를 가진 파일을 import/require 할 수 있다.
•  tsconfig.json에 정의된 paths를 사용할 수 있다.
typescript-eslint @typescript-eslint/parser plugin:@typescript-eslint/recommended ESLint, Pretter가 TypeScript를 지원할 수 있도록 하는 도구 no-inferrable-types: number, string, boolean 등으로 초기화 된 변수나 매개 변수에 명시적으로 유형을 선언하지 않도록 강제하는 규칙. TypeScript는 이러한 경우 타입을 유추할 수 있다. 명시적으로 type annotation을 사용하면 코드가 불필요하게 장황하게 되어 가독성이 떨어진다. 또한 일반적인 기본 유형(e.g. number)보다 구체적인 literal type(e.g. number)을 추론하지 못하게 될 수 있다.
eslint-plugin-import plugin:import/recommended 적절하게 import를 할 수 있도록 유효성을 검사하는 규칙을 가지고 있는 ESLint 플러그인. file path나 스펠링이 틀린 것도 잡아낼 수 있음 no-extraneous-dependencies : 외부 패키지 사용을 금지한다.
eslint eslint:recommended ESLint 팀에서 best practice로 간주하는 규칙이 포함된 built-in configuration • no-const-assign: const variable에 재할당을 금지한다.
eslint-plugin-react plugin:react/recommended React에 특화된 ESLint 규칙 button-has-type: 명시적으로 type attribute를 쓰지 않고 button element를 사용하는 것을 금지한다.
eslint-plugin-react plugin:react/jsx-runtime React 17의 새로운 JSX transform을 사용하는 경우, 이 플러그인을 extends에 명시하여 관련 규칙을 비활성화해야 함 react-in-jsx-scope: JSX를 사용할 때 React를 꼭 import 해와야 하는 규칙. React 17 버전 이상을 사용하는 경우 이 규칙을 off하여 해제한다.
eslint-plugin-react-hooks plugin:react-hooks/recommended React Hook의 2가지 규칙을 적용하는 ESLint plugin (Reference: Rules of Hooks) • Hook은 Top Level에서만 호출 가능: loops, conditions, nested functions 내부에서 Hook을 호출하면 안 된다는 규칙. 이 규칙을 통해 컴포넌트가 렌더링 될 때마다 Hook이 동일한 순서로 호출되도록 할 수 있다. 또한 React는 여러 번의 useState와 useEffect 호출 사이에 Hook의 상태를 올바르게 보존할 수 있다. • React function component 및 custom Hook에서만 Hook 호출이 가능하다.
eslint-config-prettier plugin:prettier/recommended Prettier와의 충돌을 방지하기 위해 code formatting과 관련된 ESLint rule을 비활성화한다.  
eslint-config-airbnb airbnb eslint-config-airbnb는 Airbnb의 .eslintrc를 확장이 가능한 공유된 config로서 제공하는 패키지 • 익명 함수를 사용해야 하는 경우(inline callback 등) 함수 선언문이 아닌 화살표 함수 표현식을 사용하도록 강제한다. (eslint: prefer-arrow-callback, arrow-spacing) 화살표 함수는 해당 컨텍스트의 this에서 실행하는 버전의 함수를 만들기 때문에 보통 원하는 대로 작동한다. 또한 문법이 간결해진다.
eslint-plugin-storybook plugin:storybook/recommended Storybook을 Best practice로 사용하기 위한 공식적인 ESLint plugin storybook/default-exports: Story file에는 default export가 있어야 한다.

 

사용한 rules 목록

Rules Description Reason
no-warning-comments 설정에 명시된 특정한 단어를 포함하고 있는 주석에 대해 보고하는 규칙 커밋하기 전에 ‘TODO’, ‘FIXME’, ‘XXX’, ‘BUG’ 주석에 있을 경우 보고 받아서 주석을 제거할 수 있게 이 규칙을 적용했습니다.
import/order require나 import 문의 순서 컨벤션을 정하고 그것을 강제하는 규칙 import 문의 순서 컨벤션을 사용하면 코드의 가독성이 좋아지므로 이 규칙을 적용했습니다.
react/function-component-definition 함수 컴포넌트를 사용할 때 특정한 함수 타입을 적용하도록 도와주는 규칙 컨벤션에서 함수를 arrow function을 사용해서 정의하기로 결정했기 때문에, 함수 컴포넌트 정의시 arrow function을 사용하도록 이 규칙을 적용했습니다.
no-console console의 사용을 금지하는 규칙 console.log를 사용하여 에러를 확인하는 것은 git commit 전 확인하는데 유용하므로, console을 사용하기로 결정해서 이 규칙을 비활성화 했습니다.
react/jsx-filename-extension JSX 문법 포함하는 파일이 .jsx 확장자를 사용하도록 권장하는 규칙 jsx 확장자를 통해 파일에서 JSX 문법을 사용하는지 한번에 알아볼 수 있으므로 이 규칙을 적용했습니다.
import/prefer-default-export export하는 파일 내에서 default export를 사용하도록 권장하는 규칙 default export를 사용하면 intellisense가 잘 작동하지 않아서 불편하기 때문에, default export를 사용하지 않기로 결정하여 이 규칙을 비활성화 했습니다.
no-plusplus 단항 연산자 ++, —를 허용하지 않는 규칙 단항 연산자는 자동 semi-colon(;) 삽입 대상이어서 공백의 차이로 인해 소스 코드의 의미 체계가 달라질 수 있으므로, 단항 연산자를 사용하지 않도록 이 규칙을 적용했습니다. 그러나 for 문에서의 단항 연산자는 semi-colon 없이 사용하므로, allowForLoopAfterthoughts 옵션을 통해 단항 연산자를 허용했습니다.
react/require-default-props 컴포넌트의 props가 required 타입이 아닐 때 대응되는 defaultProps값을 설정하도록 강제하는 규칙 프로젝트에서 TypeScript를 사용하기 때문에, defaultProps 값이 필요 없으므로 이 규칙을 비활성화 했습니다.
react/prop-types 컴포넌트의 props에 대한 타입인 propTypes을 설정하도록 강제하는 규칙 프로젝트에서 TypeScript를 사용하기 때문에, propTypes 값이 필요 없으므로 이 규칙을 비활성화 했습니다.
consistent-return return 문을 사용하도록 강제하는 규칙 return 문이 필요 없는 경우도 존재하므로 이 규칙을 비활성화 했습니다.
curly 모든 block 문에 대해 일관적인 괄호({}) 스타일을 강제하는 규칙 모든 block 문을 괄호로 감싸는 일관적인 스타일을 적용하여 코드의 가독성을 높이기 위해 이 규칙을 적용했습니다.
eqeqeq strick equality operator(===, ≠=)를 사용하도록 강제하는 규칙 타입 변환을 일으키는 eqaulity operator(==, ≠) 대신에 stict equality operator(===, ≠=)를 사용하는 것이 안전하기 때문에 이 규칙을 적용했습니다. smart 옵션을 사용하여 literal 값을 비교할 때, typeof 연산자의 반환값을 평가할 때, null과 비교할 때의 3가지 경우에 이 규칙이 적용되지 않도록 했습니다.
@typescript-eslint/naming-convention 코드 전반에 네이밍 컨벤션을 강제하는 규칙 기존에 논의한 네이밍 컨벤션을 강제함으로써 코드의 가독성이 좋아지므로 이 규칙을 적용했습니다. 사용하지 않는 변수나 매개변수를 정의할 때 underscore(_)로 시작하는 이름을 붙이기 때문에, 변수와 매개변수에 leadingUnderscore 옵션을 사용했습니다.
@typescript-eslint/array-type 배열 타입을 일관적으로 정의하도록 강제하는 규칙 모든 배열 타입을 T[]의 형태로 정의함으로써 코드의 가독성이 좋아지므로 이 규칙을 적용했습니다.
no-unused-vars 변수가 사용되지 않는 것을 허용하지 않는 규칙 사용되지 않는 변수는 코드에서 자리를 차지하고 가독성을 헤치므로, 이러한 경우를 방지하기 위해 이 규칙을 적용했습니다. 사용하지 않는 인수나 구조 분해 할당 시 사용하지 않는 변수를 underscore(_)로 정의하기 때문에, argsIgnorePattern: ‘^_’ 과 destructredArrayIgnoerPattern : ‘^_' 옵션을 사용했습니다.
react/react-in-jsx-scope JSX를 사용할 때 scope 내에 React가 있는지 확인하는 규칙 React 17 버전 이후부터 새로운 JSX transfom에서 자동적으로 react/jsx-runtime 함수를 불러오기 때문에, 이 규칙을 비활성화 했습니다.
arrow-body-style arrow function의 내부를 괄호로 감싸도록 강제하는 규칙 arrow function의 내부를 항상 괄호로 감싸는 일관적인 스타일을 적용하여 코드의 가독성을 높이기 위해 이 규칙을 적용했습니다.
import/extensions source path를 import할 때 파일 확장자의 사용 여부를 결정하는 규칙 typescript resolver가 import 경로에서 파일 확장자의 생략을 허용하므로, import 경로에 파일 확장자를 사용하지 않도록 이 규칙을 적용했습니다. 추후에 이미지 파일 import 시 이미지 파일임을 구분하기 위해 특정 파일 확장자를 사용할 예정입니다.