FSD를 위한 ESLint 플러그인 개발 일지
아래의 링크에서 사용할 수 있습니다.
배경

항해 플러스의 과정 중 '클린 코드' 교육이 있다.
과정을 들으면서 리팩토링의 핵심 요인이나, 코드 품질을 높이기 위한 방법을 배웠다.
그리고, 그 과정에서 Feature-Sliced-Design(FSD)
의 개념을 배우고 적용해보았다.
강제되는 규칙이 많아, 프로젝트나 네이버 웹툰 인턴을 하며 경험중인 실무에서 이를 적용해보려고 하는데, 마땅한 Lint 플러그인이 없어서 불편함을 느꼈다.
그래서, 이를 개발하기로 결정했다.
문제 상황
만약 도구를 쓴다면, 무조건 만들려고 하기 보다는 이미 있는 것을 활용하는 것이 좋다.
내가 가진 개발 철학 중 하나이다. 소프트웨어의 장점 중 하나는 '재사용성'이다. 그래서, 리서치 과정을 꼭 거치는 편이다.
공식 문서에서는 Steiger라는 도구를 권장하고 있었다.
그러나, 내가 필요한 설정은 없었고, ESLint
가 아닌 별도 라이브러리이기에 설치해서 관리해야한다는 어려움이 있었다.
비교 항목 | Steiger (별도 CLI 툴) | ESLint 플러그인 |
---|---|---|
설치 및 유지보수 | - 프로젝트에 별도 툴(클라이언트/서버) 설치 필요 - 추가 의존성 및 버전 관리 필요 - 툴 업데이트 시 팀 전체에 재설치 안내 | - 이미 존재하는 ESLint 구성에 플러그인만 추가 - 별도 툴 관리 부담 감소 - ESLint 버전 관리만 신경 쓰면 됨 |
개발 워크플로 및 에디터 통합 | - 전용 CLI를 실행해야 하므로, IDE 내 즉각적인 피드백이 어려울 수 있음 - 에디터마다 Steiger 관련 플러그인이 있을 수도 있지만, 보편적이지 않을 수 있음 | - VSCode, WebStorm 등 거의 모든 IDE에서 ESLint를 바로 연동 - 실시간으로 린트 에러 확인 가능 - GitHooks/CI 파이프라인에서 별도 세팅 없이 자동 검사 가능 |
규칙 제어 및 세분화(린트 룰) | - 제공되는 규칙을 기반으로 사용해야 함 - 규칙 세분화나 직접적인 커스터마이징(오픈소스 기여 제외)은 제한적 - CLI 기반이므로, 에디터 내 오토픽스(autofix) 기능 사용이 어려울 수 있음 | - ESLint의 Rule 시스템을 그대로 활용 가능 - 규칙 세분화 및 에러/워닝 등 심각도 설정이 자유롭고, 예외 처리를 쉽게 구성 - AST 분석을 통한 깊이 있는 룰 작성 가능 - ESLint autofix 등 자동 수정 활용 가능 |
온보딩 및 교육 | - 새로 들어온 팀원에게 Steiger 도구 설치 및 사용법을 추가로 안내해야 함 - ESLint 경험만 있는 사람에게는 별도 툴 학습 부담 | - 대부분의 개발자가 ESLint에 익숙 - “ESLint 룰이 추가됐다” 정도로 안내하면 되므로 학습 부담 최소화 - 마이그레이션 및 팀 합의가 수월 |
확장성 및 커스터마이징 | - 도구 자체가 제공하는 옵션, 버전을 고려해야 함 - 고도화나 수정이 필요한 경우 툴 자체 업데이트를 기다려야 함 - 오픈소스 기여 과정이 복잡할 수 있음 | - 오픈소스로 릴리스 시, 빠른 피드백 및 기여 가능 - TypeScript 확장 시 @typescript-eslint/parser 등과 쉽게 연동 - 프로젝트 요구사항에 맞게 룰을 추가/변경하기 용이 |
위와 같은 이유로, ESLint
플러그인을 리서치햇다.
검색하는 과정에서 몇가지 도구를 찾았다. 그러나, 이들은 저마다 문제를 안고 있었다.
도구 | 설명 | 불편한 점 |
---|---|---|
Eslint plugin for FSD best practices | FSD BP를 기준으로 만들어진 ESLint이다. | 마지막 관리가 4~5년전으로, 관리가 제대로 이루어지지 않고 있다. 이에 따라서, ESLint 9 이상의 최신 버전에서 강제되는 Flat Config 에 대한 대응이 부족했다. |
ESLint plugin fsd import | 아마도 개인이 사용하기 위해 만들어진 Lint 같았다. | 관리가 제대로 되고 있지 않으며 기능도 제한적이다. 신뢰성이 적어 사용이 어려웠다. |
@feature-sliced/eslint-config | 135개의 스타와 8000명 이상이 다운받은 FSD를 위한 Lint 이다. | 제대로 된 관리가 2022년이며, Flat Config 의 대응이 되고 있지 않았다. |
이 외에도 여러가지 도구를 찾아보았으나, 마음에 쏙 드는 플러그인이 없었다.
필요한 요소 중, 특히 핵심적으로 다루어야 하는 부분이 Flat Config
에 대한 대응이었다.

특히나, 공식문서에서 ESLint 8.x 버전 이전의 지원이 끊긴다고 언급하고 있으므로, 신생 프로젝트에서는 ESLint 9 이상을 사용이 필요했다.
내가 찾은 방법에서는 특정 ESLint Plugin
이 Flat Config
를 제공하지 않을 경우, 플러그인 자체의 코드를 수정하지 않고서는 이를 Flat Config
에 적용할 수 있는 방법이 없었다.
그래서, 이를 해결하기 위해 직접 ESLint Plugin
을 만들기로 결정했다.
Flat Config란 무엇인가?
Flat Config란?
ESLint 8.23.0 이후부터 도입된 새로운 설정 방식이다.
기존의 .eslintrc
파일 대신 Javascript 기반의 배열 형식으로 설정하는 방식이다.
기존 방식(.eslintrc.json) vs Flat Config
- 기존 방식(.eslintrc)
- Flat Config
{
"extends": "eslint:recommended",
"env": {
"node": true
},
"rules": {
"no-console": "warn"
}
}
단점
- JSON 기반이라 조건문, 변수 사용 등이 불가능하다.
import js from '@eslint/js';
export default [
js.configs.recommended, // eslint:recommended와 동일
{
languageOptions: {
globals: {
console: 'readonly',
},
},
rules: {
'no-console': 'warn',
},
},
];
장점
- JavaScript 문법 사용 가능 → import / export, 변수, 조건문 활용 가능 하다.
- 설정의 유연성 증가 → 여러 개의 설정을 배열로 조합 가능하다.
- 속도 개선 → 필요 없는 규칙을 줄이고, 트리 구조 없이 배열을 병합하는 방식으로 처리가능 하다.
Flat Config의 주요 문법
Flat Config는 배열(export default []
)을 기반으로 여러 설정을 조합하는 방식이다.
1. 기본 구조
export default [
{
languageOptions: {},
rules: {},
plugins: {},
settings: {},
},
];
각 객체는 별도의 설정 블록으로 독립적으로 존재할 수 있다.
2. languageOptions
: 언어 설정
JavaScript 환경을 설정하는 옵션이다.
import globals from 'globals';
export default [
{
languageOptions: {
ecmaVersion: 'latest', // 최신 ECMAScript 버전 사용
sourceType: 'module', // ESM(ES Modules) 사용
globals: globals.browser, // 브라우저 환경 전역 변수 허용
},
},
];
옵션 | 설명 |
---|---|
ecmaVersion | 지원할 ECMAScript 버전 ("latest" , "2021" , "2020" 등) |
sourceType | "module" (ESM) 또는 "script" (CommonJS) |
globals | 전역 변수 (예: globals.node , globals.browser ) |
ECMAScript 최신 버전 설정
languageOptions: {
ecmaVersion: "latest",
}
Node.js 환경 설정
languageOptions: {
globals: globals.node,
}
브라우저 환경 설정
languageOptions: {
globals: globals.browser,
}
3. rules
: ESLint 규칙 설정
Flat Config에서는 규칙을 rules
객체에 정의한다.
export default [
{
rules: {
'no-console': 'warn', // console.log 사용 시 경고
eqeqeq: 'error', // 일치 연산자(===) 강제
},
},
];
"off"
: 규칙 비활성화"warn"
: 경고만 표시"error"
: 오류 처리
4. plugins
: 플러그인 추가가
Flat Config에서 플러그인은 직접 import
후 추가해야 한다.
import ts from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
{
languageOptions: {
parser: tsParser,
},
plugins: {
'@typescript-eslint': ts,
},
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
},
},
];
5. extends
대신 배열 병합
기존 .eslintrc
에서 "extends"
를 사용하던 방식 대신, Flat Config에서는 배열을 통해 설정을 조합한다.
- 기존 방식(.eslintrc)
- Flat Config
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
}
import js from '@eslint/js';
import ts from '@typescript-eslint/eslint-plugin';
export default [
js.configs.recommended, // ESLint 기본 추천 설정
ts.configs.recommended, // TypeScript 추천 설정
];
배열을 통해서 여러 설정을 순차저긍로 병합할 수 있다.
6. overrides
: 특정 파일에 대한 별도 규칙 설정
특정 파일 패턴에 대해 별도의 규칙을 적용할 수 있다.
- 예제 1
- 예제 2
export default [
{
files: ['test/**/*.js'],
rules: {
'no-console': 'off', // 테스트 파일에서는 console.log 허용
},
},
];
export default [
{
files: ['**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
},
},
];
7. ignores
: 특정 파일/폴더 제외
특정 파일이나 폴더를 예외처리 할 수 있다.
- 예제 1
- 예제 2
export default [
{
ignores: ['node_modules/', 'dist/', '.next/'],
},
];
export default [
{
ignores: ['.env'],
},
];
요약
특징 | .eslintrc.json (기존) | eslint.config.js (Flat Config) |
---|---|---|
설정 방식 | JSON/YAML 기반 | JavaScript 코드 가능 |
extends 사용 | 문자열 배열 | 객체 배열 |
plugins 설정 | "plugins": ["@typescript-eslint"] | import 후 객체 추가 |
환경 설정 | "env": { "node": true } | globals.node 사용 |
파일별 규칙 적용 | overrides 배열 사용 | files 키 사용 |
특정 파일 무시 | .eslintignore 필요 | ignores 키 사용 |
프로젝트 목표
Feature-Sliced-Design
의 컨벤션을 준수하는ESLint Plugin
을 만든다.Flat Config
를 지원한다.ESLint Plugin
을 만들면서,ESLint
의 동작 방식을 이해한다.
사실 있는 설정을 가져다쓰기만 했지, ESLint에 대해서 제대로 이해하고 있지 않았다.
그래서 이번에 도전을 하면서, ESLint에 대해 제대로 이해하고, 이를 통해 Feature-Sliced-Design
의 컨벤션을 준수하는 ESLint Plugin
을 만들어보기로 했다.
계획
처음 시도해보는 도전이라, 모든게 새로웠고, 모든게 장벽이었다.
부족한게 많기에 전략적으로 접근할 필요가 있었다.
목표를 수립하고, 이를 효과적으로 달성하기 위해서 계획을 세웠다.
계획 | 내용 |
---|---|
1. 기본 개념 이해 및 환경 준비 | - ESLint의 기본 개념과 작동 방식을 학습. - ESLint 플러그인을 만들기 위한 기본 환경 설정. - NPM 패키지 및 모듈 관리 기본 학습. |
2. ESLint 규칙의 기본 구조 이해 | - 간단한 커스텀 규칙 작성해보기. - ESLint의 AST(Abstract Syntax Tree)와 ESLint Rule API 이해. |
3. Feature-Sliced Design 이해, 관련 규칙 설계 및 구현 | - Feature-Sliced Design의 구조와 철학 학습. - 설계 원칙을 ESLint 규칙으로 변환하는 방법 구상. - FSD를 위한 규칙 초안 작성. |
4. ESLint 플러그인 개발 | - 여러 규칙을 플러그인으로 패키징. - 플러그인 테스트 및 디버깅. |
5. 배포 준비 및 테스트 | - 플러그인의 문서 작성. - 플러그인을 NPM에 배포하는 과정 학습. |
6. 실제 프로젝트에 적용해보며 테스트 | - 실제 프로젝트에 적용해보면서 정상적으로 동작하는지 사용성 테스트 진행. |
GPT와 함께 짠 커리큘럼이다. 이를 바탕으로 천천히 따라가면서 프로젝트를 진행하기로 했다.
본 글도 이 과정대로 진행할 예정이다.
1단계: 기본 개념 이해 및 환경 준비
목표
- ESLint와 Prettier의 역할과 작동 방식을 이해한다.
- ESLint 플러그인 개발을 위한 기본 환경을 설정한다.
- NPM을 사용한 패키지 관리와 개발 환경 설정을 익힌다.
ESLint란 무엇인가?
들어가기에 앞서서, ESLint가 무엇인지 간단하게 알아보자.
Find and fix problems in your javascript code.
위의 설명에서 확인할 수 있듯이, 자바스크립트 코드에서 문제를 찾아서 수정하는 도구이다.
ESLint statically analyzes your code to quickly find problems. It is built into most text editors and you can run ESLint as part of your continuous integration pipeline.
ESLint는 코드를 정적으로 분석하여 빠르게 문제를 찾아내는 것과, 일관된 코드 품질 유지를 목적으로 한다.
라이브러리나 프레임워크에 종속되지 않고, 대부분의 텍스트 에디터에서 사용할 수 있다.
또한, CI 파이프라인에서도 ESLint를 사용할 수 있다.
ESLint의 주요 기능 요약
ESLint의 주요 기능을 요약하면 다음과 같다.
기능 | 설명 |
---|---|
문법 오류 감지 | 코드에서 발생 가능한 오류를 잡아낸다. |
코드 스타일 체크 | 팀 규칙이나 코드 스타일을 유지하도록 도와준다. |
자동 수정(Auto-fix) | 가능한 경우, 문제를 자동으로 고쳐준다. |
확장 가능 | 플러그인이나 규칙을 추가하여 원하는 대로 확장할 수 있다. |
ESLint가 중요한 이유
ESLint는 코드 품질을 유지하고, 팀원들 간의 코드 컨벤션을 일관되게 유지하는데 중요한 역할을 한다.
특히, 대규모 프로젝트에서는 이러한 도구가 필수적이다.
디버깅 시간을 줄여주고, 컨벤션과 관련된 불필요한 자원 낭비를 예방해준다.
즉, 생산성을 높여주는 도구이기에 중요하다고 할 수 있다.
ESLint의 동작 원리
ESLint는 다음과 같은 과정으로 동작한다.
1. 코드 입력
ESLint는 검사할 JavaScript 또는 TypeScript 파일을 읽어들인다.
2. 파싱 (Parsing)
코드를 분석하기 위해 파서(Parser)를 사용하여 AST(Abstract Syntax Tree, 추상 구문 트리)로 변환한다.
기본적으로 ESLint는 espree라는 파서를 사용하지만, TypeScript를 지원하려면 @typescript-eslint/parser 같은 커스텀 파서를 사용할 수도 있다.
AST는 코드 구조를 트리 형태로 표현하는 데이터 구조이다.
const sum = (a, b) => a + b;
예시로 이 코드는 다음과 같은 AST로 변환된다.
Program
├── VariableDeclaration (const sum)
│ ├── VariableDeclarator
│ │ ├── Identifier (sum)
│ │ ├── ArrowFunctionExpression
│ │ ├── Parameters (a, b)
│ │ ├── Body (BinaryExpression +)
3. 규칙(Rules) 적용
ESLint는 AST를 순회하면서 설정된 규칙(Rules)을 적용한다.
규칙은 노드 유형(Node Type)에 따라 동작하며, 특정한 패턴이 발견되면 오류를 보고하거나 자동으로 수정할 수 있다.
예를 들어서 no-console
규칙이 활성화되어 있다면, console.log()
같은 코드가 있으면 ESLint가 경고를 띄워준다.
이때, ESLint는 각 노드에 대해 방문자(Visitor) 패턴을 사용하여 규칙을 검사한다.
module.exports = {
create(context) {
return {
Identifier(node) {
// (1) 방문자 함수: Identifier 노드 방문 시 실행
if (node.name === 'temp') {
// (2) 특정 조건 검사
context.report({
node,
message: "'temp'라는 변 수명을 사용하지 마세요.", // (3) 규칙 위반 보고
});
}
},
};
},
};
이 규칙은 temp
라는 변수를 사용하면 ESLint가 경고를 띄우는 간단한 예제이다.
방문자(Visitor) 패턴이란?
방문자 패턴(Visitor Pattern)이란?
방문자 패턴(Visitor Pattern)
은 객체의 구조는 변경하지 않으면서 새로운 기능을 추가할 수 있도록 하는 행동 패턴(Behavioral Pattern)
중 하나이다.
방문자 패턴은 객체의 구조와 그 구조에서 수행되는 작업을 분리하여 구현하는 패턴이다.
객체 구조를 이루는 요소들을 독립적으로 변경할 수 있도록 하는 패턴으로, 객체의 구조와 구조에서 수행되는 작업을 분리하여 구현함으로써 객체의 확장성과 유연성을 높이는데 목적이 있다.
즉, 기존 클래스의 구조를 수정하지 않고도 새로운 연산을 쉽게 추가할 수 있도록 돕는 디자인 패턴이다.
방문자 패턴은 어떤 문제를 해결하기 위해 등장하였는가?
프로그램을 개발하다 보면 어떤 객체(클래스)에 새로운 기능(메서드)를 추가해야하는 경우가 많다.
하지만, 기존 클래스를 수정하면 유지보수가 어려워지고 코드가 점점 복잡해지는 문제가 발생한다.
특히 객체들의 구조(클래스 설계)는 그대로 유지하면서, 기능만 유연하게 확장하고 싶을 때 어려움이 생긴다.
방문자 패턴의 목적
객체의 구조(클래스)는 변경하지 않으면서 새로운 기능을 추가하는 것이 목적이다.
즉, 객체(Element) 자체는 건드리지 않고, 방문자(Visitor)가 새로운 기능을 수행하도록 만들자는 것이다.
이를 통해서 기존 코드의 수정 없이 유연하게 기능을 추가하고자 한다.
실생활 예제를 통해 이해하기
🏡 실생활 예제: 집(객체)과 수리공(방문자)
🏠 집(객체)과 수리공(방문자)를 예시로 들어서 이해해보자.
당신은 집(객체)를 갖고 있다.
집에는 다양한 방(Rooms)이 있다.
어느 날, 당신은 집에 전기 점검도 하고 싶고, 페인트칠도 하고 싶다. 🎨⚡
하지만, 집의 구조는 바꾸고 싶지 않다. 😵
🛠 해결 방법: 방문자 패턴 적용
집의 방(Rooms)마다 일일이 기능을 추가하지 않고, 전문가(Visitor)를 초대해서 일을 맡기자 라는게 발상이다.
전기기사(Electician), 페인트공(Painter) 같은 방문자가 집의 각 방(Room)을 방문하면서 필요한 작업을 해주는 방식이다.
방문자 패턴을 코드로 이해하기
객체 구조 (Element)
- 집에는 여러개의 방(객체)이 있다.
interface Room {
accept(visitor: RoomVisitor): void; // 방문자(Visitor)를 받아들임
}
class Bedroom implements Room {
accept(visitor: RoomVisitor): void {
visitor.visitBedroom(this);
}
}
class Kitchen implements Room {
accept(visitor: RoomVisitor): void {
visitor.visitKitchen(this);
}
}
방문자 인터페이스 (Visitor)
- 각 방을 방문하여 특정 작업을 수행하는 방문자(전문가)를 정의한다.
interface RoomVisitor {
visitBedroom(room: Bedroom): void;
visitKitchen(room: Kitchen): void;
}
구체적인 방문자 (Concrete Visitor)
- 전기기사(Electician)와 페인트공(Painter)이 방을 방문해서 작업을 수행한다.
class Electrician implements RoomVisitor {
visitBedroom(room: Bedroom): void {
console.log('🔌 침실에서 전기 점검 중...');
}
visitKitchen(room: Kitchen): void {
console.log('🔌 주방에서 전기 점검 중...');
}
}
class Painter implements RoomVisitor {
visitBedroom(room: Bedroom): void {
console.log('🎨 침실에 페인트칠 중...');
}
visitKitchen(room: Kitchen): void {
console.log('🎨 주방에 페인트칠 중...');
}
}