[Review] 모노레포 기반 ESLint-Config 구축 및 실무 적용기

사내 프로젝트가 늘어나면서 가장 먼저 마주한 장벽은 '파편화된 코드 컨벤션'이었습니다. 프로젝트마다 ESLint 설정이 달라 협업 시 일관성이 저하되고, 새로운 규칙을 전사적으로 적용하기 위해 모든 레포지토리를 일일이 수정해야 했던 비효율을 어떻게 모노레포 기반의 NPM 패키지화로 해결했는지 공유합니다.

1. 전사 표준화: "설정 파일이 아닌 라이브러리로 관리하기"

많은 팀이 .eslintrc 파일을 복사해서 사용하지만, 저는 유지보수의 한계를 극복하기 위해 설정을 NPM 패키지로 구축했습니다.

  • 린트 설정 파편화: 새로운 컨벤션 적용 시 모든 프로젝트 레포지토리를 개별적으로 수정해야 하는 번거로움이 있었습니다.

  • 해결 전략: 공통 설정을 중앙에서 제어하고 배포할 수 있는 시스템을 위해 NPM 패키지화를 통한 버전 관리 체계를 수립했습니다. eslint-config-dotori-* 시리즈를 통해 복잡한 설정을 한 번의 설치로 단순화했습니다.


2. 모노레포 설계: pnpm과 유령 의존성(Phantom Dependencies) 해결

효율적인 패키지 관리를 위해 모노레포 환경을 구축하면서, 의존성의 안정성을 최우선으로 고려했습니다.

🛠️ 왜 pnpm인가? (유령 의존성 차단) 기존 npm이나 yarn v1의 호이스팅(Hoisting) 방식은 package.json에 명시하지 않은 패키지에도 접근할 수 있게 만드는 '유령 의존성' 문제를 유발합니다. 이는 공유 설정 패키지처럼 수많은 플러그인을 다루는 환경에서 치명적입니다.

  • 문제점: 직접 설치하지 않은 라이브러리에 의존하다가, 엉뚱한 패키지가 업데이트되어 의존성 트리가 변하면 린트가 갑자기 동작하지 않는 불안정성이 존재했습니다.

  • 해결: pnpm심볼릭 링크(Symbolic Link) 기반의 엄격한 구조를 사용하여 오직 명시된 패키지만 접근하도록 강제합니다. 덕분에 어떤 프로젝트 환경에서도 동일하게 동작하는 신뢰도 높은 린트 환경을 확보했습니다.


3. 표준 설정 수립: 환경별 dotori-eslint 시리즈 구축

검증된 airbnb 설정을 베이스로 채택하고, 사내 환경에 맞춰 필요한 설정을 조합할 수 있도록 패키지를 세분화했습니다.

가장 기본이 되는 설정부터 타입스크립트, 리액트, 그리고 코드 정렬 기능을 각각 설치합니다.

  • 패키지 구성: base, typescript, react, import-sort 등으로 나누어 프로젝트 성격에 맞게 선택적 설치가 가능합니다.

# 핵심 설정 패키지 설치
npm install eslint-config-dotori-base eslint-config-dotori-react eslint-config-dotori-import-sort eslint-config-dotori-typescript --save-dev

# 필수 의존성(Peer Dependencies) 설치
npm install eslint --save-dev
{
  "extends": [
    "eslint-config-dotori-base",        // 1. 기본 자바스크립트 규칙
    "eslint-config-dotori-typescript",  // 2. 타입스크립트 지원
    "eslint-config-dotori-react",       // 3. 리액트 전용 규칙
    "eslint-config-dotori-import-sort"  // 4. 임포트 자동 정렬
  ]
}

각 패키지는 특정 도메인의 규칙을 담당하며, 서로 의존하거나 독립적으로 동작합니다.

① eslint-config-dotori-base

  • 역할: 모든 프로젝트의 뿌리가 되는 기본 설정입니다.

  • 주요 기능: 가장 널리 검증된 airbnb-base 규칙을 계용하며, 자바스크립트의 기본적인 문법 오류나 잠재적인 버그를 잡아줍니다. 어떤 환경에서도 반드시 포함되어야 하는 핵심 패키지입니다.

② eslint-config-dotori-typescript

  • 역할: 타입스크립트 환경을 위한 특화 설정입니다.

  • 주요 기능: @typescript-eslint/parser를 사용하여 TS 문법을 해석하고, 타입 정의 관련 규칙이나 인터페이스 네이밍 컨벤션 등을 강제합니다. base 설정 위에 얹어서 사용합니다.

③ eslint-config-dotori-react

  • 역할: 리액트 프로젝트를 위한 UI 관련 규칙입니다.

  • 주요 기능: JSX 문법 검사, 리액트 훅(Hooks)의 호출 순서 준수 여부, 컴포넌트 선언 방식 등을 관리합니다. Next.js 프로젝트에서도 핵심적인 역할을 수행합니다.

④ eslint-config-dotori-import-sort

  • 역할: 코드 가독성을 위한 자동 정렬 도구입니다.

  • 주요 기능: 무분별하게 섞여 있는 import 구문들을 외부 라이브러리, 내부 모듈, 상대 경로 순으로 자동 정렬해 줍니다. 협업 시 파일 상단의 import 영역을 깔끔하게 유지하여 코드 리뷰의 피로도를 낮춰줍니다.

모든 규칙을 하나의 패키지에 담지 않고 굳이 4개로 분리한 이유는 의존성 최적화 때문입니다.

  • 관심사의 분리: 자바스크립트 전용 프로젝트에 굳이 무거운 타입스크립트 파서나 리액트 플러그인을 설치할 필요가 없습니다.

  • 확장성: 새로운 환경(예: Vue나 Svelte)이 추가되더라도 기존 패키지를 건드리지 않고 eslint-config-dotori-vue만 새로 만들어 모노레포에 추가하면 됩니다.

  • 유령 의존성 방지: 앞서 언급한 pnpm의 철학과 맞닿아 있습니다. 각 패키지는 자신이 필요한 플러그인만 명확히 관리하므로, 설치 시 의존성 트리가 꼬이는 현상을 방지합니다.

  • 최신 환경 대응: ESLint 8 버전의 레거시 설정뿐만 아니라, 최신 ESLint 9(Flat Config) 환경에서도 FlatCompat을 통해 즉시 적용 가능하도록 설계했습니다.

const eslintConfig = [
  ...compat.extends(
    'next/core-web-vitals',
    'eslint-config-dotori-base',
    'eslint-config-dotori-typescript',
    'eslint-config-dotori-react',
    'eslint-config-dotori-import-sort',
  ),
  // ... 생략
];

4. 결과: "유지보수 효율 극대화와 문화의 개선"

인프라 구축 이후, 사내 개발 환경에는 눈에 띄는 변화가 생겼습니다.

  • 전사 표준화 달성: 분산되어 있던 설정을 하나의 라이브러리로 통합했습니다. 이제 각 프로젝트는 extends 배열에 패키지명을 추가하는 것만으로 전사 컨벤션을 즉시 동기화합니다.

  • 유지보수 효율 극대화: 새로운 규칙 도입 시 모노레포 한 곳만 수정하면 전사 프로젝트에 일괄 적용이 가능한 구조를 완성했습니다.

  • 자기주도적 개선: 간과되기 쉬운 운영 효율화 영역을 스스로 학습하고 도입하여, 조직 전체의 개발 문화를 개선하고 기술적 부채를 해결하는 성과를 거두었습니다.


마치며

단순히 "코드를 정돈한다"는 개념을 넘어, 운영 효율화의존성 안정성을 위한 시스템을 고민해 보았습니다. 특히 pnpm을 통해 유령 의존성 문제를 원천 차단한 것은, 패키지 관리의 신뢰도를 한 단계 높이는 중요한 결정이었습니다. 이러한 기반 위에서 전사 프로젝트가 ESLint 9과 같은 변화에도 유연하게 대응할 수 있게 되었다는 점이 가장 큰 보람입니다.