[React] ESLint

2023-01-25 hit count image

React에서 소스 코드를 분석하여 버그와 오류를 찾기 위해 ESLint를 사용하는 방법에 대해서 알아봅시다.

블로그 시리즈

이 블로그 포스트는 시리즈로 제작되었습니다. 다음은 블로그 시리즈 리스트입니다.

개요

ESLint란 ES(EcmaScript) + Lint(에러 코드 표식) 합성어로 Javascript 코드를 분석하고, 잠재적인 오류나 버그를 찾는데 도움을 주는 툴입니다.

이번 블로그 포스트에서는 create-react-app으로 생성한 React 프로젝트에 ESLint를 적용하는 방법에 대해서 살펴보도록 하겠습니다.

프로젝트 준비

React에서 ESLint를 사용하기 위해, create-react-app으로 간단하게 프로젝트를 생성하겠습니다. create-react-app에 관해 잘 모르시는 분들은 아래의 링크를 참고하시기 바랍니다.

그럼 다음 명령어를 사용하여 ESLint를 사용할 React 프로젝트를 생성합니다.

npx create-react-app eslint_example --template=typescript

저는 주로 TypeScript를 사용하여 React를 개발하므로 --template=typescript 옵션을 사용하여 React 프로젝트를 생성하였습니다.

ESLint 설치

React에서 ESLint를 사용하기 위해서는 ESLint 라이브러리를 설치할 필요가 있습니다. 다음 명령어를 사용하여 ESLint 라이브러리를 설치합니다.

# cd eslint_example
npm install eslint --save-dev

ESLint 설정

React에서 ESLint를 사용하기 위해서는 ESLint를 설정할 필요가 있습니다. 다음 명령어를 실행하여 ESLint를 설정합니다.

npx eslint --init

위에 명령어를 실행하면, 다음과 같은 질문을 확인할 수 있습니다.

? How would you like to use ESLint? …
  To check syntax only
❯ To check syntax and find problems
  To check syntax, find problems, and enforce code style

자신이 사용하고자 하는 옵션을 선택합니다. 저는 To check syntax and find problems을 선택하였습니다. 그럼 다음과 같은 질문을 확인할 수 있습니다.

? What type of modules does your project use? …
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

React는 기본적으로 import/export 방식을 사용하므로 JavaScript modules (import/export)을 선택하여 진행합니다. 그럼 다음과 같은 질문을 확인할 수 있습니다.

? Which framework does your project use? …
❯ React
  Vue.js
  None of these

우리는 ESLint를 React 프로젝트에서 사용할 예정이므로 React를 선택하여 진행합니다. 그럼 다음과 같은 질문을 확인할 수 있습니다.

? Does your project use TypeScript? › No / Yes

저는 React를 개발할 때, 주로 TypeScript를 사용하므로 Yes를 선택하여 진행하였습니다. TypeScript를 사용하지 않는다면 No를 선택하고 진행하시기 바랍니다. 그럼 다음과 같은 질문을 확인할 수 있습니다.

? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node

React는 Browser에서 코드가 실행되므로 Browser를 선택하여 진행합니다. 그럼 다음과 같은 질문을 확인할 수 있습니다.

? What format do you want your config file to be in? …
❯ JavaScript
  YAML
  JSON

지금까지 대답한 ESLint의 설정 내용을 저장할 파일 포맷을 선택하는 옵션입니다. 저는 JavaScript 포맷을 선호하므로, JavaScript를 선택하여 진행하였습니다.

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
? Would you like to install them now with npm? › No / Yes

마지막으로, 이렇게 설정한 내용을 바탕으로 필요한 라이브러리를 설치할지 물어봅니다. Yes를 선택하여 필요한 라이브러리를 설치합니다.

ESLint 룰

ESLint의 공식 사이트와 TypeScript ESLint 공식 페이지에서 설정 가능한 다른 룰(Rule)들을 확인할 수 있습니다.

  • ESLint 공식 사이트: Rules
  • ESLint Plugin TypeScript: Rules

해당 룰들을 확인하고, 자신의 프로젝트에 맞게 룰을 설정할 수 있습니다. 하단은 제가 사용하고 있는 룰 설정 내용입니다.

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:storybook/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
    project: './tsconfig.json',
  },
  plugins: ['react', '@typescript-eslint', 'functional', 'import'],
  settings: {
    react: {
      version: 'detect',
    },
  },
  rules: {
    // General
    'no-console': ['error', { allow: ['debug', 'warn', 'error'] }],
    // TypeScript
    '@typescript-eslint/consistent-type-imports': 'error',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-member-accessibility': 'off',
    '@typescript-eslint/indent': 'off',
    '@typescript-eslint/member-delimiter-style': 'off',
    '@typescript-eslint/no-confusing-void-expression': [
      'error',
      {
        ignoreArrowShorthand: true,
        ignoreVoidOperator: true,
      },
    ],
    'no-duplicate-imports': 'off',
    '@typescript-eslint/no-duplicate-imports': 'error',
    '@typescript-eslint/no-implicit-any-catch': 'error',
    'no-invalid-this': 'off',
    '@typescript-eslint/no-invalid-this': 'error',
    '@typescript-eslint/no-invalid-void-type': 'error',
    'no-loop-func': 'off',
    '@typescript-eslint/no-loop-func': 'error',
    'no-loss-of-precision': 'off',
    '@typescript-eslint/no-loss-of-precision': 'error',
    '@typescript-eslint/no-parameter-properties': 'off',
    'no-redeclare': 'off',
    '@typescript-eslint/no-redeclare': 'error',
    'no-shadow': 'off',
    '@typescript-eslint/no-shadow': 'error',
    'no-throw-literal': 'off',
    '@typescript-eslint/no-throw-literal': 'error',
    '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
    '@typescript-eslint/no-unnecessary-condition': 'error',
    '@typescript-eslint/no-unnecessary-type-arguments': 'error',
    'no-unused-expressions': 'off',
    '@typescript-eslint/no-unused-expressions': 'error',
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-use-before-define': [
      'error',
      {
        variables: false,
      },
    ],
    '@typescript-eslint/prefer-enum-initializers': 'error',
    '@typescript-eslint/prefer-for-of': 'error',
    '@typescript-eslint/prefer-includes': 'error',
    '@typescript-eslint/prefer-nullish-coalescing': 'error',
    '@typescript-eslint/prefer-optional-chain': 'error',
    '@typescript-eslint/prefer-reduce-type-parameter': 'error',
    '@typescript-eslint/prefer-string-starts-ends-with': 'error',
    '@typescript-eslint/prefer-ts-expect-error': 'error',
    '@typescript-eslint/promise-function-async': 'error',
    'no-return-await': 'off',
    '@typescript-eslint/return-await': 'error',
    '@typescript-eslint/strict-boolean-expressions': 'error',
    '@typescript-eslint/switch-exhaustiveness-check': 'error',
    // React
    'react/jsx-boolean-value': 'warn',
    'react/jsx-curly-brace-presence': 'warn',
    'react/jsx-fragments': 'warn',
    'react/jsx-no-useless-fragment': 'warn',
    'react/jsx-uses-react': 'off',
    'react/prefer-stateless-function': 'warn',
    'react/prop-types': 'off',
    'react/react-in-jsx-scope': 'off',
    // Functional
    'functional/prefer-readonly-type': [
      'error',
      {
        allowLocalMutation: true,
        allowMutableReturnType: true,
        ignoreClass: true,
      },
    ],
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal'],
        pathGroups: [
          {
            pattern: '{react,react-dom/**}',
            group: 'external',
            position: 'before',
          },
        ],
        pathGroupsExcludedImportTypes: ['react'],
        'newlines-between': 'always',
        alphabetize: {
          order: 'asc',
          caseInsensitive: true,
        },
      },
    ],
    'react/react-in-jsx-scope': 'off',
    'linebreak-style': ['error', 'unix'],
    eqeqeq: ['error', 'always', { null: 'ignore' }],
    camelcase: ['error', { properties: 'never' }],
    quotes: ['error', 'single', { avoidEscape: true }],
  },
};

위에 룰을 사용하려면 다음 명령어를 실행하여 eslint-plugin-functional을 설치할 필요가 있습니다.

npm install --save-dev eslint-plugin-functional

ESLint 체크

다음 명령어를 사용하면 우리가 정의한 ESLint의 룰을 지키고 있지 않은 파일과 내용을 확인할 수 있습니다.

npx eslint ./src

명령어를 실행하면 다음과 같이 정의된 룰을 지키고 있지 않는 파일과 내용을 확인할 수 있습니다.

/eslint_test/src/App.tsx
  5:1  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types

/eslint_test/src/reportWebVitals.ts
  1:1   error    All imports in the declaration are only used as types. Use `import type`  @typescript-eslint/consistent-type-imports
  3:25  warning  Missing return type on function                                           @typescript-eslint/explicit-module-boundary-types

✖ 3 problems (1 error, 2 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

ESLint로 코드 수정

다음 명령어를 사용하면 우리가 정의한 ESLint의 룰을 사용하여 코드를 수정합니다.

npx eslint --fix ./src

명령어를 실행하면 ESLint가 수정할 수 있는 파일은 수정하게 되고, 수정하지 못한 파일들은 다음과 같이 표시합니다.

/eslint_test/src/App.tsx
  5:1  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types

/eslint_test/src/reportWebVitals.ts
  3:25  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types

수정하지 못한 파일들은 파일을 열어 직접 수정해 줍니다.

// code ./src/App.tsx
function App(): JSX.Element {
// code ./src/reportWebVitals.tsx
const reportWebVitals = (onPerfEntry?: ReportHandler): void => {

이렇게 파일 수정한 후, 다시 다음 명령어를 실행하여 코드를 체크해 보면, 이전과는 다르게 아무 경고가 표시되지 않는 것을 확인할 수 있습니다.

npx eslint ./src

이로써 우리는 미리 정의한 ESLint 룰들을 잘 지키고 있음을 확인할 수 있습니다.

저는 ESLint가 코드를 자동으로 수정하는 --fix 옵션 사용을 선호하지 않습니다. 직접 코드를 보면서 이유를 알아가는 것도 중요하기 때문이고, 가끔 ESLint가 잘못 수정하는 경우도 있기 때문입니다.

package.json 설정

앞에서 살펴본 check 명령어와 fix 명령어를 점 더 쉽게 사용하기 위해 package.json 파일을 열고 다음과 같이 수정합니다.

"scripts": {
  ...
  "lint": "eslint ./src",
  "lint:fix": "eslint --fix ./src"
},

이렇게 package.json 파일을 수정하였다면, 이제 다음 명령어를 사용하여 ESLint를 사용할 수 있습니다

npm run lint
npm run lint:fix

완료

이것으로 React 프로젝트에서 ESLint를 설정하는 방법에 대해서 알아보았습니다. 지금부터 ESLint를 사용하여 잠재적인 오류와 버그들을 수정해 보시기 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통
Posts