여러사람과 협업하며 개발을 하다보면, 단순한 import 문도 사람마다 다르게 쓰는 경우를 많이 보게 됩니다.

 

별 내용의 차이는 없어도 위와 같이 Import 문의 순서만 달라져도 git diff로 인식되는 것을 확인 할 수 있습니다.

git diff 뿐만 아니라, 아무런 규칙 없이 개발자 제각각 마구잡이로 Import문을 작성하다보면 코드 품질과 가독성은 당연히 떨어질테고, 결국은 효율적인 개발 업무에 방해가 되겠죠.

 

이를 해결하기 위해, ESLint를 사용할 수 있습니다. ESLint로 import의 순서(order) 규칙을 정하고 제한할 수 있는 것이죠.

물론 ESLint에서 제공하는 규칙에 플러스로 더 많은 기능을 제공하는 플러그인을 사용해야합니다.

이를 위해 어떠한 설정을 해야하는지 알아보겠습니다 🙂

 

이 글의 코드 예시는 Vite + TypeScript + React 환경에서 작성하였습니다.

 

 

우선 import 순서의 규칙을 정하고 적용해주는 Eslint plugin들이 있는데, 보통은 아래 3개의 플러그인이 대표적으로 사용되는 것 같습니다. (이는 ESLint에서 공식적으로 제공하는 플러그인이 아닌 커뮤니티 플러그인들입니다.)

저는 npm기준 다운로드 수가 가장 많은 ‘eslint-plugin-import’를 사용해서 설정해 보겠습니다.

 

 

1. 플러그인 설치

우선, 프로젝트에 eslint-plugin-import를 설치해줍니다. 아래와 같이 터미널에 입력합니다.

npm i -D eslint-plugin-import

그러면 package.json의 “devDependencies”에 eslint-plugin-import가 추가 된 것을 확인 할 수 있습니다.

참고로 Vite로 프로젝트로 생성을 할 때 TypeScript 옵션을 골랐다면 devDependencies에 **@typescript-eslint/parser**가 설치되어 있습니다. 없을 경우에는 똑같이 npm i -D 로 설치해주세요.

 

ESLint는 타입스크립트 코드를 분석하지 못하기 때문에 따로 AST(Abstract Syntax Tree)를 생성해 주어 정적 분석을 하도록 해야 하는데, ESLint를 위해 AST를 생성하는 역할을 하는 것이 @typescript-eslint/parser입니다.

타입스크립트를 환경에서 eslint-plugin-import 를 사용하기 위해서 또 하나 설치해 주어야 할 것이 있습니다.

 

타입스크립트 모듈 경로를 해석하기 위한 리졸버(resolver)로 ESLint가 타입스크립트에서 Import하는 모듈의 경로를 해석하고 찾을 수 있게 도와주는 라이브러리입니다. 아래와 같이 입력하여 설치합니다.

npm i -D eslint-import-resolver-typescript

이제 필요한 라이브러리의 설치는 끝났습니다!

 

 

2. 기본 설정

본격적으로 import order 규칙을 적용하기 전에, 저는 모듈 alias(별칭)을 설정해 주도록 하겠습니다.

상대 경로를 사용하면 ‘../../src/page/…’ 이런 식으로 길어지면 가독성이 떨어지고 작성하기 번거롭기 때문에, 별칭을 사용하여 절대경로를 사용하는 방법이 많이 쓰이고 있습니다.

타입스크립트를 사용한다면 tsconfig.json에서 별칭을 설정할 수 있습니다.

Vite로 프로젝트를 생성했을 경우, npm run 했을 때, 개발 서버에서 별칭이 인식될 수 있도록 vite.config.ts에서도 설정을 해주어야 합니다.

/* vite.config.ts */

import { defineConfig } from 'vite';

export default defineConfig({
  resolve: {
    alias: {
      "@": "/src",
    },
  },
});
/* tsconfig.json */

{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"]
    }
  },
}

 

그 다음으로 .eslintrc.cjs 를 수정해 봅시다. Vite로 프로젝트를 생성했다면 이미 root디렉토리에 .eslintrc.cjs 파일이 생성되어 있는걸 확인할 수 있습니다. 없다면 아래 명령어를 터미널에 입력하여 파일을 생성해줍니다.

npm i -D eslint 

#eslint 초기 설정 옵션들을 묻는 질문에 대한 답을 선택 후 .eslintrc.cjs를 생성합니다
npx eslint --init

 

우선 Import 규칙 설정을 제외한 나머지 설정들을 아래와 같이 .eslintrc.cjs 에서 수정 또는 추가합니다.

{
  "plugins": ["import"], //eslint-plugin-import를 사용하기 위해 명시합니다
	"extends": ["plugin:import/recommended"], //eslint-plugin-import에서 권유하는 설정들을 사용합니다.
  "settings": {
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true, //import하는 모듈의 @type/**에서 .d.ts파일을 찾아 타입 추론을 합니다.
        "project": "./tsconfig.js", // tsconfig.json 파일의 위치를 명시합니다. 생략 시, 현재 위치에서 가장 가까운 tsconfig.json이 인식됩니다.
       
				// 아래는 MonoRepo와 같이 하나의 프로젝트의 다수의 tsconfig.json이 있을 때의 예시입니다.
				// 아래와 같이 Array로 tsconfig.json 파일의 경로를 나타낼 수 있습니다.
        "project": [
          "packages/module-a/tsconfig.json",
          "packages/module-b/tsconfig.json"
        ],
      }
    }
  }
	"parser": "@typescript-eslint/parser",
	"ignorePatterns": ["dist", ".eslintrc.cjs", "vite.config.js"] //eslint 규칙 적용을 무시할 파일들의 이름 패턴을 작성합니다. "src/constants/**/*.ts"] 식으로 작성할 수도 있습니다.
}
  • 권유설정(recommended)는 eslint-plugin-import 깃헙 페이지에서 확인할 수 있습니다.
    https://github.com/import-js/eslint-plugin-import
  • “alwaysTryTypes”를 true로 설정하면 항상 모듈의 타입 선언 파일을 찾아 타입체크를 합니다. 보통 대부분의 외부 모듈들은 @types 라는 별칭으로 모듈에 대한 타입이 선언된 보조 라이브러리를 제공합니다.
    이는 node_modules 에서 확인할 수 있습니다. 만약 이 보조라이브러리가 없다면 사용자가 직접 타입 선언 파일을 생성해 주어야 합니다. 이 때, root/@types 디렉토리 안에, import한 모듈에 대한 .d.ts 파일을 생성해주면 이 파일을 찾아 모듈에 대한 타입체크를 실행하게 되는 것입니다.

 

 

3. 규칙 설정

이게 본격적으로 Import 규칙을 설정해 봅시다. 계속 .eslintrc.cjs에서 작성해 나갑니다.

 

Groups

그룹별로 import하는 모듈의 순서를 지정합니다. 이 그룹들은 eslint-plugin-import에서 지정해놓은 string값이며 배열의 원소로 작성되어야 합니다. 그룹 옵션은 아래와 같으며, 배열로 묶을 수도 있습니다. 작성한 순서대로 import 순서가 설정됩니다. 작성되지 않은 모듈은 작성된 모듈들의 다음 순서로 설정됩니다.

  • builtin - node.js내의 모듈
  • external - 외부 패키지 및 라이브러리
  • internal - 프로젝트 내부의 모듈
  • parent - 부모(상위) 디렉토리의 모듈
  • sibling - 동일한 디렉토리의 모듈
  • index - 현재 디렉토리의 index 파일
  • object - console.log와 같이 내장 객체 import(typescript에서만 사용 가능)
  • type - type 파일 (typescript에서만 사용 가능)
  • unknown - 그 외에 인식 할 수 없는 타입의 모듈

저는 아래와 같이 작성했습니다.

"import/order": [
      "error",
      {
        groups: [
          "builtin", 
          "external",
          "internal",
          ["sibling", "parent", "index"],
          "type",
          "unknown",
        ],
]

모듈들을 그룹별로 위의 순서대로 나열하되, parent, sibling, index 디렉토리에 해당되는 파일들은 함께 묶어 순서를 정했습니다.

 

PathGroups

위 그룹내에서도 더 세분화하여 순서를 정하고 싶다면 pathGroups 속성에 규칙을 정의해줍니다.

작성해주어야 할 속성은 아래와 같습니다.

  • pattern - import할 모듈 경로의 패턴
  • group - 적용될 그룹
  • position - 순서 위치. after 또는 before로 입력

저는 크게 external과 internal 그룹에서 더 세분화된 규칙을 설정했습니다.

external은 React/React이름이 들어간 라이브러리 ⇒ axios 라이브러리 ⇒ log 라이브러리

internal은 hooks안의 파일 ⇒ components 경로 안의 파일 ⇒ page경로 안의 파일

기본적으로 position으로 위치를 정하는데, 같은 position값이라면 우선 작성한 순서대로 규칙이 적용됩니다.

아래와 같이 입력합니다.

"import/order": [
      "error",
      {
        groups: [
          "builtin",
          "external",
          "internal",
          ["sibling", "parent", "index"],
          "type",
          "unknown",
        ],
        pathGroups: [
          {
            pattern: "react",
            group: "external",
            position: "before",
          },
          {
            pattern: "react*",
            group: "external",
            position: "before",
          },
          {
            pattern: "axios*",
            group: "external",
            position: "before",
          }
          {
            pattern: "logLevel*",
            group: "external",
            position: "after",
          },
          {
            pattern: "@/hooks/*",
            group: "internal",
            position: "before",
          },
          {
            pattern: "@/components/*",
            group: "internal",
            position: "before",
          },
          {
            pattern: "@/pages/*",
            group: "internal",
            position: "before",
          },
     
        ],
        "newlines-between": "always",
        alphabetize: {
        	order: "asc",
        	caseInsensitive: true,
        },
   ]

PathGroups 설정 외의 작성해준 설정은 다음과 같습니다.

  • newlines-between : 각기 다른 그룹의 Import문 사이에 줄바꿈이 들어갑니다.
  • alphabetize: 그룹 내에서의 순서를 알파벳 순으로 정렬합니다. order이 asc이면 오름차순, desc이면 내림차 순이고, caseInsensitive는 대소문자 구분여부로 false일 경우 구분, true이면 구분하지 않음입니다.

이렇게 해서 import문 순서 규칙 작성이 완료되었습니다. 🥳 위의 규칙이 적용된 import문 코드 예시를 보여드리겠습니다.

import { useEffect, useState, useRef } from "react";

import { useNavigate } from "react-router-dom";

import { ko } from "date-fns/esm/locale";

import DatePicker from "react-datepicker";

import { Container, Navbar, Form, Button } from "react-bootstrap";

import "react-datepicker/dist/react-datepicker.css";
import useDebounce from "@/hooks/common/useDebounce";
import useImgUpload from "@/hooks/common/useImgUpload";

잘 적용된 것 같네요!

 

 

4. 규칙에 맞게 수정하기

 

import 순서 규칙 작성이 끝났다면, 이 규칙에 맞게 import문을 작성해주어야 합니다. 만약 규칙에 맞지 않다면 아래와 같이 Eslint 에러가 나타나게 됩니다.

 

위에 ‘react’에서의 Import문 순서가 가장 먼저가 되도록 작성했었죠?

그 규칙에 맞게 ‘react’ import가 ‘react-router-dom’보다 먼저 와야 된다는 메시지를 보여주고 있습니다.

이런 에러를 하나하나 찾아서 작성한 규칙을 참고하며 수정하는건 참 번거로운일입니다.

그래서 우리가 작성한 import규칙에 맞게 자동으로 수정 되는 방법을 알아보겠습니다. 제가 알려드리는 방법은 두가지 입니다.

 

Eslint Fix

Eslint 수정 명령어를 터미널에 입력하여 실행합니다.

package.json 파일의 “scripts”에 명령어를 아래와 같이 지정해주고 터미널에 입력하면, 한번에 모든 코드에서 import 순서 규칙을 적용하여 수정할 수 있습니다.

"scripts": {
	"lint:fix": "eslint . --ext .ts,.tsx --fix"
}

//확장자가 '.ts' 또는 '.tsx'인 파일들에 대해서 lint를 실행 후, 수정합니다.

 

터널에는 다음과 같이 입력하면 됩니다.

npm run lint:fix

 

또는 개발 모드를 구동 할 때, (npm run dev) 스크립트에 해당 명령어를 입력하면, lint 수정 후 개발모드가 켜지게 할 수도 있습니다.

 

On Save시 수정

이 방법은 lint 실행 명령어 보다 더 간단합니다. 작업 저장 시, 자동으로 lint 설정에 맞게 수정되게 하는 방법입니다.

단, IDE를 Visual Studio Code를 사용한다는 전제하에 입니다…! 😅

Vscode를 켜주시고 ctrl + shift + p를 누르고 setting.json을 검색하여 켜줍니다.

그러면 이것저것 사용자의 설정이 나와 있는 json 파일이 열리는데요, 이 때 아래와 같이 작성하여 추가해줍니다.

"editor.codeActionOnSave" : {
		"source.fixAll.eslint" : "explicit"
}

이렇게 설정 후, eslint 오류가 있는 코드 파일을 저장하게 되면 저절로 빠른 수정이 된 후 적용이 됩니다.

만약에, 저장 될 때마다 수정되는 이 기능을 비활성화 하고 싶으시면, “explicit”을 “never”로 바꿔주시면 됩니다.

 

 

이렇게 Eslint로 import 순서 규칙을 정하는 방법과 또 왜 수정을 해주는게 좋은지, 수정하는 방법은 무엇이 있는지 알아봤습니다.

이 글이 많은 도움이 되었으면 좋겠네요!