본문 바로가기
공부/Project

eslint-plugin-layered-imports 제작기 - 1 | 구조를 지키는 ESLint 규칙 만들기

by Electrohyun 2026. 6. 17.

<eslint-plugin-layered-imports 제작기 시리즈> 이어서 보기


ESLint 플러그인 만들기 재밌당 히히

 

 안녕하세요! electrohyun입니다. 이번에는 프로젝트 관련 글을 작성하게 되네요. 주기적으로 글을 작성하고 싶다는 생각이 드는데, 이번 여름방학에는 자주 글을 올려보려고 합니다. 이번엔 그 첫번째 글로서, 제가 현재 진행중인 프로젝트에 적용한 ESLint 플러그인의 개발 일지를 올려보려고 합니다. 개발은 이미 완료되었고, npm에도 배포중이에요! 혹시 오늘 글을 읽고 관심이 생기셨다면 한번 사용해보신 후 피드백을 남겨주시면 감사하겠습니다! 그럼 시작하겠습니다.


예시

 이 플러그인의 기능은 다음과 같습니다.

 

(1) import의 그룹을 ["builtin", "external", "internal", "relative"] 4가지로 나누고, 이들 간 빈 줄을 두어 보기 쉽게 구분합니다.

(2) 규칙이 잘못된게 있다면 자동으로 고칩니다(autofix)

(3) (커스텀) 이러한 그룹 순서는 유저가 지정할 수 있습니다.

(4) (커스텀) internal, 즉 내부 경로, alias를 사용하는 경우, ~/, @app/, @/ 등 다양한 형태를 지원합니다.

(5) (프리셋) 제가 사용하는 구조, FSD 친화적인 느낌의 프리셋을 제공하고 있습니다.

 

 꽤 간단한 기능인데요, 왜 시리즈 글로 작성하게 되었냐면... ESLint 플러그인을 처음 개발하면서 생각보다 고려할 점이 많았습니다. 고민을 정말 많이 했고, 그것 이외에도 프론트엔드 개발에 React를 사용하는 만큼 함수형 프로그래밍을 진행해왔지만, 플러그인을 작성하면서 로직을 짜고 고민하는 부분이 꽤 재미있고 보람찬 경험이었기 때문입니다. 시리즈는 총 5편으로 구성 예정입니다. 이번 글에서는 플러그인을 만들게 된 계기와, import 정리 규칙을 다룬 과정을 작성하려 합니다.

 


(1) 플러그인을 만들게 된 계기

 

 작성 시점은 2026년 6월이지만, 첫 고민은 2025년 12월에 시작했습니다. 저는 당시 실시간 드로잉 웹 게임 폴리카소 프로젝트를 진행하며 프론트엔드 파트를 담당했습니다. 해당 파트를 혼자서 진행하는 동안 코드에 대한 검증이나, 일관된 규칙같은 것들을 지키기가 어렵다는 느낌을 받았고, 실제로 문제를 겪었습니다.

 

 import 구문의 배치는 그 어려움 중 하나였습니다. 초기에는 크게 신경쓰이지 않았습니다. 그러나 당시 Public API 개념도 모호했기에 어떤 것은 폴더 안의 해당 파일명까지 import from이 들어가는데, 어떤 파일은 폴더명까지만 작성하게 되고... 요약하자면 일관되지 않은 상황이 반복되었습니다.

 

 혼자서 개발한 뒤 만든 그대로 끝이라면 편하겠지만, 코드는 작성하고 나면 유지보수가 이뤄져야 합니다. 새로운 기능이 추가될 수도 있고, 버그가 발생할 수도 있습니다. 이런 상황에서 새 코드를 작성하거나, 기존 코드를 수정하는 사람의 입장에서 생각해본다면, 빠르게 그 내용을 파악할 수 있어야 합니다. 그런 점에서 코드는 일관되게 작성되어야 하고, 규칙을 준수해야 합니다. 혼자서 작성하더라도 적용되는 규칙입니다. 지금의 나와 미래의 나는 같지 않으니까요.

 

 그리하여 문제 상황으로는, 외부 라이브러리, 내부 alias, 상대 경로 import가 섞이고, 어떤 파일은 빈 줄로 잘 나뉘어 있는데 어떤 파일은 전부 붙어 있습니다. 당장 기능이 깨지는 문제는 아니지만, 파일을 열었을 때 구조가 한눈에 들어오지 않는다는 점이 계속 거슬렸습니다.

 

 해당 프로젝트에서는 FSD 구조를 사용했습니다. Feature Sliced Design에서는 features, entities, shared 같은 레이어를 기준으로 코드를 나누기 때문에, import 구문도 단순한 코드 조각이 아니라 “이 파일이 어떤 레이어에 의존하고 있는지”를 보여주는 단서가 됩니다. 그런데 부분을 매번 직접 보고 일관되게 작성하는 건 불가능에 가까웠습니다. 코드 리뷰에서 “import 순서 정리해주세요라는 리뷰가 들어왔을 때, 모든 파일의 import가 다를 거라는 상황을 떠올렸습니다. 기능과 직접 관련 없는 리뷰가 반복되는 느낌도 있었고, 무엇보다 이런 규칙은 사람이 기억해서 지키기보다 도구가 자동으로 잡아주는 편이 맞다고 생각했습니다.

 

 당시에 ESLint 플러그인이라는 아이디어를 바로 떠올릴 수 있었던 건 아닙니다. 그러나 2026년 5월, 제가 개인 프로젝트로서 진행중인 Git 학습 게임 <꼬깃>을 개발하면서 같은 상황이 반복되었죠. 이때가 되어서, 떠올릴 수 있었습니다. 매번 개발 초기에 설치하는 ESLint와 Prettier를 무슨 목적으로 설치했는지!

 

 

 Prettier는 코드의 모양을 맞춰줍니다. 들여쓰기, 따옴표, 세미콜론처럼 사람마다 다르게 작성할 수 있는 부분을 자동으로 정리해줍니다. ESLint는 코드의 모양을 맞추고, 프로젝트에서 지켜야 할 규칙을 검사하는 역할을 합니다. 그렇다면 import 구조도 ESLint가 검사할 수 있지 않을까? 하는 생각이 들었습니다.

 

 컨벤션 문서에 “import는 이런 순서로 작성해주세요”라고 적어두는 것도 방법입니다. 하지만 문서는 결국 사람이 읽고 기억해야 합니다. 프로젝트를 진행하다 보면 기능 구현에 집중하게 되고, 이런 작은 규칙은 쉽게 흐트러집니다. 특히 혼자 개발하는 프로젝트에서는 더 그렇습니다. 리뷰어가 따로 없으니, 코드가 조금씩 흐트러져도 바로 잡아줄 사람이 없습니다.

 

 그래서 이 문제는 문서로만 남길 게 아니라, 도구가 자동으로 잡아주는 규칙이 되어야 한다고 생각했습니다. 제가 원했던 규칙은 복잡하지 않았습니다.

 

 (1) 먼저 import를 builtin, external, internal, relative 네 가지 그룹으로 나누고 싶었습니다. Node.js 내장 모듈, 외부 라이브러리, 프로젝트 내부 alias, 상대 경로 import가 한눈에 구분되기를 바랐습니다.

 (2) 그리고 각 그룹 사이에는 빈 줄을 두어 파일을 열었을 때 import 영역이 자연스럽게 읽히도록 만들고 싶었습니다.

 (3) 여기에 더해 내부 alias는 프로젝트마다 다를 수 있으니 직접 설정할 수 있어야 했습니다. 어떤 프로젝트는 @/를 사용하고, 어떤 프로젝트는 ~/@app/ 같은 alias를 사용할 수 있기 때문입니다.

 (4) 또한 FSD 구조를 사용하는 경우에는 features, entities, shared 같은 내부 레이어의 순서도 어느 정도 맞출 수 있으면 좋겠다고 생각했습니다.

 

 기존에도 import 순서를 정리해주는 플러그인은 많습니다. 하지만 저는 제가 사용하는 구조와 기준에 맞춰, 조금 더 단순하고 명확한 규칙을 직접 만들어보고 싶었습니다. 무엇보다 이번에는 단순히 설정 파일을 가져다 쓰는 것이 아니라, 제가 불편하다고 느낀 문제를 ESLint 규칙으로 직접 표현해보고 싶었습니다.

 

그렇게 eslint-plugin-layered-imports 만들기 시작했습니다.

 


(2) Import 정리 규칙을 어떻게 다룰까?

 

eslint-plugin-layered-imports 로고

 

 플러그인을 만들기로 정한 뒤, 가장 먼저 고민한 것은 “import를 어떤 기준으로 나눌 것인가”였습니다. 알파벳 순서로 정렬하는 것도 사실 맞죠. 하지만 그것만으로는 제가 느낀 불편함을 해결하기 어렵다고 생각했습니다. 제가 보고 싶었던 것은 import source의 이름순도 맞지만, 이 파일이 어디에 의존하고 있는지를 보고 싶었습니다.

 

 그래서 import를 크게 네 가지 그룹으로 나누었습니다.

 

 첫 번째는 builtin, 내장 모듈입니다. node:path, fs처럼 Node.js에서 기본으로 제공하는 모듈입니다.

 두 번째는 external, 외부 패키지입니다. react, clsx, @tanstack/react-query처럼 npm을 통해 설치한 외부 패키지가 여기에 해당합니다.

 세 번째는 internal, 프로젝트 내부 경로입니다. @/shared/ui, @/entities/user처럼 프로젝트 내부 alias를 통해 가져오는 코드입니다.

 마지막은 relative, 상대 경로입니다. ./helper, ../lib/helper처럼 현재 파일을 기준으로 상대 경로를 따라가는 import입니다.

 

 이 기준으로 나누면 import 영역을 읽을 때 가독성이 뚜렷해진다고 느꼈습니다.

 

import path from "node:path";

import React from "react";
import { clsx } from "clsx";

import { userApi } from "@/entities/user";
import { Button } from "@/shared/ui";

import styles from "./style.module.css";
import { helper } from "../lib/helper";

 

 위 예시처럼 그룹을 나누면, 이 파일이 외부 라이브러리에 무엇을 의존하는지, 프로젝트 내부에서는 어떤 레이어를 가져오는지, 주변 파일에서는 무엇을 가져오는지 한눈에 보기 쉬워집니다. 여기서 끝!

 

 은 아닙니다. 이 플러그인은 사실, 아직 한계가 있습니다. 빈 줄 규칙만으로는 부족합니다. 어떤 프로젝트에서는 @/를 내부 alias로 사용하지만, 다른 프로젝트에서는 ~/@app/을 사용할 수도 있습니다. 반대로 @tanstack/react-query 같은 scoped package는 @로 시작하지만 외부 패키지입니다. 그래서 내부 import를 판단하는 기준은 고정값이 아니라 옵션으로 받을 수 있어야 한다고 생각했습니다.

 

 추가적으로, FSD 구조를 사용하는 경우에는 internal import 안에서도 순서가 필요합니다. features, entities, shared가 뒤섞이면 internal 그룹으로 묶이기는 하지만, 레이어의 흐름은 여전히 읽기 어렵습니다. 그래서 내부 레이어 순서를 사용자가 직접 지정할 수 있도록 internalLayerOrder(가칭) 정도의 옵션 또한 필요하다고 판단했습니다.

 

 정리하면, 프로젝트마다 alias와 레이어 구조가 다를 수 있으니, 기본값은 제공하되 필요한 부분은 직접 설정할 수 있는 규칙으로 제작해보자는 생각이 들었습니다. 이렇게 해서 eslint-plugin-layered-imports의 첫 번째 방향이 정해졌습니다.

 

 1. import를 builtin, external, internal, relative로 분류한다.

 2. 서로 다른 그룹 사이에는 빈 줄을 둔다.

 3. 그룹 순서는 설정할 수 있게 한다.

 4. 내부 alias는 프로젝트마다 다르므로 옵션으로 받는다.

 5. FSD 사용하는 프로젝트를 위해 internal layer order 지원한다.

 

 예를 들어, 이런 import 구문이 있다고 가정해보겠습니다.

 

import { Button } from "@/shared/ui"; 
import React from "react"; 
import { userApi } from "@/entities/user"; 
import styles from "./style.module.css"; 
import path from "node:path"; 
import { clsx } from "clsx"; 
import { helper } from "../lib/helper";

 

 기능 자체는 문제 없이 동작하지만, 한눈에 읽기는 어렵습니다. Node.js 내장 모듈, 외부 패키지, 프로젝트 내부 alias, 상대 경로 import가 모두 섞여 있기 때문입니다.

 

 제가 원했던 형태는 다음과 같습니다.

 

import path from "node:path"; 

import React from "react"; 
import { clsx } from "clsx"; 

import { userApi } from "@/entities/user"; 
import { Button } from "@/shared/ui"; 

import styles from "./style.module.css"; 
import { helper } from "../lib/helper";

 

 이렇게 정리하면 import 영역만 보더라도 파일의 의존 관계를 더 빠르게 파악할 수 있습니다. 외부 패키지를 먼저 확인하고, 그다음 프로젝트 내부 레이어를 확인한 뒤, 마지막으로 주변 파일과의 관계를 볼 수 있기 때문입니다.

 

 이 규칙이 모든 프로젝트에 반드시 맞는 정답은 아닙니다. 프로젝트마다 선호하는 import 순서도 다르고, alias 구조도 다를 수 있습니다. 그렇기 때문에 오히려 구조를 강제로 고정하기보다, 기본값은 제공하되 필요한 부분은 옵션으로 조정할 수 있도록 만드는 방향으로 제작을 하게 되었네요.


(3) 정리

 이번 글에서는 eslint-plugin-layered-imports를 만들게 된 배경과, import 정리 규칙을 어떤 기준으로 잡았는지 정리했습니다. 처음 고민은 2025년 12월 프로젝트를 진행하던 시점에서 시작되었고, 프론트엔드를 담당하면서 기능 구현에 집중하는 동안 코드의 일관성을 유지하기가 쉽지 않았고, 특히 import 구문이 파일마다 다르게 작성되는 상황이 반복되었습니다.

 이후 2026년 5월, 개인 프로젝트인 꼬깃을 개발하면서 같은 문제가 다시 보였습니다. 이때 단순히 컨벤션 문서에 규칙을 적어두는 것보다, ESLint 규칙으로 만들어 자동으로 검사하는 편이 낫겠다고 생각했습니다. 그 결과 import를 builtin, external, internal, relative 네 가지 그룹으로 나누고, 그룹 사이의 빈 줄과 순서, 내부 alias, FSD layer order를 다룰 수 있는 플러그인을 만들기 시작했습니다.

 

 다음 글에서는 실제로 ESLint Rule을 어떻게 작성했는지 정리해보려고 합니다. ImportDeclaration을 읽어 import source를 분류하고, 잘못된 그룹 순서나 빈 줄 누락을 어떻게 찾아내는지 다뤄보겠습니다. 긴 글 읽어주셔서 감사합니다! 지금까지 electrohyun이었습니다. 😊

 

 (제가 제작한 플러그인 eslint-plugin-layered-imports는 여기서 확인 가능합니다.)