새로운 블로그로 이전하였습니다!

React 프로젝트를 개발할 때 각종 편리했던 기능들에 대해 정리해보려 합니다.

그 중 코드를 깔끔하게 정리해주는 EsLint와 Prettier에 대해 작성해보겠습니다.

​정의

ESLint

Javascript, JSX의 코드를 분석하여 문법 오류나 안티 패턴을 찾아주거나 일괄된 코드 스타일로 작성하도록 코드 포맷을 만들 수 있는 라이브러리 입니다. 그 중 외부에 오픈되어 있는 코딩 규칙도 사용이 가능한데 유명한 Airbnb, Google 등의 Style을 제공합니다.

아래부턴 VScode를 기반으로 설명합니다.

Prettier

Prettier는 코드를 읽고 다시 작성하여 코드 스타일을 일관성 있게 맞춰주는 도구입니다. ESLint와 함께 사용할 경우 코드 스타일 관련된 ESLint 규칙을 비활성화하고 Prettier에 맡기는 방식으로 사용합니다.

Linters vs Formatters

Linters는 코드를 분석하여 문제점을 찾아내는 것이 주 역할이고, Formatters는 코드를 정리하는 것이 주 역할입니다.

Linters로 코드 작성 규칙 정리 과정은 코드 분석 및 문제점을 찾아내는 과정으로 인해 Formatters 대비 느린 속도를 보입니다. 그래서 코드작성규칙은 Prettier로 작성하길 권장하는 글을 종종 볼 수 있습니다.

하지만 Prettier는 코드를 읽고 다시 작성하기에 그 과정에서 소스 코드에서 모든 스타일 정보를 버려버립니다. 그 과정에서 개발자가 원하는 스타일을 보존할 수 없게 만듭니다.

가장 큰 예시로 printWidth 옵션을 들 수 있습니다. 아래 예시를 통해 오히려 시각적으로 더 읽기 어려운 코드가 되는 것을 확인할 수 있습니다.

// printWidth

 

ESLint의 포매팅 규칙 deprecate

2023. 12. 16 추가

ESLint v8.53.0부터 포매팅 규칙을 deprecate 처리하였고, 이후 v10부터 완전히 제거할 계획을 발표했습니다.

주된 이유로는 언어적 발전에 따른 구문들의 변화와 프레임워크(주로 React)의 사용이 폭발적으로 증가함에 따라 스타일 규칙을 동결해 관리하고자 했지만, 더 이상 변화들과 사용자 요구를 따라잡기 어려워졌기 때문이라고 합니다.

대신 코드 형식을 관리하고자 한다면 코드 포메터인 Prettier를 사용할 것을 권장합니다.

https://eslint.org/blog/2023/10/deprecating-formatting-rules/

구성

IDE에 ESLint 설치

VScode의 확장탭에서 ESLint 를 설치해줍니다.

 

 

 

프로젝트에 ESLint devDependencies 추가

아래 eslint 플러그인 목록을 추천합니다.

  • eslint : ESLint 핵심 패키지
  • eslint-import-resolver-typescript : Typescript 프로젝트에서 모듈 및 파일경로를 해석하는 역할
  • eslint-plugin-import : import문 관련된 규칙 ( import 순서 등 )
  • eslint-plugin-jsx-a11y : 웹 접근성 규칙 검사
  • eslint-plguin-react : JSX 및 React 컴포넌트 관련 규칙
  • eslint-plugin-react-hooks : React Hooks 사용 관련 규칙
  • @typescript-eslint/parser : Typescript AST와 ESLint AST 구조가 달라서 호환을 위해 사용하는 Plugin
  • @typescript-eslint/eslint-plugin : Typescript 관련 규칙
npm

npm install --save-dev eslint eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin

yarn

yarn add --dev eslint eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin


pnpm

pnpm add -D eslint eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin

 

 

eslint-typescript 의 코드 작성 규칙은 곧 ESLint stylistic 으로 흡수되어 통합될 예정이라고 합니다. 아래 설명내용이 추 후 원할하게 동작하지 않을 수 있습니다. ( 2023-10-27 )

https://eslint.style/packages/default

 

ESLint Stylistic

Stylistic Formatting for ESLint

eslint.style

방법1. 스크립트를 이용한 ESLint 설정

eslint 의 기본 설정을 사용할 수 있습니다.

수동으로 설정할 경우 아래 .eslintrc 설정을 참고해주세요.

npx eslint --init

패키지가 설치되면 여러가지 질문 사항이 나오게 됩니다.

√ How would you like to use ESLint? · problems
√ What type of modules does your project use? · esm // javascript
√ Which framework does your project use? · react
√ Does your project use TypeScript? · Yes
√ Where does your code run? · browser
√ What format do you want your config file to be in? · JSON
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
√ Would you like to install them now? · Yes
√ Which package manager do you want to use? · pnpm

 

설정이 완료되면 프로젝트의 최상위 폴더에 .eslintrc파일이 추가됩니다.
.eslintrc 파일을 아래와 같이 수정해줍니다.

( extends 의 우선순위는 맨 아래에 있을수록 우선순위가 높으므로 순서를 지켜서 추가해야 합니다. )

{
    "env": {
        "es6": true,
        "browser": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/eslint-recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json",
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": ["react", "@typescript-eslint"],
    "rules": {},
    "ignorePatterns": ["dist/", "node_modules/"]
}

 

방법2. .eslintrc 작성을 이용한 설정

rules(코드 작성 규칙) 관련 문서

ESLint : https://eslint.org/docs/latest/rules/

React : https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules

Typescript : https://typescript-eslint.io/rules/

import : https://github.com/import-js/eslint-plugin-import/tree/main

 

{
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    // devDependencies에 설치한 plugin 추가
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/eslint-recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:import/recommended",
        "plugin:import/typescript",
        "plugin:react/recommended",
        "plugin:react-hooks/recommended",
        "plugin:jsx-a11y/recommended"
    ],
    "plugins": [ "import", "react", "jsx-a11y" ],
    // typescript, react 설정
    "parser": "@typescript-eslint/parser",
    "settings": {
        "import/parsers": {
            "@typescript-eslint/parser": [".ts", ".tsx"]
        },
        "import/resolver": {
            "typescript": {
                "alwaysTryTypes": true,
                "project": [
                    "packages/*/tsconfig.json",
                    "tsconfig.json"
                ]
            }
        },
    "react": {
            "pragma": "React",
            "version": "detect",
            "fragment": "Fragment"
        }
    },
    // 코드 작성 구조
    "rules": {
        "react/jsx-equals-spacing": [2, "never"],
        "react/jsx-curly-spacing": 2,
        "react/react-in-jsx-scope": 0,
        "react/jsx-max-props-per-line": [2, { "maximum": 1 }],
        "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
        "indent": "off",
        "eqeqeq": ["warn", "smart"],
        "block-spacing": ["error", "always"],
        "semi": ["error", "always", { "omitLastInOneLineBlock": true }],
        "semi-spacing": "error",
        "eol-last": ["error", "never"],
        "object-curly-spacing": ["error", "always"],
        // 공백 관련
        "space-infix-ops": ["warn"],
        "space-in-parens": ["error", "always"],
        "space-before-blocks": ["error", "always"],
        "space-before-function-paren": ["error", "never"],
        "spaced-comment": [ "error", "always", { "markers": [ "/" ] } ],
        // const 선호 사용 (값 변경 없는 경우)
        "prefer-const": "warn",
        "no-multi-spaces": [ "error", { "ignoreEOLComments": true, "exceptions": { "VariableDeclarator": true } } ],
        "no-case-declarations": 1,
        "no-lonely-if": "warn",
        "no-trailing-spaces": [ "error", { "ignoreComments": true } ],
        "no-undef-init": "warn",
        "no-unused-vars": "off",
        "no-use-before-define": "off",
        "no-extra-semi": "error",
        "no-useless-concat": "warn",
        // styling for comma
        "comma-dangle": ["error", "always-multiline"],
        "comma-spacing": ["warn", { "after": true }],
        "comma-style": ["warn", "last"],
        // function 관련
        "func-call-spacing": ["error", "never"],
        "function-call-argument-newline": "off",
        "function-paren-newline": ["error", "consistent"],
        // 기타
        "key-spacing": ["warn", {
            "afterColon": true,
            "beforeColon": false
        }],
        "linebreak-style": "off",
        "no-multiple-empty-lines": ["error", { "max": 1 }],
        // "arrow-body-style": ["error", "as-needed"],
        "arrow-parens": ["warn", "always"],
        "arrow-spacing": ["error", { "before": true, "after": true }],
        // typescript
        "@typescript-eslint/type-annotation-spacing": 2,
        "@typescript-eslint/indent": [ "error", 4, {
            "MemberExpression": 1,
            "SwitchCase": 1,
            "VariableDeclarator": "first",
            "ImportDeclaration": "first",
            "ObjectExpression": "first",
            "ArrayExpression": "first",
            "CallExpression": { "arguments": "first" },
            "FunctionDeclaration": { "parameters": "first" }
        } ],
        "@typescript-eslint/adjacent-overload-signatures": "off",
        "@typescript-eslint/no-unused-vars": ["warn", { "varsIgnorePattern": "_.*", "argsIgnorePattern": "_.*", "args": "none" }],
        "@typescript-eslint/member-delimiter-style": ["error", {
            "multiline": {
                "delimiter": "semi",
                "requireLast": true
            },
            "singleline": {
                "delimiter": "comma",
                "requireLast": false
            }
        }],
        "@typescript-eslint/no-empty-interface": [
            "error",
            { "allowSingleExtends": true }
        ],
         // import
        "import/default": "off",
        "import/no-named-as-default-member": "off",
        "import/order": [
            "error",
            {
                "groups": [
                    "builtin",
                    "external",
                    [
                        "parent",
                        "sibling"
                    ],
                    "index"
                ],
                "pathGroups": [
                    {
                        "pattern": "react",
                        "group": "external",
                        "position": "before"
                    }
                ],
                "pathGroupsExcludedImportTypes": [
                    "react"
                ],
                "alphabetize": {
                    "order": "asc",
                    "caseInsensitive": true
                },
                "newlines-between": "always"
            }
        ]
    }
}

 

.eslintignore

ESLint 규칙 제외 대상을 지정할 수 있습니다.

Project의 최상위에 .eslintignore 파일을 생성하거나 .eslintrc 의 "ignorePatterns" 에 추가하여 설정할 수 있습니다.

node_modules/
types/
dist/

*.umd.js
*.min.js

 

 

복사했습니다!